📌

[PHP]switch文の緩やかな比較についての検証

2022/03/30に公開

著:taiki_kimura

はじめに

案件でswitch文の緩やかな比較について考える機会があったので投稿します。
PHPのswitch文で比較演算子を使用した場合の挙動について検証してみました。

内容

まずはPHPのswitch文のリファレンスを確認します。
PHP: switch - Manual

phpのswitch文の条件判定では、緩やかな比較によって処理されます。
緩やかな比較とは、==によって行われるため、型が異なる同士の比較は自動的に型変換が行われます。(緩やかな比較と厳密な比較の詳細については割愛します)

この緩やかな比較は注意が必要なため、リファレンスにも割とあっさりめですが書かれています。

注意:
switch/case が行うのは、 緩やかな比較 であることに注意しましょう。

肝心な緩やかな比較についての表は下記にまとめられています。
PHP: PHP 型の比較表 - Manual

比較演算子については下記の表に沿って上から順に比較されていくことも要確認です。
PHP: 比較演算子 - Manual

検証

では検証していきます。
検証用の値を配列で用意し、switch文で条件分岐させます。記事の便宜上、0〜6番のkeyを設定しています。また、foreach文は表示のために記述しているのでスルーして下さい。

// 検証用の値
$calc_list = [
    0 => -1,
    1 => 0,
    2 => 1,
    3 => 5,
    4 => false,
    5 => true,
    6 => null
];

foreach ($calc_list as $key => $calc) {
    echo $key . '番目:';
    // switch文は緩やかな比較
    switch ($calc) {
        case $calc < 5:
            echo '5未満です' . "\n";
            break;

        case 5 < $calc:
            echo '6以上です' . "\n";
            break;

        default:
            echo 'defultです' . "\n";
            break;
    }
}

実行結果が以下の通りです。各行の左の値は配列の番号を示しています。

0番目:5未満です
1番目:6以上です
2番目:5未満です
3番目:defultです
4番目:6以上です
5番目:defultです
6番目:6以上です

解説

配列のキーの番号ごとに出力された結果について解説していきます。

0番目、2番目、3番目

最初に、0番目2番目3番目の配列の値ですが、caseの比較演算子の通り、表示されているかと思います。(表現の仕方について全ての結果は比較演算子の通りで間違いないのですが、分かりやすいという意味で使ってます)
0番目は、-1のため、5未満ですが表示。
2番目は 、1のため、5未満ですが表示。
3番目は 、5のため、一致する条件が無くdefaultですが表示。

1番目: 0

では、1番目の配列についてです。
値に0が入っていますが、判定としては6以上ですとなっています。
ここが緩やかな比較の注意する点となります。
答えとしては、$calc0の場合、case 5 < $calcfalseのため、0 == falseにより条件が一致することにより、判定として6以上ですが表示されます。
最初の比較式のcase $calc < 5ではtrueとなり、条件が一致しないため次の比較が処理されます。

せっかくなので、緩やかな比較の対応表から見た一致する値です。

<img title='PHP__PHP_型の比較表_-Manual-2.png' alt='PHP__PHP_型の比較表-_Manual-2' src='/attachments/3a0977b2-df33-42a0-a13a-368e118c56be' width="1128" data-meta='{"width":1128,"height":826}'>

4番目: false

値がfalseの場合は、数値と比較する際にbool変換して判定を行っています。
リファレンスに記載されている両辺を bool に変換し、false < true と判断しますの判定となります。

bool型の比較例

var_dump(100 < TRUE); // FALSE - same as (bool)100 < TRUE
var_dump(-10 < FALSE);// FALSE - same as (bool)-10 < FALSE

本題の式でも確認してみました。

$cal = false;
var_dump($cal < 5);
var_dump(5 < $cal);
bool(true)
bool(false)

よって、5 < $calcfalseとなり、false == falseにより条件が一致するため6以上ですが表示されます。

5番目: true

こちらも4番目の配列と同様にvar_dumpを使用して結果を確認してみました。

$cal = true;
var_dump($cal < 5);
var_dump(5 < $cal);
bool(false)
bool(false)

結果から一致するcaseが無いためdefault文の処理defultですが実行されます。

6番目: null

値がnullの場合も同様にvar_dumpを使用して結果を確認してみました。

$cal = null;
var_dump($cal < 5);
var_dump(5 < $cal);
bool(true)
bool(false)

$cal < 5ではtrueとなりますが、nullは型変換によりfalseとなるため一致しません。
5 < $calcではfalseとなり、条件が一致するため、6以上ですが表示されます。

番外編: 文字列

PHP8から数値と文字列を緩やかな比較で行った場合、結果がfalseに仕様変更となりました。

PHP7まで:0 == "php"true
PHP8  :0 == "php"false

参考:PHP 型の比較表

  • PHP 8.0.0 より前のバージョンでは、true でした。

検証用の式でもPHPの上記のバージョンによって結果が異なります。
最新のPHP8を使用する場合はこのような仕様変更も注意が必要ですね。

おわりに

条件分岐においてswitch文を使用する機会はあると思いますが、比較する際は緩やかな比較であるということに注意が必要です。
本来、今回の検証の式のような比較を伴う厳密な条件分岐においては、switch文は適さないと思うため素直に別の方法(if文など)を考えたほうが良いと思います。
switch(true)による厳密比較の方法もありますが、if文の方が本来の書き方からして合ってそうな感じがします。

検証に当たってPHPのソースコードを覗いて見ましたが、こちらをベースに解説するには骨が折れるため挫折しました 興味があったら読んでみて下さい。
ソースコードを追った記事もありますので参考までに。
PHPの緩やかな比較の実態 - Qiita

宣伝

パーソンリンクではエンジニアを募集しています!
https://www.person-link.co.jp/recruit

Discussion