PHP 8.1 이상에서 strtotime() 함수를 쓸 때 갑자기 'Uncaught ValueError' 에러가 터진 경험이 있을까? 아마 PHP 7.x에서는 멀쩡하던 코드인데도 업그레이드 후 서비스가 먹통이 되는 상황을 겪었을 수도 있다. 문제는 PHP 8.0부터 null 값 처리가 엄격해졌다는 것. 이번에는 strtotime()이 null을 받을 때 왜 터지는지, 그리고 실무에서 바로 쓸 수 있는 정확한 해결책을 정리해서 소개하겠다.

 

PHP 8.0 이상에서 strtotime() null 처리 강화된 이유

PHP 8.0 전환점에서 내부 타입 체크가 대폭 강화됐다. strtotime()은 첫 번째 인자인 $time 파라미터에 null이 들어오면 이제 ValueError를 던진다. PHP 7.x에서는 null이 빈 문자열로 암묵적 변환되거나 무시됐지만, PHP 8.0부터는 "명시적 타입 선언"을 기준으로 엄격하게 검사하기 시작한 것.

실제로 strtotime()의 함수 시그니처를 보면 PHP 8.1부터 첫 번째 파라미터가 string 타입으로 명시됐다. 즉, null은 더 이상 받을 수 없는 상태가 된 것. 이는 개발자의 실수를 조기에 잡으려는 PHP의 설계 방향인데, 기존 레거시 코드와 맞닿으면서 많은 서비스에서 충돌을 일으킨다.

 

실패 사례와 올바른 수정법

 

✗ 잘못된 코드 - null 체크 없음
<?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

 

✓ 올바른 코드 - null 체크 추가
<?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)
?>

 

✓ 실무 방식 - null 안전 래퍼 함수 제작

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 ? "성공" : "실패"; // "실패" 출력
?>

 

DB 조회 시 null 값이 자주 나오는 이유

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 데이터 처리 필요 새 프로젝트나 새 컬럼 추가 시

 

PHP 7.x에서 8.x로 마이그레이션할 때 한 번에 처리하기

대규모 프로젝트에서 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() 대신 DateTime 클래스 사용 검토

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 값을 설정해서 원천차단하는 방법도 고려하자. 작은 방어 코드가 모여서 런타임 에러를 완전히 차단하는 시스템을 만든다는 점을 기억하면서, 이 글의 래퍼 함수를 참고해 팀의 공통 헬퍼 클래스에 추가하면 같은 실수를 두 번 하지 않을 수 있을 것이다.