PHP로 개발하다 보면 갑자기 콘솔에 떠오르는 경고 메시지가 있다. 바로 'Undefined property' 경고다. 대부분의 개발자들은 이 경고를 무시하거나 에러 로그를 끄는 식으로 대처한다. 다만 이게 왜 떴는지, 정확히 뭐가 잘못된 건지를 모른 채 넘어가는 경우가 많다. 이번에는 Undefined property 오류가 정확히 뭔지, 왜 발생하는지, 어떻게 완벽하게 해결하는지 실무 기반으로 정리해서 소개하겠다.
PHP에서 객체의 속성(property)에 접근할 때 그 속성이 정의되지 않았으면 경고를 던진다. 예를 들어 $user 객체가 있는데 $user->name이라는 속성이 없으면 'Undefined property' 경고가 나온다. 웹 개발에서는 데이터베이스 쿼리 결과를 객체로 받거나 API 응답을 stdClass로 변환할 때 이 문제가 가장 자주 터진다.
경고 수준이므로 스크립트가 멈추지는 않는다. 다만 개발 환경에서는 로그에 쌓이고 프로덕션 환경에서는 사용자 입장에서 느려진다. 게다가 이 경고가 누적되면 로그 파일의 용량이 계속 늘어나는 문제까지 생긴다.
✗ 잘못된 코드
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->query('SELECT id, name FROM users WHERE id = 1');
$user = $stmt->fetch(PDO::FETCH_OBJ);
echo $user->id; // 정상
echo $user->email; // Warning: Undefined property
echo $user->phone; // Warning: Undefined property
?>
원인: SELECT 문에서 email, phone 컬럼을 조회하지 않았는데 접근하려고 한다. 데이터베이스에는 존재하는 컬럼이지만 현재 쿼리 결과에는 포함되지 않아서다.
✓ 올바른 코드
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// 실제 필요한 모든 컬럼을 SELECT에 포함
$stmt = $pdo->query('SELECT id, name, email, phone FROM users WHERE id = 1');
$user = $stmt->fetch(PDO::FETCH_OBJ);
echo $user->id; // ✓
echo $user->email; // ✓
echo $user->phone; // ✓
?>
또는 속성 존재 여부를 미리 확인:
<?php
// isset()으로 먼저 체크
if (isset($user->email)) {
echo $user->email;
}
// property_exists()로 체크 (더 명확함)
if (property_exists($user, 'email')) {
echo $user->email;
}
?>
✗ 잘못된 코드
<?php
$response = file_get_contents('https://api.example.com/user/1');
$data = json_decode($response);
echo $data->user->name; // 정상
echo $data->user->profile_url; // Warning: Undefined property (필드가 없을 수 있음)
echo $data->meta->status; // Warning: Undefined property (meta 객체가 없을 수 있음)
?>
원인: API 응답의 필드가 항상 반환되는 게 아니다. 특정 조건에서만 있는 필드를 무조건 접근하면 경고가 난다.
✓ 올바른 코드
<?php
$response = file_get_contents('https://api.example.com/user/1');
$data = json_decode($response);
// 1) isset() + is_object() 조합
if (isset($data->user) && is_object($data->user) && isset($data->user->profile_url)) {
echo $data->user->profile_url;
}
// 2) property_exists() 사용 (더 명확)
if (property_exists($data, 'meta') && property_exists($data->meta, 'status')) {
echo $data->meta->status;
}
// 3) Null Coalescing Operator (PHP 7.0+)
echo $data->user->profile_url ?? 'no_profile';
echo $data->meta->status ?? 'unknown';
?>
✗ 잘못된 코드
<?php
class User {
public $id;
public $name;
public function __construct($id, $name) {
$this->id = $id;
$this->name = $name;
}
}
$user = new User(1, 'John');
echo $user->id; // ✓ 정상
echo $user->email; // Warning: Undefined property
echo $user->created_at; // Warning: Undefined property
?>
원인: 클래스 정의에 없는 속성을 접근하려 했다.
✓ 올바른 코드 (1) 속성 선언
<?php
class User {
public $id;
public $name;
public $email; // 추가
public $created_at; // 추가
public function __construct($id, $name, $email = null, $created_at = null) {
$this->id = $id;
$this->name = $name;
$this->email = $email;
$this->created_at = $created_at;
}
}
$user = new User(1, 'John', 'john@example.com', date('Y-m-d H:i:s'));
echo $user->email; // ✓
echo $user->created_at; // ✓
?>
✓ 올바른 코드 (2) Magic Method 활용
<?php
class User {
private $data = [];
public function __get($name) {
return $this->data[$name] ?? null; // 없으면 null 반환
}
public function __set($name, $value) {
$this->data[$name] = $value;
}
}
$user = new User();
$user->id = 1;
$user->name = 'John';
$user->email = 'john@example.com';
echo $user->email; // ✓
echo $user->phone ?? '없음'; // 경고 안 남, null 반환됨
?>
| 상황 | 해결 방법 | 예제 |
|---|---|---|
| 단순 속성 체크 | isset() 또는 property_exists() | isset($obj->prop) ? $obj->prop : '' |
| null 값 허용 | Null Coalescing (??) | $obj->prop ?? 'default' |
| 중첩 객체 접근 | property_exists() 체이닝 | property_exists($obj, 'user') && property_exists($obj->user, 'name') |
| API/JSON 응답 | null Coalescing + json_decode 연계 | json_decode($json, false)->field ?? 'default' |
| 동적 속성 많음 | Magic Method (__get/__set) | $obj->prop = 'value'; echo $obj->unknown ?? null; |
isset()은 속성이 NULL이면 false를 반환하지만 property_exists()는 속성 존재 여부만 확인한다.
<?php
class Test {
public $prop = null;
}
$obj = new Test();
var_dump(isset($obj->prop)); // false (NULL이므로)
var_dump(property_exists($obj, 'prop')); // true (속성 존재)
// 속성이 NULL일 수 있으면 property_exists() 사용
?>
json_decode()의 두 번째 인자가 true면 연관배열로 디코딩되어 속성 접근 문법이 달라진다.
<?php
$json = '{"name": "John", "email": "john@example.com"}';
// 객체로 디코딩
$obj = json_decode($json); // 기본값
echo $obj->name; // ✓
// 배열로 디코딩
$arr = json_decode($json, true);
echo $arr['name']; // ✓
echo $arr->name; // 작동하지 않음
?>
지금까지는 각각의 접근 지점을 안전하게 만드는 방법을 봤다. 여기서는 개발 단계에서 모든 경고를 보되 프로덕션에서는 로그만 남기는 설정을 소개한다.
<?php
// php.ini 또는 runtime 설정
if (getenv('ENVIRONMENT') === 'development') {
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php-errors.log');
}
?>
이렇게 하면 개발 중에는 경고를 눈으로 확인할 수 있고 프로덕션에서는 로그에만 기록되어 사용자에게 영향을 주지 않는다.
Undefined property 경고는 작은 실수처럼 보이지만 방치하면 로그가 무거워지고 디버깅이 어려워진다. isset()이나 property_exists() 체크, 혹은 Null Coalescing 연산자를 습관처럼 쓰는 것만으로도 대부분의 문제를 방지할 수 있다. 특히 API 응답이나 데이터베이스 쿼리를 다룰 때는 항상 필드/속성이 존재하는지 먼저 확인하는 방어 기제를 갖춰두자. 이 글의 해결 방법 섹션을 참고해 현재 프로젝트의 위험한 객체 접근 지점을 정리하면, 훨씬 안정적인 PHP 애플리케이션을 만들 수 있을 것이다.