はじめに
前回までの記事で、if・else・elseif という条件分岐の基本三兄弟を解説してきました。今回取り上げる switch は、それらとは少し異なるアプローチで、1つの値を、あらかじめ列挙した複数の候補(ケース)と順番に比較するための制御構造です。
同じ変数の値を多数の候補と比較する処理を elseif の連鎖で書くと、同じ変数名が何度も繰り返されて冗長になりがちですが、switch を使えばその変数を1回だけ指定し、各ケースをすっきりと列挙できます。一方で、switch には「ゆるい比較」や「フォールスルー」といった、PHP開発者をしばしば悩ませる独特の挙動もあります。本記事では基本的な使い方から、よくある落とし穴、より新しい match 式との使い分けまで詳しく解説します。
構文概要
| 項目 | 内容 |
|---|---|
| 構文名 | switch |
| 種別 | 制御構造(言語構文) |
| 基本構文 | switch ($value) { case A: ...; break; default: ...; } |
| 比較方法 | ゆるい比較(==相当) が使われる |
breakの省略 | 省略すると、次のcaseの処理に「フォールスルー(素通り)」する |
| 代替構文 | switch ($value): case A: ...; endswitch;(テンプレート内で使用) |
| 対応バージョン | PHP 4以前から存在する最古参の構文の一つ |
switchの動作フロー(イメージ図)
switch ($fruit) {
case 'apple':
echo 'りんごです';
break; ← ここで処理を抜ける
case 'banana':
case 'orange': ← caseを連続させることでOR条件のように使える
echo 'バナナかオレンジです';
break;
default:
echo '不明な果物です';
}
│
▼
┌───────────────────────────┐
│ $fruitの値を上から順に各caseの値と │
│ ゆるい比較(==)で照合し、 │
│ 最初に一致したcase以降を実行する │
│ breakがなければ、次のcaseも続けて実行 │
│ (フォールスルー)されてしまう │
└───────────────────────────┘
ポイントは、switch が**ゆるい比較(==)を使う点と、break を書き忘れると次の case の処理まで実行され続けてしまう(フォールスルー)**という2点です。この2つの挙動が、switch に関する最も有名で、最も多くのバグを生んできた落とし穴です。
実践サンプル7選
例1:基本的な使い方
<?php
function describeDayNumber(int $day): string
{
switch ($day) {
case 1:
return '月曜日';
case 2:
return '火曜日';
case 3:
return '水曜日';
default:
return '不明';
}
}
echo describeDayNumber(2) . PHP_EOL; // "火曜日"
例2:複数のcaseをまとめてOR条件のように扱う
<?php
function isWeekend(string $day): bool
{
switch ($day) {
case 'Saturday':
case 'Sunday':
// caseをbreakなしで連続させることで、
// "SaturdayまたはSunday"という条件を表現する
return true;
default:
return false;
}
}
var_dump(isWeekend('Saturday')); // true
var_dump(isWeekend('Monday')); // false
例3:ゆるい比較による意図しないマッチの実演
<?php
function dangerousSwitch(mixed $value): string
{
switch ($value) {
case 0:
// switchは==で比較するため、文字列や配列などの
// 0と「ゆるく等しい」とみなされる値もここにマッチしてしまう
return '0と判定されました';
case 'hello':
return 'helloと判定されました';
default:
return 'その他';
}
}
// PHP 8以降では文字列と数値の比較ルールが改善されているが、
// 古いコードベースや特定の値では依然として注意が必要
echo dangerousSwitch(0) . PHP_EOL;
echo dangerousSwitch(false) . PHP_EOL; // falseも最初のcase 0にマッチする
例4:break忘れによるフォールスルーの実演(典型的なバグ)
<?php
function describeLevel(int $level): void
{
switch ($level) {
case 1:
echo 'レベル1の処理' . PHP_EOL;
// breakを忘れている!
case 2:
echo 'レベル2の処理も実行されてしまう' . PHP_EOL;
break;
default:
echo '不明なレベル' . PHP_EOL;
}
}
describeLevel(1); // "レベル1の処理" と "レベル2の処理も実行されてしまう" の両方が出力される
例5:意図的なフォールスルーを活用する正当なパターン
<?php
function getAccessLevel(string $role): int
{
switch ($role) {
case 'superadmin':
// 意図的にbreakせず、adminの権限レベルも含める設計
case 'admin':
return 10;
case 'editor':
return 5;
default:
return 1;
}
}
echo getAccessLevel('superadmin') . PHP_EOL; // 10
echo getAccessLevel('admin') . PHP_EOL; // 10
例6:switch(true)パターンによる範囲条件の表現
<?php
function gradeFromScore(int $score): string
{
/**
* switch(true)とすることで、各caseに範囲条件式を書ける、
* やや裏技的だが広く使われるパターン
*/
switch (true) {
case $score >= 90:
return 'A';
case $score >= 70:
return 'B';
case $score >= 50:
return 'C';
default:
return 'D';
}
}
echo gradeFromScore(75) . PHP_EOL; // "B"
例7:match式への書き換え比較(PHP 8.0以降)
<?php
function statusLabelWithSwitch(string $status): string
{
switch ($status) {
case 'pending':
return '保留中';
case 'completed':
return '完了';
case 'cancelled':
return 'キャンセル済み';
default:
return '不明';
}
}
/**
* matchは厳密な比較(===)を行い、breakが不要で、
* フォールスルーの心配がない、より安全な代替構文
*/
function statusLabelWithMatch(string $status): string
{
return match ($status) {
'pending' => '保留中',
'completed' => '完了',
'cancelled' => 'キャンセル済み',
default => '不明',
};
}
echo statusLabelWithMatch('completed') . PHP_EOL;
関連機能との比較
| 機能 | 役割 | switchとの違い |
|---|---|---|
switch | 1つの値を複数のケースと比較する | 本記事の対象。ゆるい比較を行い、breakがないとフォールスルーする |
if / elseif | 任意の条件式を順番に評価する | より柔軟だが、同じ変数を何度も繰り返し書く必要がある場合がある |
match(PHP 8.0以降) | 値に基づいて式を返す、厳密な比較を行う分岐 | 厳密な比較(===相当)、break不要、フォールスルーなし、値を返す「式」である点が異なる |
| 連想配列によるディスパッチ | キーに対応する値や関数を取得する | switchの代替として、ルックアップテーブル的に使われることがある |
in_array() | 値が配列内に存在するかを判定する | 単純な包含判定であり、分岐処理自体は別途ifなどで行う必要がある |
よくある落とし穴(注意点)
breakを忘れるとフォールスルーが発生する これがswitchに関する最も有名で危険な落とし穴です。各caseの末尾にbreak(またはreturn、continue)を書き忘れると、次のcaseの処理まで連続して実行されてしまいます(例4を参照)。意図的なフォールスルー(例5)と、単なる書き忘れを区別できるよう、コメントを添えるなどの工夫が推奨されます。- ゆるい比較(
==)による予期しないマッチswitchは各caseとの比較に==を使用するため、型が異なる値同士が「等しい」と判定されてしまうことがあります(例3を参照)。厳密な比較が必要な場合は、match(PHP 8.0以降)への切り替えを検討しましょう。 defaultケースの配置は末尾である必要はないが、慣習として末尾に置くdefaultはswitch内のどこに書いても機能しますが、コードの可読性のためには、末尾(または最初)に統一的に配置するのが一般的な慣習です。- 複雑な条件式を直接
caseに書くことはできないswitchの各caseは単一の値との比較が基本であり、case $score >= 90:のような式を直接書くことはできません(これができるのはswitch (true)という特殊なパターンのみです。例6を参照)。 - 変数のスコープが
switchブロック全体で共有されるswitch内の各caseブロックは、if–elseifのような独立したブロックスコープを持たず、switch全体で1つのスコープを共有します。これはフォールスルーと合わせて、変数の扱いに注意が必要な一因になります。
まとめ
| 観点 | まとめ |
|---|---|
| 何をする構文か | 1つの値を、複数のcaseで列挙した候補と順番に比較し、マッチした処理を実行する |
| 主な用途 | 単一の変数に対する多数の値の判定、権限レベルの判定など |
| 比較方法 | ゆるい比較(==)。厳密な比較が必要ならmatchを検討する |
breakの重要性 | 省略するとフォールスルー(次のcaseへの素通り)が発生する |
| 注意点 | ゆるい比較による予期しないマッチ、break忘れ、複雑な条件式はswitch(true)パターンが必要 |
switch は、同じ変数に対する多数の値の判定を簡潔に書けるPHPの伝統的な制御構造ですが、ゆるい比較とフォールスルーという2つの特性ゆえに、バグの温床になりやすいことでも知られています。PHP 8.0以降では、これらの問題を解消した match 式という選択肢もあるため、新規コードでは用途に応じて両者を使い分けることが推奨されます。
