[PHP]switchとは?1つの値を複数のケースと比較する構文を徹底解説

PHP

はじめに

前回までの記事で、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との違い
switch1つの値を複数のケースと比較する本記事の対象。ゆるい比較を行い、breakがないとフォールスルーする
if / elseif任意の条件式を順番に評価するより柔軟だが、同じ変数を何度も繰り返し書く必要がある場合がある
match(PHP 8.0以降)値に基づいて式を返す、厳密な比較を行う分岐厳密な比較(===相当)、break不要、フォールスルーなし、値を返す「式」である点が異なる
連想配列によるディスパッチキーに対応する値や関数を取得するswitchの代替として、ルックアップテーブル的に使われることがある
in_array()値が配列内に存在するかを判定する単純な包含判定であり、分岐処理自体は別途ifなどで行う必要がある

よくある落とし穴(注意点)

  1. breakを忘れるとフォールスルーが発生する これが switch に関する最も有名で危険な落とし穴です。各 case の末尾に break(または return、continue)を書き忘れると、次の case の処理まで連続して実行されてしまいます(例4を参照)。意図的なフォールスルー(例5)と、単なる書き忘れを区別できるよう、コメントを添えるなどの工夫が推奨されます。
  2. ゆるい比較(==)による予期しないマッチ switch は各 case との比較に == を使用するため、型が異なる値同士が「等しい」と判定されてしまうことがあります(例3を参照)。厳密な比較が必要な場合は、match(PHP 8.0以降)への切り替えを検討しましょう。
  3. defaultケースの配置は末尾である必要はないが、慣習として末尾に置く default は switch 内のどこに書いても機能しますが、コードの可読性のためには、末尾(または最初)に統一的に配置するのが一般的な慣習です。
  4. 複雑な条件式を直接caseに書くことはできない switch の各 case は単一の値との比較が基本であり、case $score >= 90: のような式を直接書くことはできません(これができるのは switch (true) という特殊なパターンのみです。例6を参照)。
  5. 変数のスコープがswitchブロック全体で共有される switch 内の各 case ブロックは、if–elseif のような独立したブロックスコープを持たず、switch 全体で1つのスコープを共有します。これはフォールスルーと合わせて、変数の扱いに注意が必要な一因になります。

まとめ

観点まとめ
何をする構文か1つの値を、複数のcaseで列挙した候補と順番に比較し、マッチした処理を実行する
主な用途単一の変数に対する多数の値の判定、権限レベルの判定など
比較方法ゆるい比較(==)。厳密な比較が必要ならmatchを検討する
breakの重要性省略するとフォールスルー(次のcaseへの素通り)が発生する
注意点ゆるい比較による予期しないマッチ、break忘れ、複雑な条件式はswitch(true)パターンが必要

switch は、同じ変数に対する多数の値の判定を簡潔に書けるPHPの伝統的な制御構造ですが、ゆるい比較とフォールスルーという2つの特性ゆえに、バグの温床になりやすいことでも知られています。PHP 8.0以降では、これらの問題を解消した match 式という選択肢もあるため、新規コードでは用途に応じて両者を使い分けることが推奨されます。

タイトルとURLをコピーしました