PHP 개발 중에 "Warning: Illegal string offset"이라는 경고를 본 적 있을까? 대부분의 개발자들은 이 에러 메시지를 보고도 정확히 뭐가 잘못됐는지 파악 못 한 채 구글링한 코드를 덮어씌운다. 다만 이 경고는 단순한 주의 수준이 아니라 코드의 논리 오류를 가리키는 신호인데, 방치하면 나중에 예측 불가능한 버그를 만든다. 이번에는 이 에러가 정확히 언제, 왜 발생하는지, 그리고 어떻게 완벽하게 해결하는지 실무 기반으로 정리해서 소개하겠다.
이 에러는 문자열 변수를 배열처럼 접근하려고 할 때 나타난다. PHP는 문자열도 인덱싱으로 개별 문자에 접근할 수 있지만, 배열처럼 연관 키(associative key)로 접근하려고 하면 경고를 날린다. 핵심은 "배열인 줄 알고 접근했는데 실제론 문자열이다"는 뜻이다.
더 정확히 말하면, PHP 8.0 이상에서는 이 동작이 더 엄격해졌다. 이전 버전에서는 묵묵히 null을 반환했지만, 현재는 명시적 경고를 날린다. 이는 코드의 약점을 미리 잡자는 PHP 개발팀의 의도다.
✗ 잘못된 코드 - 문자열을 배열처럼 접근
<?php
$data = "hello";
echo $data['name']; // Warning: Illegal string offset "name"
$user = "john,doe";
echo $user['email']; // Warning: Illegal string offset "email"
?>
위 코드에서 $data와 $user는 명백히 문자열인데, 배열의 문법으로 접근하려고 한다. PHP는 이를 감지하고 경고를 날린다. 그리고 이 경우 결과값은 null이다.
실전 예제 - API 응답 파싱 실수
<?php
// 외부 API에서 받은 응답
$response = '{"user":"john","age":30}';
// 실수: json_decode를 빼먹음
echo $response['user']; // Warning: Illegal string offset "user"
// 올바른 방법
$decoded = json_decode($response, true);
echo $decoded['user']; // "john" (정상)
?>
이는 실무에서 가장 흔한 실수다. JSON 문자열을 배열로 변환하지 않고 바로 접근하려고 한다.
✓ 올바른 접근 - 타입 먼저 확인
<?php
$data = "hello";
// 방법 1: is_array() 체크
if (is_array($data)) {
echo $data['name'];
} else {
echo "$data는 배열이 아닙니다.";
}
// 방법 2: is_string() 체크
if (is_string($data)) {
echo "이 변수는 문자열입니다.";
}
// 방법 3: gettype()으로 명확히 파악
echo gettype($data); // "string"
?>
에러를 근본적으로 해결하려면 변수의 타입을 정확히 아는 것이 첫 단계다. 특히 외부 입력이나 API 응답을 다룰 때는 이를 매번 체크하는 습관이 중요하다.
상황 1: JSON/XML 문자열을 배열로 변환
<?php
// ✗ 잘못된 코드
$json_response = '{"user":"john","status":"active"}';
echo $json_response['user']; // Warning: Illegal string offset
// ✓ 올바른 코드
$data = json_decode($json_response, true);
if (is_array($data)) {
echo $data['user']; // "john"
}
?>
상황 2: 데이터베이스 결과 핸들링
<?php
// ✗ 잘못된 코드 - fetch가 문자열을 반환할 수 있음
$result = $mysqli->query("SELECT name FROM users LIMIT 1");
$row = $result->fetch_row();
echo $row['name']; // 잘못된 접근 방식
// ✓ 올바른 코드 - 명시적으로 연관배열 지정
$result = $mysqli->query("SELECT name FROM users LIMIT 1");
$row = $result->fetch_assoc(); // 연관배열 반환
echo $row['name']; // "john"
// 또는 PDO 사용
$stmt = $pdo->query("SELECT name FROM users LIMIT 1");
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo $row['name']; // "john"
?>
상황 3: 폼 데이터나 쿼리 파라미터 검증
<?php
// ✗ 잘못된 코드 - 타입 확인 없이 접근
$name = $_GET['name'] ?? ''; // 혹시 문자열이 아닐 수도
echo $name['first']; // Warning: Illegal string offset
// ✓ 올바른 코드 - 타입과 존재 확인
if (isset($_GET['name']) && is_string($_GET['name'])) {
$name = $_GET['name'];
echo htmlspecialchars($name);
}
// 또는 더 안전하게
$name = isset($_GET['name']) ? $_GET['name'] : '';
if (is_array($name)) {
$name = ''; // 배열이면 버림
}
?>
타입 힌팅으로 미리 차단하기
<?php
function processUser(array $user): string {
// $user가 반드시 배열임을 보장
return $user['name'] ?? 'Unknown';
}
// 올바른 호출
processUser(['name' => 'john']); // OK
// 잘못된 호출 - TypeError 발생
processUser('john'); // TypeError: Argument must be array
?>
함수를 정의할 때 파라미터 타입을 명시하면, 호출 시점에 이미 타입 검증이 일어난다. 이는 프로덕션에서 예측 불가능한 에러를 사전에 방지한다.
안전한 배열 접근 헬퍼 함수
<?php
function safe_array_get($array, $key, $default = null) {
// 배열이 아니면 즉시 기본값 반환
if (!is_array($array)) {
return $default;
}
return $array[$key] ?? $default;
}
// 사용 예
$data = '{"user":"john"}';
echo safe_array_get($data, 'user', 'N/A'); // "N/A"
$data = json_decode($data, true);
echo safe_array_get($data, 'user', 'N/A'); // "john"
?>
| 실수 | 원인 | 해결책 |
|---|---|---|
| API 응답을 decode하지 않고 접근 | json_decode() 누락 | json_decode($response, true) 사용 |
| DB fetch 메서드 잘못 선택 | fetch_row() 사용 후 연관키 접근 | fetch_assoc() 또는 FETCH_ASSOC 사용 |
| 문자열 정규식 매칭 결과 직접 접근 | preg_match() 결과가 배열이 아닐 수 있음 | 반환값 체크: if ($matches) 후 접근 |
| 함수 반환값 타입 미확인 | 함수가 null/문자열/배열 중 뭘 반환하는지 불명 | 함수 문서 확인 + 타입 체크 |
| 설정 배열 병합 후 접근 | array_merge() 결과가 예상과 다름 | var_dump() 로 구조 확인 후 접근 |
PHP 버전에 따라 이 경고의 심각도가 다르다:
- PHP 7.4 이하: E_WARNING (경고, 실행 계속)
- PHP 8.0+: E_WARNING (경고) 또는 설정에 따라 E_ERROR (에러, 실행 중단)
만약 이 경고를 무시하고 싶다면 (권장하지 않음):
<?php
// 임시 억제 - 좋은 습관 아님
$value = @$string_var['key'];
// 더 나은 방법 - 타입 체크 후 접근
if (is_array($string_var) && isset($string_var['key'])) {
$value = $string_var['key'];
}
?>
"Illegal string offset" 에러는 단순한 경고가 아니라 코드의 논리 오류를 가리키는 신호다. 외부 입력, API 응답, 데이터베이스 결과 등을 다룰 때 타입을 먼저 확인하는 습관이 모여서 견고한 코드를 만든다는 점을 잊지 말자. 이 글의 타입 체킹 헬퍼 함수와 안전한 데이터 접근 패턴을 참고해 코드 전반을 리팩토링하면, 프로덕션에서 예측 불가능한 버그를 상당히 줄일 수 있을 것이다.