PHP 8.1 이상에서 strtotime() 함수를 쓸 때 갑자기 'Uncaught ValueError' 에러가 터진 경험이 있을까? 아마 PHP 7.x에서는 멀쩡하던 코드인데도 업그레이드 후 서비스가 먹통이 되는 상황을 겪었을 수도 있다. 문제는 PHP 8.0부터 null 값 처리가 엄격해졌다는 것. 이번에는 strtotime()이 null을 받을 때 왜 터지는지, 그리고 실무에서 바로 쓸 수 있는 정확한 해결책을 정리해서 소개하겠다.
PHP 8.0 전환점에서 내부 타입 체크가 대폭 강화됐다. strtotime()은 첫 번째 인자인 $time 파라미터에 null이 들어오면 이제 ValueError를 던진다. PHP 7.x에서는 null이 빈 문자열로 암묵적 변환되거나 무시됐지만, PHP 8.0부터는 "명시적 타입 선언"을 기준으로 엄격하게 검사하기 시작한 것.
실제로 strtotime()의 함수 시그니처를 보면 PHP 8.1부터 첫 번째 파라미터가 string 타입으로 명시됐다. 즉, null은 더 이상 받을 수 없는 상태가 된 것. 이는 개발자의 실수를 조기에 잡으려는 PHP의 설계 방향인데, 기존 레거시 코드와 맞닿으면서 많은 서비스에서 충돌을 일으킨다.
<?php
$user_birthday = null; // DB에서 가져온 값이 null일 수 있음
$timestamp = strtotime($user_birthday); // PHP 8.1에서 ValueError 발생
$age = date('Y') - date('Y', $timestamp);
echo $age;
?>
위 코드를 PHP 8.1에서 실행하면 다음과 같은 에러가 난다.
Fatal error: Uncaught ValueError: strtotime(): Argument #1 ($time) cannot contain null
Stack trace:
#0 {main}
thrown in /var/www/example.php on line 3
<?php
$user_birthday = null; // DB에서 가져온 값
// 방법 1. if로 명시적 체크
if ($user_birthday !== null) {
$timestamp = strtotime($user_birthday);
$age = date('Y') - date('Y', $timestamp);
echo $age;
} else {
echo "생년월일 정보 없음";
}
?>
또는 더 간단하게 삼항 연산자로 기본값을 설정할 수 있다.
<?php
$user_birthday = null;
// 방법 2. 삼항 연산자로 기본값 설정
$timestamp = strtotime($user_birthday ?? '1970-01-01');
$age = date('Y') - date('Y', $timestamp);
echo $age; // 54 (현재 연도 - 1970)
?>
DB에서 날짜 데이터를 자주 다룬다면 재사용할 수 있는 헬퍼 함수를 만드는 게 좋다.
<?php
function safeStrtotime(?string $time, $default = false): int|bool {
if ($time === null || $time === '') {
return $default;
}
try {
return strtotime($time);
} catch (ValueError $e) {
// strtotime이 false를 반환했을 때도 처리
error_log("strtotime 파싱 실패: $time");
return $default;
}
}
// 사용 예
$user_birthday = null;
$timestamp = safeStrtotime($user_birthday, strtotime('1970-01-01'));
$age = date('Y') - date('Y', $timestamp);
echo $age; // 기본값으로 54 출력
// 잘못된 날짜 형식도 처리
$invalid_date = '2024-13-45'; // 잘못된 월/일
$result = safeStrtotime($invalid_date, false);
echo $result ? "성공" : "실패"; // "실패" 출력
?>
MySQL이나 PostgreSQL에서 날짜 컬럼을 조회할 때 NULL이 반환되는 경우가 많다. 특히 회원 가입 시 선택사항으로 두었던 필드(예: 생년월일, 가입 확인 날짜 등)가 그렇다. PHP의 PDO나 MySQLi로 조회하면 NULL은 PHP의 null로 변환되는데, 이걸 strtotime()에 바로 넣으면 PHP 8.0 이상에서 충돌한다.
<?php
// DB에서 조회한 데이터
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass');
$stmt = $pdo->prepare('SELECT birthday FROM users WHERE id = ?');
$stmt->execute([1]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$birthday = $result['birthday']; // NULL 또는 '2000-05-15'
// strtotime() 호출 전에 null 체크 필수
if (!empty($birthday)) { // empty()는 null, '', '0'을 모두 감지
$timestamp = strtotime($birthday);
// 처리...
}
?>
| 방법 | 장점 | 단점 | 추천 상황 |
|---|---|---|---|
| if 문으로 null 체크 | 의도가 명확하고 가독성 좋음 | 코드가 조금 길어짐 | 로직이 복잡하거나 null일 때 처리가 많을 때 |
| null 병합 연산자 (??) | 간결하고 빠름 | 기본값 설정만 가능 | 단순히 null을 대체값으로 바꿀 때 |
| 래퍼 함수 | 재사용 가능하고 중앙화된 처리 | 함수 호출 오버헤드 | 여러 곳에서 strtotime()을 쓰거나 팀 규칙으로 통일하고 싶을 때 |
| DB 스키마에서 DEFAULT 값 설정 | DB 단에서 null 원천 차단 | 기존 NULL 데이터 처리 필요 | 새 프로젝트나 새 컬럼 추가 시 |
대규모 프로젝트에서 strtotime() 호출이 많다면, 한 곳에서 모든 호출을 관리하는 게 좋다. 프로젝트의 공통 헬퍼 클래스에 메서드를 추가하자.
<?php
class DateHelper {
/**
* null 안전한 strtotime 래퍼
* PHP 8.0+ 호환성 보장
*/
public static function toTimestamp(?string $time, int $default = 0): int {
// 빈 값이면 기본값 반환
if (empty($time)) {
return $default;
}
// strtotime 실행 (false 반환도 기본값으로)
$result = strtotime($time);
return $result !== false ? $result : $default;
}
/**
* 날짜 객체로 반환 (DateTime 선호시)
*/
public static function toDateTime(?string $time, ?string $default = null): ?DateTime {
if (empty($time)) {
return $default ? new DateTime($default) : null;
}
try {
return new DateTime($time);
} catch (Exception $e) {
error_log("DateTime 파싱 실패: $time");
return $default ? new DateTime($default) : null;
}
}
}
// 사용
$birthday = null;
$timestamp = DateHelper::toTimestamp($birthday, strtotime('1970-01-01'));
echo "나이: " . (date('Y') - date('Y', $timestamp));
// DateTime 선호시
$dt = DateHelper::toDateTime($birthday, '1970-01-01');
echo "생년월일: " . $dt->format('Y-m-d');
?>
strtotime()의 null 문제로 계속 고생한다면 처음부터 DateTime 클래스를 쓰는 게 낫다. DateTime은 타입 힌팅이 명확하고 예외 처리도 직관적이다.
<?php
$birthday = null;
// strtotime() 방식 (null 체크 필수)
// if ($birthday) {
// $timestamp = strtotime($birthday);
// }
// DateTime 방식 (더 명확함)
try {
if ($birthday) {
$dt = new DateTime($birthday);
$age = (new DateTime())->diff($dt)->y;
echo "나이: $age";
} else {
echo "생년월일 정보 없음";
}
} catch (Exception $e) {
echo "날짜 형식 오류: " . $e->getMessage();
}
?>
PHP 8.1 이상에서는 DateTimeImmutable도 있으니 참고하자. strtotime() 수정보다는 DateTime으로 완전히 옮겨가는 게 장기적으로 유지보수 비용을 줄인다.
strtotime() null 값 문제는 PHP 8.0 이상의 타입 엄격성 강화에서 비롯된 것이다. 단순히 null 체크를 한두 번 추가하는 것으로도 해결되지만, 프로젝트 규모가 크다면 래퍼 함수나 DateTime 클래스로 통일하는 게 낫다. DB에서 null 값이 자주 나온다면 스키마 단에서 DEFAULT 값을 설정해서 원천차단하는 방법도 고려하자. 작은 방어 코드가 모여서 런타임 에러를 완전히 차단하는 시스템을 만든다는 점을 기억하면서, 이 글의 래퍼 함수를 참고해 팀의 공통 헬퍼 클래스에 추가하면 같은 실수를 두 번 하지 않을 수 있을 것이다.