issetは「定義されていて、かつnullでない」ときにtrue、emptyは「未定義、またはfalseとみなされる値」のときにtrue、is_nullは「値がnull」のときにtrueを返します。 3つは似ていますが、"0" や 0、空の配列、未定義の変数を渡したときの結果がそれぞれ異なります。

この記事では、未定義・null・""・"0"・0・0.0・false・[]・" " などの値を PHP 8.4.21 で実際に実行した結果 を早見表にしました。あわせて、PHP 8 で Warning になった Undefined variable と Undefined array key の再現、array_key_exists との違い、??・??=・?-> への書き換え、フォーム入力の "0" が empty で未入力扱いになる例と直し方を、そのまま貼って動くコードで紹介します。

isset・empty・is_nullの早見表

まずは結論の表です。下の「実行コード」で出力した結果をそのまま整理しています(PHP 8.4.21)。

値 isset($v) empty($v) is_null($v) $v === null $v ?? 'D' if ($v)
未定義の変数 false true true(Warningあり) true(Warningあり) 'D' false(Warningあり)
null false true true true 'D' false
""(空文字) true true false false "" false
"0"(文字列のゼロ) true true false false "0" false
0 true true false false 0 false
0.0 true true false false 0.0 false
false true true false false false false
[](空の配列) true true false false [] false
" "(空白1文字) true false false false " " true
"0.0" true false false false "0.0" true
"a" true false false false "a" true
[0] true false false false [0] true

表から読み取れる関係は次の3つです。

覚え方 内容
isset($v) と $v ?? 'D' は同じ基準 どちらも「未定義またはnull」だけを特別扱いします。"" や 0 はそのまま通します
empty($v) は !isset($v) || !$v と同じ 未定義でもWarningを出さずに、if ($v) の逆を返します
is_null($v) と $v === null は同じ結果 どちらも未定義の変数を渡すとWarningが出ます。Warningを出さないのは isset・empty・?? です

やりたいことから選ぶときは、次の表が目安になります。

やりたいこと 書き方
変数や配列キーがあって、nullでないか調べたい isset($a['key'])
なければ既定値を使いたい $a['key'] ?? '既定値'
なければ代入しておきたい $a['key'] ??= '既定値'
値がnullかどうかだけ調べたい(変数は必ず定義済み) $v === null
配列にキーがあるか調べたい(値がnullでも「ある」としたい) array_key_exists('key', $a)
配列が空かどうか調べたい $list === [] または count($list) === 0
フォームの入力が空かどうか調べたい trim($v ?? '') === ''
nullかもしれないオブジェクトのプロパティを読みたい $user?->profile?->city

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PHP isset・empty・is_nullの違い|null判定の早見表」

早見表を出力したコード(コピペで再現できます)

早見表は次のコードで出力しました。table.php という名前で保存し、php table.php で実行できます。

<?php
// table.php : 値ごとの判定結果を一覧にする(PHP 8.4 で実行)
error_reporting(E_ALL);
set_error_handler(fn() => true); // 未定義変数の Warning を表に混ぜない

function b(bool $x): string { return $x ? 'true ' : 'false'; }

$cases = [
    'null'   => null,
    '""'     => '',
    '"0"'    => '0',
    '0'      => 0,
    '0.0'    => 0.0,
    'false'  => false,
    '[]'     => [],
    '" "'    => ' ',
    '"0.0"'  => '0.0',
    '"a"'    => 'a',
    '[0]'    => [0],
];

printf("%-8s| %-6s| %-6s| %-8s| %-9s| %-10s| %s\n",
    'value', 'isset', 'empty', 'is_null', '=== null', "?? 'D'", 'if($v)');
echo str_repeat('-', 64), "\n";

// 未定義の変数
printf("%-8s| %s | %s | %s   | %s    | %-10s| %s\n", '(undef)',
    b(isset($undef)), b(empty($undef)), b(is_null($undef)), b($undef === null),
    var_export($undef ?? 'D', true), b((bool)$undef));

foreach ($cases as $label => $v) {
    printf("%-8s| %s | %s | %s   | %s    | %-10s| %s\n", $label,
        b(isset($v)), b(empty($v)), b(is_null($v)), b($v === null),
        str_replace("\n", '', var_export($v ?? 'D', true)), b((bool)$v));
}

実行結果(PHP 8.4.21):

value   | isset | empty | is_null | === null | ?? 'D'    | if($v)
----------------------------------------------------------------
(undef) | false | true  | true    | true     | 'D'       | false
null    | false | true  | true    | true     | 'D'       | false
""      | true  | true  | false   | false    | ''        | false
"0"     | true  | true  | false   | false    | '0'       | false
0       | true  | true  | false   | false    | 0         | false
0.0     | true  | true  | false   | false    | 0.0       | false
false   | true  | true  | false   | false    | false     | false
[]      | true  | true  | false   | false    | array ()  | false
" "     | true  | false | false   | false    | ' '       | true 
"0.0"   | true  | false | false   | false    | '0.0'     | true 
"a"     | true  | false | false   | false    | 'a'       | true 
[0]     | true  | false | false   | false    | array (  0 => 0,)| true 

"0" は empty で true、"0.0" と " " は empty で false になる点が、いちばん間違えやすいところです。PHPが「falseとみなす文字列」は空文字 "" と "0" の2つだけ、と覚えておくと整理しやすくなります。

3つの関数の役割を整理する

isset:定義されていて、nullでないか

isset は関数のように見えますが、PHPの言語構造(言語に組み込まれた構文)です。そのため、未定義の変数や存在しない配列キーを渡してもWarningが出ません。

<?php
$user = ['id' => 1, 'nickname' => null];

var_dump(isset($name));                       // 未定義の変数 → false
var_dump(isset($user['id']));                 // true
var_dump(isset($user['nickname']));           // 値が null → false
var_dump(isset($user['email']));              // キーなし → false
var_dump(isset($user['id'], $user['email'])); // 全部そろっていれば true → false

複数の引数を渡すと、すべてが「定義済みでnullでない」ときだけtrue になります。必須項目がそろっているかを1行で確かめたいときに便利です。

empty:空とみなせる値か

empty も言語構造です。未定義でもWarningを出さず、「値がfalseとみなされるかどうか」を返します。trueになるのは、未定義・null・""・"0"・0・0.0・false・[] の8種類 です。

便利な反面、0 や "0" を「入力された値」として扱いたい場面では意図と違う結果になります。数量・点数・フラグのように ゼロが意味を持つ値には empty を使わず、後述の厳密な比較に置き換える と安心です。

is_null:値がnullか

is_null は通常の関数で、$v === null と同じ結果を返します。関数なので、未定義の変数を渡すとWarningが出ます(結果はtrue)。

<?php
error_reporting(E_ALL); ini_set('display_errors', '1');
var_dump(is_null($undef));
var_dump($undef === null);

実行結果(PHP 8.4.21):

Warning: Undefined variable $undef in misc.php on line 4
bool(true)

Warning: Undefined variable $undef in misc.php on line 6
bool(true)

変数が必ず定義されている場面(関数の引数、初期化済みのプロパティなど)では $v === null が読みやすく、定義されているか分からない場面では isset や ?? が向いています。

== null はゆるい比較になる

== null は型をそろえてから比べるため、null以外でもtrueになる値があります。

""     == null : true   === null : false
"0"    == null : false  === null : false
0      == null : true   === null : false
false  == null : true   === null : false
[]     == null : true   === null : false
null   == null : true   === null : true

"0" == null だけがfalseになるなど、覚えにくい結果になります。nullの判定は === で書く と、表を思い出す必要がなくなります。

PHP 8でWarningになったUndefinedエラーを再現する

未定義の変数・配列キー・プロパティを読むとどうなるか

PHP 8.0 から、未定義の変数・配列キー・プロパティの読み取りは Notice から Warning に引き上げられました。次のコードで再現できます。

<?php
// warn.php : 未定義の変数・配列キー・プロパティを読むとどうなるか
error_reporting(E_ALL);
ini_set('display_errors', '1');

echo "--- 1) 未定義の変数\n";
var_dump($name);

echo "--- 2) 未定義の配列キー\n";
$user = ['id' => 1];
var_dump($user['email']);

echo "--- 3) 未定義のプロパティ\n";
$obj = new stdClass();
var_dump($obj->email);

echo "--- 4) null を配列として読む\n";
$none = null;
var_dump($none['email']);

echo "--- 5) null のプロパティを読む\n";
var_dump($none->email);

echo "--- 6) null のメソッドを呼ぶ\n";
try {
    $none->getEmail();
} catch (Error $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}

実行結果(PHP 8.4.21。ファイルのパスは省略しています):

--- 1) 未定義の変数

Warning: Undefined variable $name in warn.php on line 7
NULL
--- 2) 未定義の配列キー

Warning: Undefined array key "email" in warn.php on line 11
NULL
--- 3) 未定義のプロパティ

Warning: Undefined property: stdClass::$email in warn.php on line 15
NULL
--- 4) null を配列として読む

Warning: Trying to access array offset on null in warn.php on line 19
NULL
--- 5) null のプロパティを読む

Warning: Attempt to read property "email" on null in warn.php on line 22
NULL
--- 6) null のメソッドを呼ぶ
Error: Call to a member function getEmail() on null

1〜5はWarningを出したあと NULL として処理が続きます。6の nullに対するメソッド呼び出しだけは Error 例外 になり、捕捉しなければそこで処理が止まります。

バージョン別のエラーレベル

操作 PHP 7.4 まで PHP 8.0 以降 8.4.21 でのメッセージ
未定義の変数を読む Notice Warning Undefined variable $name
未定義の配列キーを読む Notice Warning Undefined array key "email"
未定義のプロパティを読む Notice Warning Undefined property: stdClass::$email
nullを配列として読む Notice(7.4で追加) Warning Trying to access array offset on null
nullのプロパティを読む Notice Warning Attempt to read property "email" on null
nullのメソッドを呼ぶ Error 例外 Error 例外 Call to a member function getEmail() on null

上の3行(変数・配列キー・プロパティ)が Notice から Warning に変わったことは、PHPマニュアルの「PHP 7.4.x から PHP 8.0.x への移行 ― 下位互換性のない変更点」を今回開いて確認しました。PHP 7.4 での実行は今回の環境では未実行 です。PHP 7 系では Notice: Undefined variable: name、Notice: Undefined index: email という文言だったことが広く知られており、検索でこの文言が出てきた場合は PHP 7 以前の情報だと判断できます。

よく見る文言 出るバージョン 意味
Notice: Undefined index: xxx PHP 7.4 まで 配列に文字列キー xxx がない
Notice: Undefined offset: 3 PHP 7.4 まで 配列に数値キー 3 がない
Warning: Undefined array key "xxx" PHP 8.0 以降 上の2つが統合された文言
Notice: Undefined variable: xxx PHP 7.4 まで 変数が未定義
Warning: Undefined variable $xxx PHP 8.0 以降 同上($ 付きの表記に)

エラーレベルが変わっても、処理が続き、値が NULL になる動作は同じ です。変わるのは、error_reporting の設定で Notice だけを外していた環境でも表示・記録されるようになる点です。

Warningを出さずに読む書き方

次の書き方は、未定義でもWarningを出しません。

<?php
// nowarn.php : Warning を出さずに読む書き方
error_reporting(E_ALL);
ini_set('display_errors', '1');

$user = ['id' => 1, 'nickname' => null];
$none = null;

var_dump(isset($name));                 // 未定義の変数
var_dump(isset($user['email']));        // 未定義のキー
var_dump(empty($user['email']));
var_dump($user['email'] ?? '(なし)');
var_dump($user['profile']['city'] ?? '(なし)'); // 何段でも
var_dump($none?->email);                // nullsafe 演算子
var_dump($none?->getProfile()?->city);
var_dump(isset($user['id'], $user['email'])); // 複数まとめて
echo "Warning は 1 つも出ません\n";

実行結果(PHP 8.4.21):

bool(false)
bool(false)
bool(true)
string(8) "(なし)"
string(8) "(なし)"
NULL
NULL
bool(false)
Warning は 1 つも出ません

$user['profile']['city'] ?? '(なし)' のように、途中のキーがなくても ?? は最後まで安全に評価 してくれます。

Warningを例外に変えて気づきやすくする

開発中は、Warningを例外に変換しておくと、未定義のキーを読んだ場所で処理が止まり、原因の行がすぐ分かります。

<?php
// err.php : Warning を例外に変えて、見落としを防ぐ
set_error_handler(function (int $no, string $msg, string $file, int $line) {
    throw new ErrorException($msg, 0, $no, $file, $line);
});
$user = ['id' => 1];
try {
    echo $user['email'];
} catch (ErrorException $e) {
    echo '捕捉: ', $e->getMessage(), ' (severity=', $e->getSeverity(), ', E_WARNING=', E_WARNING, ")\n";
}

実行結果(PHP 8.4.21):

捕捉: Undefined array key "email" (severity=2, E_WARNING=2)

LaravelやSymfonyなどのフレームワークは、標準でこの変換を行っています。素のPHPで書くときも、同じ仕組みを入れておくと原因の特定が早くなります。

issetとarray_key_existsの違い

isset は「キーがあり、値がnullでない」とき、array_key_exists は「キーがある」ときにtrue を返します。値がnullのキーで結果が分かれます。

<?php
// ake.php : isset と array_key_exists の違い
$row = ['id' => 7, 'deleted_at' => null];

echo "キー deleted_at(値は null)\n";
var_dump(isset($row['deleted_at']));
var_dump(array_key_exists('deleted_at', $row));
var_dump($row['deleted_at'] ?? 'default');

echo "キー memo(存在しない)\n";
var_dump(isset($row['memo']));
var_dump(array_key_exists('memo', $row));

echo "3 つの状態を区別する\n";
function state(array $a, string $k): string {
    if (!array_key_exists($k, $a)) return 'キーなし';
    if ($a[$k] === null)           return 'キーあり・値は null';
    return 'キーあり・値あり';
}
foreach (['id', 'deleted_at', 'memo'] as $k) {
    echo str_pad($k, 11), ' => ', state($row, $k), "\n";
}

echo "オブジェクトのプロパティ\n";
class Member { public ?string $nickname = null; public string $name = 'Sato'; }
$m = new Member();
var_dump(isset($m->nickname));
var_dump(property_exists($m, 'nickname'));
var_dump(property_exists($m, 'age'));

実行結果(PHP 8.4.21):

キー deleted_at(値は null)
bool(false)
bool(true)
string(7) "default"
キー memo(存在しない)
bool(false)
bool(false)
3 つの状態を区別する
id          => キーあり・値あり
deleted_at  => キーあり・値は null
memo        => キーなし
オブジェクトのプロパティ
bool(false)
bool(true)
bool(false)
状態 isset array_key_exists ?? '既定値'
キーあり・値あり true true 元の値
キーあり・値はnull false true 既定値
キーなし false false 既定値

使い分けの目安は次のとおりです。

場面 向いている書き方 理由
値があれば使い、なければ既定値 ?? nullとキーなしを同じ扱いにできる
データベースの行でNULLの列を区別したい array_key_exists 「列はあるが値がNULL」を区別できる
JSONで「項目を送っていない」と「nullを送った」を区別したい array_key_exists 部分更新で、nullを「消す」の意味に使える
オブジェクトでプロパティの有無を調べたい property_exists 値がnullでもtrueになる

issetの三項演算子を ?? ・??= ・?-> に書き換える

PHP 7.0 で ??(null合体演算子)、PHP 7.4 で ??=(null合体代入演算子)、PHP 8.0 で ?->(nullsafe演算子)が加わりました。isset と三項演算子の組み合わせは、これらで短く書けます。

演算子 使えるバージョン 意味 書き換え前
$a ?? $b PHP 7.0 以降 $a が定義済みでnullでなければ $a、そうでなければ $b isset($a) ? $a : $b
$a ??= $b PHP 7.4 以降 $a が未定義またはnullのときだけ $b を代入 if (!isset($a)) { $a = $b; }
$o?->p PHP 8.0 以降 $o がnullならnull、そうでなければ $o->p $o !== null ? $o->p : null
$a ?: $b PHP 5.3 以降 $a がtrueとみなせれば $a、そうでなければ $b(未定義だとWarning) $a ? $a : $b
<?php
// coalesce.php : isset の三項演算子を ?? / ??= / ?-> に書き換える
error_reporting(E_ALL); ini_set('display_errors', '1');
$_GET = ['page' => '3', 'q' => ''];
$config = ['db' => ['host' => 'localhost']];

// 1) isset + 三項演算子 → ??
$page1 = isset($_GET['page']) ? $_GET['page'] : '1';
$page2 = $_GET['page'] ?? '1';
var_dump($page1 === $page2);

// 2) 連鎖:左から順に、最初の「null でない値」
$sort = $_GET['sort'] ?? $config['default_sort'] ?? 'id';
var_dump($sort);

// 3) ?? は空文字や 0 を通す。?: は falsy を置き換える
var_dump($_GET['q'] ?? 'すべて');
var_dump(($_GET['q'] ?? '') ?: 'すべて');

// 4) ??= : 未定義または null のときだけ代入
$config['db']['port'] ??= 3306;
$config['db']['host'] ??= '127.0.0.1';   // すでに値があるので変わらない
$count = null;
$count ??= 0;
echo json_encode($config), ' count=', $count, "\n";

// 5) ?-> : null ならそこで止めて null を返す
class Profile { public function __construct(public ?string $city = null) {} }
class User { public function __construct(public ?Profile $profile = null) {} }
$users = [new User(new Profile('Osaka')), new User(), null];
foreach ($users as $u) {
    echo $u?->profile?->city ?? '(未登録)', "\n";
}

// 6) 演算子の優先順位:?? は + や . より低い
$bonus = null;
var_dump(100 + $bonus ?? 50);     // (100 + null) ?? 50
var_dump(100 + ($bonus ?? 50));

実行結果(PHP 8.4.21):

bool(true)
string(2) "id"
string(0) ""
string(9) "すべて"
{"db":{"host":"localhost","port":3306}} count=0
Osaka
(未登録)
(未登録)
int(100)
int(150)

ポイントは3つあります。

  • ?? は空文字や0をそのまま通します。 3) のように、空文字のときも既定値にしたい場合は ($v ?? '') ?: '既定値' と組み合わせます。
  • ?? は + や . より優先順位が低い ため、6) のように計算や文字列連結と混ぜるときは ( ) で囲みます。囲まないと 100 + null が先に計算され、結果は 100 になります。
  • ?-> は読み取り専用 です。$u?->name = 'x'; のような代入には使えません。

フォーム入力の"0"がemptyで未入力扱いになる例と直し方

フォームから届く値はすべて文字列です。数量の欄に 0 と入力すると、PHPには "0" が届き、empty("0") はtrueになります。

<?php
// form.php : フォーム入力 "0" が empty で弾かれる例と直し方
$_POST = ['quantity' => '0', 'name' => ' ', 'note' => ''];  // 送信内容の代わり

echo "--- 直す前:empty で必須チェック\n";
foreach (['quantity', 'name', 'note', 'zip'] as $key) {
    $msg = empty($_POST[$key]) ? '未入力と判定' : '入力ありと判定';
    printf("%-9s %-5s => %s\n", $key, var_export($_POST[$key] ?? null, true), $msg);
}

echo "--- 直した後:文字列として空かどうかを調べる\n";
function is_blank(mixed $v): bool
{
    if ($v === null) return true;                 // 未送信
    if (is_string($v)) return trim($v) === '';    // 空文字・空白だけ
    if (is_array($v)) return $v === [];           // 何も選ばれていない
    return false;                                 // 0, 0.0, false は「値あり」
}
foreach (['quantity', 'name', 'note', 'zip'] as $key) {
    $msg = is_blank($_POST[$key] ?? null) ? '未入力と判定' : '入力ありと判定';
    printf("%-9s %-5s => %s\n", $key, var_export($_POST[$key] ?? null, true), $msg);
}

実行結果(PHP 8.4.21):

--- 直す前:empty で必須チェック
quantity  '0'   => 未入力と判定
name      ' '   => 入力ありと判定
note      ''    => 未入力と判定
zip       NULL  => 未入力と判定
--- 直した後:文字列として空かどうかを調べる
quantity  '0'   => 入力ありと判定
name      ' '   => 未入力と判定
note      ''    => 未入力と判定
zip       NULL  => 未入力と判定

直す前は2か所で意図と違う結果になっています。

入力 emptyの判定 期待する判定 理由
数量 "0" 未入力 入力あり "0" はfalseとみなされる文字列
名前 " "(空白だけ) 入力あり 未入力 空白1文字はtrueとみなされる

フォームの必須チェックは trim($v ?? '') === '' で書く と、"0" を入力として受け取り、空白だけの入力を未入力として扱えます。全角スペースも取り除きたい場合は、PHP 8.4 で追加された mb_trim() が使えます。

型ごとの「空」の判定は、次のように書き分けると意図がはっきりします。

対象 emptyを使った書き方 意図がはっきりする書き方
文字列の入力 empty($s) trim($s ?? '') === ''
数値(0も有効) empty($n) $n === null
配列 empty($list) $list === [] または count($list) === 0
真偽値 empty($flag) $flag === false または !$flag
null許容のオブジェクト empty($obj) $obj === null

つまずきやすい5つのポイント

1. 型付きプロパティは初期化前に読むとError

型を付けたプロパティは、値を入れる前は「未初期化」という状態です。isset はfalseを返しますが、直接読むとErrorになります。

<?php
class Item { public string $name; public ?string $memo = null; }
$i = new Item();
var_dump(isset($i->name), isset($i->memo));
try { var_dump($i->name); } catch (Error $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; }

実行結果(PHP 8.4.21):

bool(false)
bool(false)
Error: Typed property Item::$name must not be accessed before initialization

null許容型(?string)でも、= null と書かなければ初期値は入りません。 コンストラクタで代入するか、宣言時に初期値を書いておくと、この状態を避けられます。

2. emptyには式を渡せるが、issetには変数しか渡せない

empty(trim($s)) のように、empty には関数の戻り値や式を渡せます(PHP 5.5 以降)。一方 isset(trim($s)) は構文エラーになります。式の結果がnullかどうかは === null で調べます。

<?php
function find() { return ''; }
var_dump(empty(find()));       // bool(true)
var_dump(empty(trim('  ')));   // bool(true)

3. 文字列に対するisset

文字列に [ ] を付けると、1文字を取り出す操作になります。isset もこの規則に従います。

<?php
$s = 'abc';
var_dump(isset($s[1]), isset($s[5]), isset($s['x']), isset($s[1][0]));

実行結果(PHP 8.4.21):

bool(true)
bool(false)
bool(false)
bool(true)

配列を期待している変数に文字列が入っていると、isset($data[0]) がtrueになることがあります。配列かどうかも確かめたいときは is_array($data) && isset($data[0]) と書くと確実です。

4. マジックメソッド __isset と __get

__get で動的なプロパティを返すクラスでは、isset と empty は先に __isset を呼びます。

<?php
class Bag {
    private array $d = ['a' => 1, 'n' => null];
    public function __isset($k) { echo "[__isset($k)] "; return isset($this->d[$k]); }
    public function __get($k) { echo "[__get($k)] "; return $this->d[$k] ?? null; }
}
$bag = new Bag();
var_dump(isset($bag->a));
var_dump(empty($bag->a));
var_dump($bag->zzz ?? 'D');

実行結果(PHP 8.4.21):

[__isset(a)] bool(true)
[__isset(a)] [__get(a)] bool(false)
[__isset(zzz)] string(1) "D"

__get だけを実装して __isset を書いていないクラスでは、値があっても isset がfalse、empty がtrueになります。__get を書くときは __isset もセットで実装する と、?? や empty が期待どおりに動きます。

5. error_reportingでWarningを外しても原因は残る

error_reporting(E_ALL & ~E_WARNING) と設定すると、未定義キーのWarningは表示されなくなります。ただし値が NULL になる動作は変わらないため、後続の計算や表示に影響します。表示を止めるより、?? で既定値を決めるほうが、動作が読み手に伝わります。 @$a['x'] のようにエラー制御演算子で抑える方法も同じ理由で、$a['x'] ?? null に置き換えるのがおすすめです。

直す前と直した後の書き換え集

直す前 直した後 変わる点
isset($a['k']) ? $a['k'] : 'x' $a['k'] ?? 'x' 同じ意味で短くなる
if (!isset($a['k'])) { $a['k'] = []; } $a['k'] ??= []; 同じ意味で1行になる
if (empty($_POST['qty'])) if (trim($_POST['qty'] ?? '') === '') "0" を入力として扱える
if (empty($list)) if ($list === []) 配列以外が来たときに気づける
if (is_null($v)) if ($v === null) 同じ結果。関数呼び出しがなくなる
if ($v == null) if ($v === null) 0 や "" をnull扱いしなくなる
@$a['k'] $a['k'] ?? null ほかのエラーを隠さない
$u ? $u->getName() : null $u?->getName() nullのときだけ止まる
if (isset($row['deleted_at'])) でキーの有無を判定 array_key_exists('deleted_at', $row) 値がnullのキーも「ある」と判定できる

関数の引数と戻り値では型で表す

変数が定義されているか分からない状況を減らすには、null許容型(?string)と既定値 を使います。

<?php
function show(?string $name = null): string
{
    return $name ?? '(名前なし)';
}
echo show(), ' / ', show('Sato'), "\n";

実行結果(PHP 8.4.21):

(名前なし) / Sato

引数に型があれば、関数の中では $name が必ず定義されています。isset で確かめる必要がなく、=== null か ?? だけで書けます。

判定方法を選ぶフローチャート

flowchart TD
    A[調べたい対象は?] --> B{変数やキーが<br>未定義の可能性がある?}
    B -- ある --> C{なければ既定値を使う?}
    C -- はい --> D["?? または ??="]
    C -- いいえ --> E{値がnullのキーも<br>あると判定したい?}
    E -- はい --> F[array_key_exists]
    E -- いいえ --> G[isset]
    B -- ない --> H{何を調べる?}
    H -- nullかどうか --> I["=== null"]
    H -- 文字列が空か --> J["trim して === ''"]
    H -- 配列が空か --> K["=== []"]
    H -- falseとみなせるか --> L["if (!$v) または empty"]

よくある質問

Q. issetとemptyはどちらを使えばよいですか? A. 「あるかどうか」を調べたいなら isset、「falseとみなせる値かどうか」を調べたいなら empty です。0 や "0" を有効な値として扱う場面では、empty の代わりに === null や trim($v ?? '') === '' を使うと意図どおりになります。

Q. !empty($v) と isset($v) は同じですか? A. 同じではありません。$v が 0・""・"0"・false・[] のとき、isset($v) はtrueですが !empty($v) はfalseです。!empty($v) は isset($v) && $v と同じ意味です。

Q. is_null($v) と $v === null はどちらがよいですか? A. 結果は同じです。=== null は関数呼び出しがなく、否定も !== null と書けるので読みやすくなります。array_filter などにコールバックとして渡すときは、'is_null' や is_null(...) が便利です。

Q. Undefined array keyのWarningを消すにはどうすればよいですか? A. キーがない場合の値を $a['key'] ?? '既定値' で決めるのが基本です。必須のキーなら isset で確かめてから読み、ない場合はエラーメッセージを返すようにします。error_reporting で表示を止める方法は、値が NULL になる動作が残るため、原因の対処にはなりません。

Q. PHP 7から8に上げたらWarningが増えました。動作は変わりましたか? A. 未定義の変数・配列キー・プロパティを読む操作は、エラーレベルが Notice から Warning に変わっただけで、値が NULL になる点は同じです。ログに出たWarningの行を ?? や isset で書き換えていくと、順に整理できます。

まとめ

  • issetは「定義済みでnullでない」、emptyは「未定義またはfalseとみなせる」、is_nullは「nullである」 を調べます。
  • "0"・0・0.0・""・false・[]・null・未定義は empty でtrue、" " と "0.0" はfalseです。
  • 未定義でもWarningを出さないのは isset・empty・??・??= です。is_null と === null は未定義の変数でWarningが出ます。
  • PHP 8.0 から Undefined variable・Undefined array key は Warning になりました。値が NULL になる動作は変わりません。
  • 値がnullのキーを「ある」と判定したいときは array_key_exists、プロパティなら property_exists を使います。
  • isset($a) ? $a : $b は $a ?? $b、nullかもしれないオブジェクトは ?-> で短く書けます。
  • フォームの必須チェックは trim($v ?? '') === '' にすると、"0" を入力として受け取れます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PHP isset・empty・is_nullの違い|null判定の早見表」

株式会社RJCでは、PHPのnull判定や型の扱いを仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?