はじめに
動機
数年前に作られたであろうソースコードを読んでいたのですが、switch文の記述でどのような実行結果になるのか一目で分からない処理を見つけました。
公式ドキュメントを見ながら、swich文について調べ直したので、備忘録として残します。
記事の構成上、初級編からになっていますが、一番伝えたいことは上級編の部分です。
上級編
記事の構成
それぞれのセクションは下記のような構成になっています。
悪いコード例を見て、実行結果がどうなるか予想してみると良いと思います。
- 悪いコード例
- 実行結果
- 公式ドキュメントの引用
- 解説
- リファクタリング後のコード(記載しない場合もあり)
実行環境
動作確認は下記バージョンで行っています。
$ php -v
PHP 7.0.12 (cli) (built: Oct 13 2016 10:47:49) ( ZTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
初級編
問題1 型比較
悪いコード例
<?php
$case = '1';
switch ($case) {
case true:
echo 'bool';
break;
case 1:
echo 'int';
break;
case '1':
echo 'string';
break;
default:
echo 'other';
}
まずは、swich文の悪い例でよく出てくるパターンです。
string型の1に対して、bool型、int型、string型の順に比較しています。
実行結果
$ php swich.php
bool
公式ドキュメントの引用
switch/case が行うのは、 緩やかな比較 であることに注意しましょう。
解説
PHPのswitch文では厳密な比較(===)ではなく、緩やかな比較(==)が行われます。
緩やかな比較については、公式ドキュメントに比較表があるので、参考にしてください。
PHP 型の比較表
リファクタリング
緩やかな比較であることを利用してswitch文の先頭にはtrueを入れます。
各case文で$case
と厳密な比較を行うようにして、対策を行っています。
<?php
$case = '1';
switch (true) {
case $case === true:
echo 'bool';
break;
case $case === 1:
echo 'int';
break;
case $case === '1':
echo 'string';
break;
default:
echo 'other';
}
緩やかな比較に気をつけろ!
問題2 break文の省略
悪いコード例
<?php
$case = 'orange';
switch ($case) {
case 'apple':
echo 'apple';
case 'orange':
echo 'orange';
case 'strawberry':
echo 'strawberry';
default:
echo 'other';
}
各case文の終わりでbreak文を省略しています。
実行結果
$ php swich.php
orangestrawberryother
公式ドキュメントの引用
PHPはswitchブロックの終わりまたは最初の break文まで実行を続けます。 CASE文の終わりにbreak文を書かない場合は、PHPは 次のCASE文を実行しつづけます。
解説
次のcase文が出てきても、break文を省略した場合は、switch文の終わり(閉じ波括弧}
)まで書かれている処理が実行されます。
次のような、複数のcase文に対して、同じ処理を実行する場合を考えると、イメージがつきやすいのではないでしょうか。
<?php
$case = 'orange';
switch ($case) {
case 'apple':
case 'orange':
echo 'fruit';
break;
case 'carrot':
case 'cabbage':
echo 'vegetable';
break;
default:
echo 'other';
}
$ php swich.php
fruit
リファクタリング
省略します。
break文を忘れずに!
中級編
問題3 switchの中のcontinue
悪いコード例
<?php
for ($i = 1; $i <= 3; $i++) {
switch ($i) {
case 1:
echo 1;
break;
case 2:
echo 2;
break;
case 3:
echo 3;
continue;
}
echo '-';
}
for文でループをしている中でswitch文を実行しています。
作者は$i = 3
の時にswitch文の中からfor文のループをcontinueして、switch文の次の処理echo '-';
をスキップして1-2-3
のような出力を期待しています。
実行結果
$ php swich.php
1-2-3-
公式ドキュメントの引用
他の言語とは違って、 continue命令は switch にも適用され、breakと同じ動作をします。 ループの内部で switch を使用しており、 外側のループの処理を続行させたい場合には、continue 2 を使用してください。
解説
PHPではswitch文をループ処理として認識します。
そのため、continue;
ではswitch文から抜けるとして処理されているため、for文のループから抜けず、$i = 3
のときも処理も実行されてしまいます。
リファクタリング
引用部分の通り、continue 2;
とすることで、switch文の外側のループに対してもcontinue文が働き、1-2-3
という出力結果にすることができます。
<?php
for ($i = 1; $i <= 3; $i++) {
switch ($i) {
case 1:
echo 1;
break;
case 2:
echo 2;
break;
case 3:
echo 3;
continue 2;
}
echo '-';
}
switch文はループ処理である!
問題4 case文の終わりをセミコロンにする
悪いコード例
<?php
$case = 'orange';
switch ($case) {
case 'apple';
echo 'apple';
break;
case 'orange';
echo 'orange';
break;
case 'strawberry';
echo 'strawberry';
break;
default;
echo 'other';
}
各case文の終わりをコロン:
ではなく、セミコロン;
で書かれています。
実行結果
$ php swich.php
orange
公式ドキュメントの引用
case の最後はコロンではなくセミコロンとすることもできます。
解説
case文の終わりはコロンでもセミコロンでも同じ動きをするようです。
文法的に間違いはないとはいえ、処理が分かりにくくなってしまうので、コロンで統一するようにしましょう。
リファクタリング
省略します。
case文はコロンで締めるべし!
上級編
上級からは悪いコードを実行時の選択肢をつけました。
正解はどの選択肢になるか予想してみてください。
問題5 default文の下にcase文があるとき
悪いコード例
<?php
$case = 'orange';
switch ($case) {
default:
echo 'other';
case 'apple':
echo 'apple';
break;
case 'orange':
echo 'orange';
break;
case 'strawberry':
echo 'strawberry';
break;
}
選択肢
-
other
が出力される -
otherapple
が出力 -
orange
が出力 - 1~3のどれか + warningエラーが発生
- fatalエラーが発生し、処理が中断
default文が先頭にあり、しかも、default文の処理の後でbreakしていません。
実行結果の予想としては、上記の3択で迷う人が多いと思います。
実行結果
$ php swich.php
orange
公式ドキュメントの引用
default は、case 文の特別な場合です。これは他の全ての case にマッチしない場合に実行されます。
解説
問題1のように、上から評価されていくことを考えると、defaultが最初に来ているので、条件に関わらずdefaultの中が実行されそうに見えます。
default文がcase文の上にある場合については、公式ドキュメントにも記載されていませんでしたが、上記引用部に書かれているとおり、default文は順番にかかわらず、どのcase文にも一致しなかった場合のみに実行されます。
非常に分かりにくいので、default文はswitchの最後に書くようにしましょう。
ちなみに、先頭にdefaultが書かれている場合で、どのcaseにも当てはまらなかったときはきちんとdefaultの中身が実行されます。
上記プログラムではdefaultの最後にbreak文がないので、実行結果はotherapple
になります。
リファクタリング
省略します。
default文は最後に書け!
問題6 default文が複数あるとき
悪いコード例
<?php
$case = 'melon';
switch ($case) {
case 'apple':
echo 'apple';
break;
case 'orange':
echo 'orange';
break;
case 'strawberry':
echo 'strawberry';
break;
default:
echo 'other1';
break;
default:
echo 'other2';
break;
}
選択肢
-
other1
が出力される -
other2
が出力される -
other1other2
が出力される - 1~3のどれか + warningエラーが発生
- fatalエラーが発生し、処理が中断
defaultの処理が2つ書かれています。
実行結果
$ php swich.php
PHP Fatal error: Switch statements may only contain one default clause in /path/to/swich.php on line 21
公式ドキュメントの引用
default ケースを複数指定すると E_COMPILE_ERROR エラーが発生するようになりました。
解説
PHP>=7.0.0 ではコンパイル時のエラー(Fatalエラー)が出力されるようになったみたいです。割と最近ですね( ゚Д゚);
では、PHP<7.0.0ではどうなるのでしょうか?
PHP5.6.19の環境で試してみました。
$ php swich.php
other2
warningエラーすら出ずに、other2
のみが出力されました。
リファクタリング
省略します。
6. default文は1つにしろ!
PHPerが知るべきswitch文の6つのこと
- 緩やかな比較に気をつけろ!
- break文を忘れずに!
- switch文はループ処理である!
- case文はコロンで締めるべし!
- default文は最後に書け!
- default文は1つにしろ!