복잡하게 중첩된 API 응답 데이터나 설정 파일 배열을 다룰 때 특정 키가 깊숙이 존재하는지 체크해본 경험이 있을 것이다.
다만 다차원 배열에서 매번 isset()이나 array_key_exists()를 여러 번 중첩해서 쓰다 보면 코드가 지저분해지고 null 값 처리가 꼬이는 경우가 많다.
이번에는 다차원 배열에서도 점 표기법(Dot Notation)으로 키 존재 여부를 안전하고 정확하게 확인하는 array_has() 서포트 함수를 구현하고 실무에 바로 적용하는 방법을 정리해서 소개하겠다.
PHP 기본 함수인 isset()이나 array_key_exists()는 1차원 배열에서는 아주 잘 동작한다. 하지만 3단계, 4단계로 깊어지는 다차원 배열에서는 단번에 하위 키의 존재 유무를 확인하기 어렵다.
예를 들어 $data['user']['profile']['settings']['theme'] 속성이 있는지 확인하려면 매 단계마다 isset()을 호출해야만 Undefined array key 경고 에러를 피할 수 있다.
단순히 isset($data['user']['profile']['settings']['theme']) 형태로 한 번에 접근할 수도 있지만, 이 방식은 intermediate key(중간 단계 키)가 배열이 아니라 null이나 다른 타입일 때 예외를 발생시키거나 원하는 결과를 얻지 못하는 문제가 발생한다.
게다가 특정 키의 값이 null로 설정되어 있는 경우, isset()은 false를 반환하기 때문에 키 자체가 존재하는 것인지 값이 null인 것인지 구별하지 못한다.
다차원 배열의 키 확인 방식을 상황별로 비교해보면 서포트 함수의 필요성이 명확해진다.
| 확인 방식 | 깊은 키 접근 편의성 | null 값 구분 여부 | 중간 키 타입 오류 안전성 |
|---|---|---|---|
| 중첩 isset() | 매우 불편함 (코드 길어짐) | 구분 불가 (null이면 false) | 안전함 |
| 중첩 array_key_exists() | 극도로 불편함 | 구분 가능 | 안전하지 않음 (타입 체크 필요) |
| array_has() 서포트 함수 | 매우 편리함 ('user.profile.theme') | 구분 가능 | 완벽하게 안전함 |
점 표기법을 통해 문자열 하나로 깊은 단계의 키까지 순회하며 검사하는 array_has() 함수를 만들어보자.
문자열을 explode()로 분할한 뒤, 배열 내부를 참조로 탐색하면서 마지막 키까지 안전하게 도달하는지 검증하는 구조다.
중간 단계 검사를 생략하거나 isset()에만 의존하면 값이 null일 때 키가 없다고 잘못 판단하거나 경고 에러가 발생한다.
<?php
$config = [
'database' => [
'credentials' => [
'port' => null,
]
]
];
// 잘못된 예시 1: null 값이 설정되어 있음에도 키가 없다고 판정함
if (isset($config['database']['credentials']['port'])) {
echo "포트 설정 존재";
} else {
echo "포트 설정 없음"; // 실제로는 port 키가 있지만 null이므로 이쪽이 실행됨
}
// 잘못된 예시 2: 중간 키가 배열이 아닌 경우 Warning 에러 발생 위험
$user = ['profile' => 'not_an_array'];
if (array_key_exists('theme', $user['profile']['settings'])) {
// Warning: Undefined array key 혹은 TypeError 발생
}
?>출력 결과:
포트 설정 없음
Warning: Trying to access array offset on value of type string...
배열 인과관계를 엄격히 따져 중간 요소가 ArrayAccess를 구현했거나 배열인 경우에만 탐색을 진행하고, 마지막 키에 대해 array_key_exists()를 수행하도록 작성한다.
<?php
if (!function_exists('array_has')) {
/**
* 다차원 배열에서 점 표기법(Dot Notation)으로 키 존재 여부를 확인합니다.
*
* @param array $array 탐색할 배열
* @param string $key 점으로 구분된 키 경로 (예: 'user.profile.name')
* @return bool
*/
function array_has(array $array, string $key): bool
{
if (empty($key)) {
return false;
}
if (array_key_exists($key, $array)) {
return true;
}
$segments = explode('.', $key);
foreach ($segments as $segment) {
if (is_array($array) && array_key_exists($segment, $array)) {
$array = $array[$segment];
} else {
return false;
}
}
return true;
}
}
// 실전 테스트 데이터
$config = [
'database' => [
'credentials' => [
'port' => null,
]
],
'service' => [
'status' => 'active'
]
];
// 테스트 1: 값이 null인 키 검사
var_dump(array_has($config, 'database.credentials.port'));
// 테스트 2: 존재하지 않는 중간 경로 검사
var_dump(array_has($config, 'database.connection.timeout'));
// 테스트 3: 정상 존재하는 경로 검사
var_dump(array_has($config, 'service.status'));
?>출력 결과:
bool(true)
bool(false)
bool(true)
array_has() 함수를 다룰 때 흔히 범하는 실수를 알아두면 버그를 사전에 방지할 수 있다.
첫 번째로, 배열 키 자체에 마침표(.)가 들어가는 경우다.
만약 데이터의 키 이름 자체에 'app.name'처럼 마침표가 포함되어 있다면, explode('.')로 인해 키가 분할되어 올바르게 탐색하지 못한다.
위 서포트 함수 예제 코드에서는 탐색 시작 전 array_key_exists($key, $array)를 먼저 검사하여 1차원 배열 내 점이 들어간 통째 키를 우선 보호하도록 설계되어 있다.
두 번째로, 단순 값이 null인 것과 키가 아예 정의되지 않은 것을 혼동하는 것이다.
✗ 잘못된 접근: empty($array['a']['b']) 혹은 isset($array['a']['b'])로 조건 판단하기.
✓ 올바른 접근: 값이 null이더라도 옵션으로 전달된 것인지 체크해야 할 때는 반드시 array_has()처럼 키 존재 유무(array_key_exists 기반)를 검사해야 한다.
다차원 배열의 깊은 키 존재 확인은 백엔드 API 데이터 가공이나 환경 설정값을 다룰 때 매우 자주 마주치는 작업이다. 작은 서포트 함수 하나를 프로젝트 공통 헬퍼로 등록해두는 습관이 코드의 가독성을 높이고 의도치 않은 Undefined key 에러를 완벽히 막아준다. 이 글의 array_has() 구현 코드를 참고해서 프로젝트 공통 함수에 추가해보자. 지저분한 중첩 조건문 없이 훨씬 깔끔하고 안전한 배열 제어를 경험할 수 있을 것이다.