PHP로 배열을 다루다 보면 특정 키가 존재하는지, 값이 있는지 확인해야 할 순간이 자주 온다. 다만 대부분의 개발자들은 isset(), array_key_exists(), in_array() 세 함수의 차이를 명확히 모른 채 상황에 맞지 않는 함수를 써서 버그를 만든다. 이번에는 이 세 함수가 정확히 뭐가 다르고, 언제 어떤 걸 써야 하는지, 그리고 각각이 어떻게 동작하는지 완벽하게 정리해서 소개하겠다.
먼저 세 함수의 역할을 명확히 구분해보자.
isset()은 변수나 배열 키가 "존재하면서 동시에 NULL이 아닌지"를 확인한다. 키가 없거나, 키는 있지만 값이 NULL이면 false를 반환한다.
array_key_exists()는 배열에 특정 키가 "존재하는지만" 확인한다. 값이 NULL이든 빈 문자열이든 상관없이, 키 자체의 존재 여부만 본다.
in_array()는 배열에 특정 "값"이 존재하는지 확인한다. 키는 무시하고 값의 존재만 본다.
이 세 함수는 확인 대상(키 vs 값)과 NULL 값 처리 방식에서 근본적으로 다르다. 잘못된 함수를 쓰면 예상과 다른 결과가 나온다.
실제 코드로 각 함수의 차이를 비교해보자.
isset() 사용 사례: 키의 존재 여부 + NULL 값 체크
<?php
$user = [
'name' => 'John',
'email' => NULL, // 키는 있지만 값이 NULL
'phone' => '' // 빈 문자열
];
// isset() 테스트
var_dump(isset($user['name'])); // true - 키 있음, 값 있음
var_dump(isset($user['email'])); // false - 키는 있지만 값이 NULL
var_dump(isset($user['phone'])); // true - 키 있음, 빈 문자열은 NULL 아님
var_dump(isset($user['address'])); // false - 키 없음
?>
✓ isset()은 NULL 값을 "없는 것"으로 취급한다. 빈 문자열이나 0은 통과하지만 NULL은 막는다.
array_key_exists() 사용 사례: 키의 존재 여부만 순수하게 확인
<?php
$user = [
'name' => 'John',
'email' => NULL, // 키는 있지만 값이 NULL
'phone' => '' // 빈 문자열
];
// array_key_exists() 테스트
var_dump(array_key_exists('name', $user)); // true
var_dump(array_key_exists('email', $user)); // true - NULL이어도 키가 있으면 true!
var_dump(array_key_exists('phone', $user)); // true
var_dump(array_key_exists('address', $user)); // false
?>
✓ array_key_exists()는 값이 NULL이든 뭐든 키가 존재하면 true. 데이터베이스에서 NULL 값으로 조회된 경우에도 "정보가 있다"고 판단할 수 있다.
in_array() 사용 사례: 배열 내 특정 값의 존재 여부 확인
<?php
$fruits = ['apple', 'banana', 'orange', ''];
// in_array() 테스트
var_dump(in_array('apple', $fruits)); // true
var_dump(in_array('grape', $fruits)); // false
var_dump(in_array('', $fruits)); // true - 빈 값도 배열에 있으면 true
var_dump(in_array(0, $fruits)); // true (주의!) - 느슨한 비교 시 'apple'이 0과 같다고 봄
?>
✗ in_array()는 기본적으로 느슨한 비교(loose comparison)를 한다. 'apple' == 0은 false지만, 엄격한 비교 모드가 아니면 예상치 못한 결과가 나올 수 있다.
| 상황 | 사용할 함수 | 이유 |
|---|---|---|
| 배열 키의 존재 여부 확인 (NULL 값 제외) |
isset() | 키가 있으면서 값이 "실제로 있는" 경우만 true |
| 배열 키의 존재 여부 확인 (NULL 값 포함) |
array_key_exists() | NULL 포함 모든 값을 유효한 데이터로 봄 |
| 배열에 특정 값이 있는지 확인 | in_array(, , true) | 값 자체를 찾되, 엄격한 비교(===)로 타입까지 확인 |
| 폼 데이터 필터링 (빈 값 제외) |
isset() | 사용자가 입력한 값 중 "실제 내용"이 있는지 확인 |
| 데이터베이스 결과 검증 (NULL도 유효한 값) |
array_key_exists() | DB의 NULL 칼럼도 조회된 정보로 취급 |
상황 1) 폼 데이터 검증: 사용자 입력값이 정말 있는지 확인
<?php
// 사용자가 POST로 보낸 데이터
$_POST = [
'username' => 'john_doe',
'bio' => '', // 빈 문자열
'newsletter' => NULL // 체크 안 함
];
// ✗ 잘못된 방법: in_array로 키 존재 확인?
if (in_array('username', $_POST)) {
// 이 코드는 $_POST 배열의 "값"에 'username'이 있는지 확인 (완전히 다른 의미)
echo "username이 값으로 존재함"; // 이 코드는 실행 안 됨
}
// ✓ 올바른 방법: isset()으로 키와 값 모두 확인
if (isset($_POST['username']) && !empty($_POST['username'])) {
echo "username에 실제 값이 있음";
// 실행됨
}
// ✓ 또는 array_key_exists()로 키만 확인하고 따로 값 검증
if (array_key_exists('bio', $_POST)) {
echo "bio 필드가 전송됨"; // 빈 문자열이어도 실행됨
if (!empty($_POST['bio'])) {
echo "bio에 내용이 있음"; // 이건 실행 안 됨
}
}
?>
✓ 폼 데이터 검증할 땐 isset()을 쓰되, 필드의 존재 여부만 확인하고 싶으면 array_key_exists()를 따로 쓴다.
상황 2) 데이터베이스에서 조회한 결과 처리
<?php
// DB에서 조회한 사용자 정보
$user = [
'id' => 1,
'name' => 'John',
'middle_name' => NULL, // DB에 NULL로 저장됨
'phone' => '' // DB에 빈 문자열로 저장됨
];
// ✗ 잘못된 방법: isset()으로 middle_name 확인
if (isset($user['middle_name'])) {
echo "중간 이름: " . $user['middle_name']; // 실행 안 됨 (NULL이므로)
} else {
echo "중간 이름이 없음"; // 실행됨 (하지만 DB에는 칼럼이 있음)
}
// ✓ 올바른 방법: array_key_exists()로 칼럼 존재 여부 확인
if (array_key_exists('middle_name', $user)) {
echo "DB에 middle_name 칼럼이 있음"; // 실행됨
if ($user['middle_name'] !== NULL) {
echo "중간 이름: " . $user['middle_name'];
} else {
echo "중간 이름이 없음 (NULL)";
}
}
?>
✓ DB 결과는 array_key_exists()로 칼럼의 존재를 확인하고, 값이 NULL인지는 별도로 체크한다.
상황 3) 배열에서 특정 값 찾기
<?php
$allowed_roles = ['admin', 'moderator', 'user', '0'];
$user_role = 'admin';
$suspicious_role = 0; // 정수 0
// ✗ 잘못된 방법: 느슨한 비교
if (in_array($suspicious_role, $allowed_roles)) {
echo "허용된 역할"; // 실행됨! 왜냐하면 0 == '0' 이기 때문
}
// ✓ 올바른 방법: 엄격한 비교(세 번째 인자 true)
if (in_array($user_role, $allowed_roles, true)) {
echo "허용된 역할 (admin 맞음)"; // 실행됨
}
if (in_array($suspicious_role, $allowed_roles, true)) {
echo "허용된 역할"; // 실행 안 됨 (0과 '0'은 타입이 다름)
}
?>
✓ in_array()를 쓸 땐 항상 세 번째 인자로 true를 넘겨서 엄격한 비교(===)를 하자.
실수 1) isset()과 array_key_exists() 혼동
<?php
$config = ['debug' => false, 'timeout' => NULL];
// ✗ false 값을 "없는 것"으로 착각
if (isset($config['debug'])) {
echo "디버그 설정이 있음"; // false도 값이므로 실행됨
}
// ✗ NULL을 "있는 것"으로 착각
if (isset($config['timeout'])) {
echo "타임아웃 설정이 있음"; // 실행 안 됨 (NULL이므로)
}
// ✓ 정확한 비교
var_dump(isset($config['debug'])); // true (false 값도 "있다")
var_dump(isset($config['timeout'])); // false (NULL은 "없다")
?>
✓ isset()은 false, 0, '', '0' 같은 "falsy" 값도 true로 반환한다. NULL만 false다.
실수 2) 느슨한 in_array() 비교의 위험성
<?php
// 보안 검증에서 느슨한 비교 사용
$user_id = '1'; // 문자열
$allowed_ids = [1, 2, 3]; // 정수
// ✗ 위험: 느슨한 비교 (in_array default)
if (in_array($user_id, $allowed_ids)) {
echo "허용된 사용자"; // '1' == 1이므로 실행됨 (우연히 맞음)
}
// 더 위험한 경우
$magic_value = 0; // 특별한 의미의 값
if (in_array($magic_value, ['apple', 'banana'])) {
echo "과일임"; // 실행됨! 0 == 'apple'이 true로 평가
}
// ✓ 안전: 엄격한 비교
if (in_array($user_id, $allowed_ids, true)) {
echo "허용된 사용자"; // '1'과 1은 타입이 다르므로 false
}
?>
✓ 보안 관련 코드에선 반드시 in_array(, , true)를 쓴다. 타입까지 맞아야 true다.
배열 요소 확인은 PHP에서 가장 기본적이면서도 가장 자주 실수하는 작업이다. 세 함수를 정확히 구분하는 것만으로도 버그의 상당 부분을 예방할 수 있다.
빠른 참고표:
- isset() → 키가 있고 NULL이 아닌가? (사용자 입력 검증)
- array_key_exists() → 키가 있는가? (DB 결과 검증, NULL 포함)
- in_array(, , true) → 배열에 이 값이 있는가? (역할/권한 검증 등)
이 글의 상황별 가이드를 참고해 상황에 맞는 함수를 선택하면, 데이터 검증에서 발생하는 대부분의 논리 버그를 막을 수 있을 것이다. 특히 in_array()를 쓸 땐 무조건 세 번째 인자로 true를 넘기는 습관만 들여도, 타입 강제 형변환으로 인한 보안 문제를 상당히 줄일 수 있다.