3.

PHP の比較演算子|== と === の違いと PHP 8 での変更点

編集
この記事の要点
  • 原則 === を使う== は型をそろえてから比べるので直感に反する結果になる
  • PHP 8 で 0 == "foo"false に変わった。PHP 7 までは true だった
  • <=>(宇宙船演算子)は -1 / 0 / 1 を返す。usort() と相性がよい
  • null の判定は === nullis_null()== null0"" も拾う
  • 比較の型変換は in_array() array_search() switch にも効く。厳密比較を明示する

演算子の一覧

演算子意味型を見るか
==等しい(型変換あり)見ない
===型も値も等しい見る
!= / <>等しくない見ない
!==型か値が違う見る
< > <= >=大小見ない
<=>左が小さければ -1、同じなら 0、大きければ 1見ない
??左が null か未定義なら右
?:左が偽なら右(0"" も偽)

== と === の違い

var_dump(1 == "1");        // true    型をそろえてから比べる
var_dump(1 === "1");       // false   型が違う

var_dump(0 == false);      // true
var_dump(0 === false);     // false

var_dump("1" == "01");     // true    どちらも数値文字列 → 数値として比較
var_dump("1" === "01");    // false

var_dump("10" == "1e1");   // true    どちらも数値文字列(1e1 は 10)
var_dump(100 == "1e2");    // true

var_dump(null == false);   // true
var_dump(null === false);  // false

「郵便番号 "0123""123" が一致してしまう」といった事故は、== が数値文字列を数値として比べることが原因です。ID・コード・郵便番号など、意味のある文字列は必ず === で比べてください。

PHP 8 で変わった点

// 数値 と 非数値文字列 の比較
var_dump(0 == "foo");      // PHP 8: false   / PHP 7: true
var_dump(0 == "");         // PHP 8: false   / PHP 7: true
var_dump("abc" == 0);      // PHP 8: false   / PHP 7: true

// 数値 と 数値文字列 の比較は今までどおり
var_dump(0 == "0");        // true
var_dump(42 == "42");      // true
var_dump(42 == " 42");     // true

PHP 8 では数値と非数値文字列を比べるとき、数値のほうを文字列に変換して比較するようになりました。if ($id == 0) のような書き方が PHP 7 と 8 で挙動を変えるため、バージョンを上げるときの確認点になります。

宇宙船演算子と並べ替え

var_dump(1 <=> 2);      // int(-1)
var_dump(2 <=> 2);      // int(0)
var_dump(3 <=> 2);      // int(1)

$users = [
    ['name' => '田中', 'age' => 30],
    ['name' => '鈴木', 'age' => 25],
];

// 年齢の昇順
usort($users, fn($a, $b) => $a['age'] <=> $b['age']);

// 降順は左右を入れ替える
usort($users, fn($a, $b) => $b['age'] <=> $a['age']);

// 複数キー: 年齢の降順、同じなら名前の昇順
usort($users, fn($a, $b) =>
    [$b['age'], $a['name']] <=> [$a['age'], $b['name']]);

配列同士に <=> を使うと先頭の要素から順に比較されるため、複数キーの並べ替えが 1 行で書けます。

null の判定

$x = 0;

var_dump($x == null);      // true    ← 0 も拾ってしまう
var_dump($x === null);     // false   これが正しい
var_dump(is_null($x));     // false

// 未定義でも警告が出ない書き方
$name = $user['name'] ?? '名無し';        // null か未定義なら右
$name = $user['name'] ?: '名無し';        // 偽(0 や "" も含む)なら右

$obj?->method();                          // null なら呼ばずに null を返す

// 代入も ?? でできる
$config['debug'] ??= false;               // 未設定のときだけ入れる
書き方右辺が使われる条件未定義でも警告なし
$a ?? $b$anull または未定義はい
$a ?: $b$a が偽(0 "" [] も含む)いいえ

配列とオブジェクトの比較

var_dump([1, 2] == [1, 2]);                       // true
var_dump(['a'=>1,'b'=>2] ==  ['b'=>2,'a'=>1]);    // true   順序は見ない
var_dump(['a'=>1,'b'=>2] === ['b'=>2,'a'=>1]);    // false  順序も見る

class P { public function __construct(public int $x) {} }
$a = new P(1); $b = new P(1); $c = $a;

var_dump($a == $b);      // true    同じクラスで中身が同じ
var_dump($a === $b);     // false   実体が違う
var_dump($a === $c);     // true    同じ実体

オブジェクトの ===「同じインスタンスか」を判定します。値が同じかどうかを見たいときは ==、または比較用のメソッドを自分で用意します。

文字列の比較

var_dump("apple" < "banana");     // true    辞書順
var_dump("10" < "9");             // true    どちらも数値文字列 → 数値比較
var_dump("10a" < "9a");           // false   非数値文字列 → 辞書順

var_dump(strcmp("a", "b"));       // int(-1)  型変換なしで比べる
var_dump(strcasecmp("A", "a"));   // int(0)   大文字小文字を無視
var_dump(strnatcmp("img10", "img9"));  // int(1)  自然順(人間の感覚)

// パスワードやトークンはタイミング攻撃に強い比較を使う
var_dump(hash_equals($stored, $input));

秘密の文字列を === で比べると、一致するバイト数で処理時間が変わり推測の手がかりになります。トークンや署名の比較には hash_equals() を使ってください。

型変換は switch と in_array にも効く

$value = 0;

switch ($value) {          // switch は == で比較する
    case "abc":            // PHP 7 なら一致してしまう
        echo "abc";
        break;
}

// match は === で比較する(PHP 8 以降・こちらが安全)
echo match ($value) {
    0 => "ゼロ",
    default => "その他",
};

var_dump(in_array("abc", [0, 1, 2]));        // PHP 8: false
var_dump(in_array(1, ["1", "one"], true));   // false  厳密比較

match は常に === で比較し、どれにも当たらなければ UnhandledMatchError を投げます。分岐漏れを実行時に検出できるため、値による分岐は switch より match が安全です。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 算術演算子
  2. 代入演算子
  3. 比較演算子
  4. 論理演算子

最近更新/作成されたページ