リモート開発メインのソフトウェア開発企業のエンジニアブログです

is_null() で障害が発生した話 ── 先輩の教えには前提条件があった

はじめに

empty() は使わず、is_null() を使ったほうがいい」

Laravel, PHPを使用して開発中、先輩エンジニアにそう教わったことがあります。

コードレビューでも指摘を受けていたため、既存コードの empty() is_null() に置き換えていました。ところが、その置き換えが原因でSTG環境に500エラーが発生してしまいました。 「場合によっては empty() を使ったほうがいいのでは?」と一瞬疑いましたが、調査を進めるうちに、先輩の教えは正しく、自分がその前提条件を理解できていなかったのだと気づきました。

この記事では、その障害の原因と調査を通じて学んだ empty()is_null() の正しい使い分けを共有します。

発生した障害

既存コードの empty()is_null() に置き換えた後、STG環境のログに以下のようなエラーが記録されました。

[2026-04-13 13:23:50] staging.ERROR: Undefined property: class@anonymous::$deactivation_expiry_date

Bladeテンプレートで $deactivation_expiry_date というプロパティを参照しようとしたところ、そのプロパティが存在しないオブジェクトにアクセスしてしまい、500エラーになりました。

原因の調査

class@anonymous はPHPの匿名クラスを指します。 コードを追うと、認証処理を担うメソッドが、ログインユーザー向けに匿名クラスを返していることがわかりました。しかし、その匿名クラスには deactivation_expiry_date プロパティが定義されていませんでした。

ここで疑問が生まれます。

empty() を is_null() に置き換えただけで、なぜ不具合が発生してしまったのか。その答えが、empty()is_null() の根本的な違いです。

empty()is_null() の違い

empty() echoisset() と同じ言語構造(language construct) です。PHPマニュアルにも次の注記があります。

注意: これは、関数ではなく 言語構造のため、可変関数 や 名前付き引数 を用いてコールすることはできません。

https://www.php.net/manual/ja/function.empty.php

一方、is_null() は通常の関数です。

https://www.php.net/manual/ja/function.is-null.php

言語構造はPHPインタープリタが特別扱いするため、引数を通常の関数呼び出しとは異なる方法で評価します。この違いが、未定義プロパティに対する挙動の差として現れます。

// empty() → 言語構造のため、未定義プロパティでも Warning を発生させない
empty($obj->deactivation_expiry_date);   // Warning なし、true を返す

// is_null() → 通常の関数のため、未定義プロパティへのアクセス時に Warning が発生する
is_null($obj->deactivation_expiry_date); // PHP Warning: Undefined property

正確には「エラー」ではなく「Warning」

厳密にいうと、未定義プロパティへのアクセスはPHPの仕様上 Warning(`E_WARNING`) ですPHP RFC: Undefined property error promotion も参照)。通常の実行環境であればWarningが出るだけで処理は継続されます。

https://wiki.php.net/rfc/undefined_property_error_promotion

それが今回500エラーになったのは、Laravelの HandleExceptionsソースコード)が set_error_handler() を使ってPHPの全エラーを ErrorException に変換するためです。LaravelはBootstrap時に error_reporting(-1)(全エラー報告)を設定しているため、E_WARNING もキャッチされ、例外として処理を中断します。

評価する値の違い

種別の違い以外にも、何を「空」「null」とみなすかという評価基準が大きく異なります。

empty()is_null()isset()
nulltruetruefalse
"" (空文字)truefalsetrue
"0" (文字列のゼロ)truefalsetrue
0 (整数のゼロ)truefalsetrue
falsetruefalsetrue
[] (空配列)truefalsetrue
未定義の変数/プロパティtrue(Warning なし)true(Warningあり)false(Warningなし)

empty() はPHPが「falsy(偽)」とみなす値をすべて true と評価します。is_null()null のみを厳密に判定します。

Moba Pro

修正内容

今回の本質的な修正は、empty() に戻すことではなく、プロパティをきちんと定義することでした。

return new class($user, $session['_csrf_token']) {
    public $id = null;
    public $email;
    public $name;
    // ... 省略 ...
    public $deactivation_expiry_date = null; // ← 追加

匿名クラスに deactivation_expiry_date をデフォルト null で定義するよう修正を行いました。

プロパティが正しく定義されていれば、is_null() は安全に動作します。問題はチェック方法ではなく、設計にありました。

考察:前提条件と現実的な選択

is_null() を使え」という教えの真意

改めて「empty() は使わず、is_null() を使ったほうがいい」というアドバイスを振り返ると、その真意は型の厳密さと意図の明確さにあります。

たとえば、次のようなケースを考えてみてください。

$count = 0;

if (empty($count)) {
    // 0 は empty() では true と評価されるため、ここに入ってしまう
    echo "件数が未設定です";
}

if (is_null($count)) {
    // 0 は null ではないため、ここには入らない(正しい挙動)
    echo "件数が未設定です";
}

0"""0" が有効な値として存在しうる場面で empty() を使うと、意図せず「空」と判定されてしまいバグの温床になります。is_null() を使えば、null かどうかだけを厳密に確認でき、コードの意図が明確になります。

先輩のアドバイスは正しかったのです。 自分はその言葉の意味だけを受け取り、なぜそうすべきかという背景を理解しないまま機械的に置き換えてしまっていました。

is_null() を使う前提条件

is_null() を安全に使うには、チェック対象の変数やプロパティが必ず定義されているという前提が必要です。今回はその前提が崩れていたために問題が発生しました。

empty() はその前提が崩れていてもエラーを表面化させません。見方を変えれば、「empty() を使っていたことで設計上の問題が隠れていた」ともいえます。

レガシーコードでは empty() のままにしておくのが現実的な場合もある

今回はプロパティの定義を追加するという修正が比較的容易でしたが、現実の開発ではそうとも限りません。

たとえば、旧システムからの移行プロジェクトで旧コードをそのまま持ち込んだ場合などは、変数やプロパティの定義が不完全な箇所が大量に存在することがあります。そうした状況で全箇所のプロパティ定義を整備してから empty()is_null()isset() に置き換えようとすると、膨大な工数がかかり現実的ではありません。

そのような場合には、empty() のままにしておくことも立派な判断のひとつです。empty() を使わない」はあくまでベストプラクティスであり、プロジェクトの状況や優先度に応じた現実的なトレードオフが必要です。大切なのは、empty() がエラーを隠す性質を持つことを意識した上で使うかどうかを判断することです。

正しい使い分けのまとめ

この経験を通じて、以下の使い分けを整理しました。

is_null() を使う場面

変数やプロパティが確実に定義されていることを前提に、null かどうかだけを厳密に確認したいとき。0false"" を有効な値として区別したい場合はこちらを選びます。

// Eloquentモデルのカラムは必ず定義されているため、is_null()が安全
if (is_null($user->deactivation_expiry_date)) {
    // nullの場合の処理
}

empty() を使う場面

$_GET$_POST・配列のキーなど、存在自体が不確かな値をゆるくチェックしたいとき。0 や空文字も「空」として扱いたい場合にも有効です。

// リクエストパラメータの存在と空チェックを同時に行う
if (!empty($_POST['name'])) {
    // nameが存在し、かつ空でない場合の処理
}

おわりに

empty() を使わず、is_null() を使え」というアドバイスに間違いはありませんでした。ただ、自分はその言葉の意味だけを受け取り、なぜそうすべきかという背景を理解しないまま機械的に置き換えてしまっていました。

is_null() を使う前提は、チェック対象が必ず定義されていること。empty() がエラーを隠している場合、その裏には設計上の問題が潜んでいる可能性があること。今回の障害はそれを教えてくれた出来事でした。

教わったことを「とりあえず従う」のではなく、「なぜそうなのか」を理解した上で使えるようになることが、エンジニアとしての成長につながると改めて感じました。今後は is_null() に置き換える際には、対象が確実に定義されているかを必ず確認するようにします。

← 前の投稿

Claude Code の Discord プラグインを使ってみた

次の投稿 →

コメントを残す