배열에서 특정 키가 존재하는지 확인할 때 많은 개발자들이 isset()과 array_key_exists()를 같은 것으로 생각한다. 다만 둘은 null 값을 처리하는 방식이 완전히 다르고, 이 차이를 모르면 버그를 찾기 어렵다. 이번에는 두 함수가 정확히 뭐가 다른지, 언제 뭘 써야 하는지, 실전 예제로 완벽하게 정리해서 소개하겠다.
PHP에서 배열 키 존재 여부를 확인할 때는 두 가지 방법이 있다.
isset(): 키가 존재하면서 동시에 그 값이 null이 아닐 때만 true를 반환한다. 즉, 키는 있지만 값이 null이면 false를 반환한다.
array_key_exists(): 키가 존재하기만 하면 true를 반환한다. 값이 null이든 뭐든 상관없다.
언뜻 비슷해 보이지만, null 값이 섞여 있는 배열을 다룰 때 이 차이가 나쁜 버그로 터진다.
isset()은 단순히 키 존재 여부만 보는 게 아니라 "값이 실제로 설정되어 있는가"까지 판단한다.
$user = array(
'name' => 'John',
'email' => null,
'phone' => '',
'age' => 0
);
// ✗ 잘못 이해하기: isset()이 모든 키를 감지한다고 생각
var_dump(isset($user['email'])); // false (null이므로)
var_dump(isset($user['phone'])); // true (빈 문자열도 "설정됨")
var_dump(isset($user['age'])); // true (0도 "설정됨")
var_dump(isset($user['address'])); // false (키가 없으므로)
결과: isset()은 null, undefined 키, 빈 배열 모두에서 false를 반환한다. 만약 API 응답에서 null 값이 섞여 있다면 isset()으로는 그 차이를 구분할 수 없다.
이 함수는 오로지 "이 키가 배열 안에 존재하는가"만 본다. 값은 신경 쓰지 않는다.
$user = array(
'name' => 'John',
'email' => null,
'phone' => '',
'age' => 0
);
// ✓ 정확한 이해: array_key_exists()는 키 존재 여부만 확인
var_dump(array_key_exists('name', $user)); // true
var_dump(array_key_exists('email', $user)); // true (null이지만 키는 존재)
var_dump(array_key_exists('phone', $user)); // true
var_dump(array_key_exists('age', $user)); // true
var_dump(array_key_exists('address', $user)); // false (키가 없음)
결과: array_key_exists()는 null이든 0이든 빈 문자열이든 모두 true를 반환한다. 키 존재만이 판단 기준이다.
외부 API에서 받은 JSON을 파싱할 때 null 값이 포함될 수 있다. 예를 들어 사용자 정보 API에서 선택적 필드는 null로 올 수 있다.
// API에서 받은 JSON
$json = '{"id":1,"name":"Alice","nickname":null,"bio":null}';
$user = json_decode($json, true);
// ✗ 잘못된 코드: isset() 사용
if (isset($user['nickname'])) {
echo "nickname: " . $user['nickname'];
} else {
echo "nickname 필드가 없습니다.";
}
// 출력: "nickname 필드가 없습니다." (실제로는 필드가 있음. null일 뿐)
// ✓ 올바른 코드: array_key_exists() 사용
if (array_key_exists('nickname', $user)) {
if ($user['nickname'] === null) {
echo "nickname: (설정되지 않음)";
} else {
echo "nickname: " . $user['nickname'];
}
} else {
echo "nickname 필드가 없습니다.";
}
// 출력: "nickname: (설정되지 않음)" (정확한 상태 반영)
차이: API 응답에서 "이 필드가 응답에 포함되었는가"를 알아야 한다면 array_key_exists()를 써야 한다. isset()으로는 "응답에 있지만 null"과 "응답에 없음"을 구분할 수 없다.
데이터베이스에서 조회한 행을 배열로 만들 때도 마찬가지다. DB 컬럼이 NULL이면 PHP 배열에도 null이 들어온다.
// DB에서 조회한 사용자 데이터 (phone 컬럼이 NULL)
$row = array(
'id' => 1,
'name' => 'Bob',
'email' => 'bob@example.com',
'phone' => null // DB에서 NULL
);
// ✗ 잘못된 코드
if (!isset($row['phone'])) {
$row['phone'] = '미등록';
}
echo $row['phone']; // "미등록" (실제 DB 데이터는 NULL)
// ✓ 올바른 코드
if (!array_key_exists('phone', $row) || $row['phone'] === null) {
$phone = '미등록';
} else {
$phone = $row['phone'];
}
echo $phone; // "미등록" (정확하게 처리됨)
차이: isset()은 null을 감지하지만, "이 컬럼이 SELECT 결과에 포함되었는가"를 알아야 한다면 array_key_exists()가 필요하다.
// 함수가 받는 옵션 배열
function processData($options = array()) {
// ✗ 잘못된 처리: isset() 사용
if (isset($options['timeout'])) {
$timeout = $options['timeout'];
} else {
$timeout = 30; // 기본값
}
// 만약 호출자가 명시적으로 timeout => null을 넘겼다면?
// isset()은 false를 반환해서 기본값으로 덮어써진다.
// ✓ 올바른 처리: array_key_exists() + 타입 체크
if (array_key_exists('timeout', $options)) {
$timeout = $options['timeout'] ?? 30; // null이면 기본값
} else {
$timeout = 30; // 옵션 미제공
}
}
// 호출 예1: 옵션 미제공
processData([]); // $timeout = 30
// 호출 예2: 명시적으로 null 지정
processData(['timeout' => null]); // $timeout = 30 (array_key_exists로 감지)
// 호출 예3: 값 지정
processData(['timeout' => 60]); // $timeout = 60
| 상황 | isset() | array_key_exists() | 추천 |
|---|---|---|---|
| 키 존재 + 값이 null | false | true | array_key_exists() |
| 키 존재 + 값이 0 또는 '' | true | true | 둘 다 OK |
| 키 없음 | false | false | 둘 다 OK |
| API/DB null 구분 필요 | 불가능 | 가능 | array_key_exists() |
| 성능 (대규모 배열) | 약간 빠름 | 약간 느림 | 대부분의 경우 무시해도 됨 |
// PHP 7+에서는 ?? 연산자로 더 간단하게
$email = $user['email'] ?? 'no-email@example.com';
// 다만 키가 없으면 Notice 경고가 뜨므로,
// 키 존재 확인이 필요할 때만
if (array_key_exists('email', $user)) {
$email = $user['email'] ?? 'unknown';
}
// development 환경
error_reporting(E_ALL);
ini_set('display_errors', 1);
// 이렇게 하면 isset() 대신 array_key_exists()를 쓸 때
// "Undefined index" Notice가 나지 않는다.
// production 환경
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_errors', 0);
// 패턴 1: null과 미존재를 구분해야 할 때
if (array_key_exists('field', $data)) {
$value = $data['field']; // null일 수도, 값이 있을 수도
if ($value === null) {
// 명시적으로 null이 설정됨
} else {
// 실제 값이 있음
}
} else {
// 필드가 없음
}
// 패턴 2: 단순히 존재 여부만 확인할 때
if (array_key_exists('flag', $options)) {
// 옵션이 제공됨 (값이 뭐든 상관없음)
}
// 패턴 3: 값이 "설정되고 유효한" 경우만 처리
if (!empty($user['bio'])) {
// 값이 있고 falsy가 아님 (null, 0, '', false 제외)
}
✗ 실수 1: 항상 isset() 사용하기
많은 개발자가 배열 접근할 때 습관적으로 isset($arr['key'])만 사용한다. 하지만 API 응답이나 DB 결과를 다룰 때는 null 값이 중요한 정보다.
✓ 올바른 접근
API나 DB 데이터는 array_key_exists(), 사용자 입력 검증은 isset() + 값 체크를 섞어서 사용한다.
✗ 실수 2: 성능 때문에 isset() 강요하기
isset()이 약간 빠르다는 이유로 정확성을 포기할 이유는 없다. 현대 서버에서 이 차이는 무시할 수준이다.
✓ 올바른 접근
코드 정확성이 우선이다. 정말 성능이 문제라면 나중에 프로파일링으로 확인하고 최적화한다.
배열 키 존재 확인은 상황에 따라 다르게 접근해야 한다. array_key_exists()는 "이 키가 배열에 존재하는가"를 순수하게 판단하고, isset()은 "이 값이 설정되고 유효한가"를 판단한다는 점을 잊지 말자. 특히 API 응답이나 데이터베이스 결과처럼 null이 의미 있는 데이터를 다룰 때는 array_key_exists()가 필수다. 이 글의 상황별 예제를 참고해 키 존재 확인 로직을 다시 점검하면, 버그 없고 정확한 데이터 처리를 할 수 있을 것이다.