PHP

PHPのswitch文でboolean(true/false)を使うと嵌るという話

PHP switch文の比較の問題とか

switch/caseで嵌った事があったので色々確認した事を覚書的に残しておきます。

PHPのversion

PHP 5.6.33 (cli) (built: Jan  3 2018 12:04:36)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies

問1.何が出力されるか

q1.php
<?php
for($i = 0; $i <= 10; $i++){
    switch ($i){
        case (0):
            echo "0: [$i]\n";
            break;
        case (2):
            echo "2: [$i]\n";
        case (3);
            echo "1st 3: [$i]\n";
            break;
        case (8):
            echo "8: [$i]\n";
        case (7):
            echo "7: [$i]\n";
            break;
        case (3);
            echo "2nd 3: [$i]\n";
            break;
        default:
            echo "default: [$i]\n";
            break;
    }
}
?>

答1.

0: [0]
default: [1]
2: [2]
1st 3: [2]
1st 3: [3]
default: [4]
default: [5]
default: [6]
7: [7]
8: [8]
7: [8]
default: [9]
default: [10]

解説するまでもないようなswitch/caseの基本形みたいな物。
まったく同じ条件のcaseがある場合はより上の行に記載したもののみ有効。

問2.何が出力されるか

q2.php
<?php
for($i = 0; $i <= 10; $i++){
    switch ($i){
        case (true):
            echo "true: [$i]\n";
            break;
        case (0):
            echo "0: [$i]\n";
            break;
        case (0 == false);
            echo "0 == false: [$i]\n";
            break;
        case (!is_null($i)):
            echo "!is_null(\$i): [$i]\n";
            break;
        case (0 == ($i % 8)):
            echo "(\$i % 8): [$i]\n";
            break;
        default:
            echo "default: [$i]\n";
            break;
    }
}
?>

答2.

0: [0]
true: [1]
true: [2]
true: [3]
true: [4]
true: [5]
true: [6]
true: [7]
true: [8]
true: [9]
true: [10]

常にtrueであるcaseが一番上にあるのに0だけは実行された。
これはPHPのbooleaが0をFALSEとして、0以外の数字はTRUEの扱いをするためでもある。
PHP: 論理型 (boolean) - Manual

問3.何が出力されるか(問2のcaseの順番を変更)

q3.php
<?php
for($i = 0; $i <= 10; $i++){
    switch ($i){
        case (0):
            echo "0: [$i]\n";
            break;
        case (0 == false);
            echo "0 == false: [$i]\n";
            break;
        case (!is_null($i)):
            echo "!is_null(\$i): [$i]\n";
            break;
        case (0 == ($i % 8)):
            echo "(\$i % 8): [$i]\n";
            break;
        case (true):
            echo "true: [$i]\n";
            break;
        default:
            echo "default: [$i]\n";
            break;
    }
}
?>

答3.

0: [0]
0 == false: [1]
0 == false: [2]
0 == false: [3]
0 == false: [4]
0 == false: [5]
0 == false: [6]
0 == false: [7]
0 == false: [8]
0 == false: [9]
0 == false: [10]

case (true):が常に実行されそうな気がしたが、
実際にはcaseの中で同じくtrueになる一番上のcase (0 == false):のみが実行される。

問4.何が出力されるか

q4.php
<?php
for($i = 0; $i <= 10; $i++){
    switch ($i){
        case (3):
            echo "3: [$i]\n";
            break;
        case (true):
            echo "true: [$i]\n";
            break;
        case (0):
            echo "0: [$i]\n";
            break;
        case (1):
            echo "1: [$i]\n";
            break;
        case (5):
            echo "5: [$i]\n";
            break;
        default:
            echo "default: [$i]\n";
            break;
    }
}
?>

答4.

0: [0]
true: [1]
true: [2]
3: [3]
true: [4]
true: [5]
true: [6]
true: [7]
true: [8]
true: [9]
true: [10]

0はfalseとみなされるのでtrueよりも下にあっても実行されるが、
0以外の数字はTRUEの扱いになってしまうため、trueよりも上になければ実行されない。

問5.何が出力されるか

q5.php
<?php
for($i = 0; $i <= 10; $i++){
    $str = "0123-567-xxx this is String.";
    $str = str_replace($i, 'word', $str);
    switch (strpos($str, 'word')){
        case (0): // 文中の先頭に特定の語句がある場合の処理
            echo "0: [$i][$str]\n";
            break;
        case (1): //文中の先頭には無いが、先頭n文字目までに特定の語句がある場合の処理
        case (2):
        case (3):
        case (4):
        case (5):
            echo "1~5: [$i][$str]\n";
            break;
        case (false): //文中に特定の語句がまったくない場合の処理
            echo "false: [$i][$str]\n";
            break;
        default: //文中に特定の語句はあるが先頭n文字目までには無い場合の処理
            echo "default: [$i][$str]\n";
            break;
    }
}
?>

答5.

0: [0][word123-567-xxx this is String.]
1~5: [1][0word23-567-xxx this is String.]
1~5: [2][01word3-567-xxx this is String.]
1~5: [3][012word-567-xxx this is String.]
0: [4][0123-567-xxx this is String.]
1~5: [5][0123-word67-xxx this is String.]
default: [6][0123-5word7-xxx this is String.]
default: [7][0123-56word-xxx this is String.]
0: [8][0123-567-xxx this is String.]
0: [9][0123-567-xxx this is String.]
0: [10][0123-567-xxx this is String.]

switchでは緩やかな比較のため0とfalseが同じとみなされる。
下に書かれているcase (false):よりも上にかかれたcase (0):のみが実行される。

  • 文中の先頭に特定の語句がある場合の処理
  • 文中の先頭には無いが、先頭n文字目までに特定の語句がある場合の処理
  • 文中に特定の語句がまったくない場合の処理
  • 文中に特定の語句はあるが先頭n文字目までには無い場合の処理

をこの方法で処理しようと思ったのだが上手く行かなかった。
if文であれば、===を使った厳密な比較が可能なので対処可能。

PHP: switch - Manual
PHP: 比較演算子 - Manual