【PHP】match式とswitch文の違いと使い分け|PHP 8.0の新機能を実務レベルで理解する

PHP 8.0 で導入された match 式は、値の一致に応じて結果を返す構文です。本記事では match 式と従来の switch 文の違い、実務での使い分け、注意点を整理します。コード例は PHP 8.0 以降を前提とします。詳細は PHP 公式マニュアルの match の節 および switch の節 を参照してください。

はじめに

switch 文で分岐を書き続け、戻り値を代入するために break と一時変数を何度も繰り返していませんか。HTTP ステータスコードからメッセージを返す、注文ステータスから表示ラベルを決める——こうした「値に応じて結果を分岐する」処理は PHP コードのあちこちに現れます。従来は switch 文が定番でしたが、PHP 8.0 からは match 式も選択肢に入ります。

この記事で扱う範囲は次のとおりです。

  • switch 文の基本と、実務で困りやすい挙動
  • match 式の構文と switch との違い
  • 比較表と使い分けの目安
  • よくある失敗例と次に試すこと

式と文の違い(この記事の前提)

PHP では 式(expression)は評価すると値になる 一方、文(statement)は処理の単位 で、単体では値になりません。

  • 式の例 — match (...) { ... } は代入や return の右辺にそのまま書ける
  • 文の例 — switch (...) { ... } はブロック内で代入や break を書く
// 式: 評価結果をそのまま代入できる
$message = match ($status) { 200 => 'OK', default => 'Unknown' };

// 文: ブロック内で処理を書く(単体では値にならない)
switch ($status) {
    case 200:
        $message = 'OK';
        break;
    default:
        $message = 'Unknown';
        break;
}

以降の比較は、この「値を返せるか」「比較やフォールスルーの挙動」に焦点を当てます。

背景:switch 文のおさらい

switch は文であり、制御式の値と各 case の値を比較して分岐します。PHP の switch緩い比較(== で一致判定を行います(公式マニュアル)。

$status = 200;

switch ($status) {
    case 200:
        $message = 'OK';
        break;
    case 404:
        $message = 'Not Found';
        break;
    default:
        $message = 'Unknown';
        break;
}

break を書き忘れると、マッチした case のあと 次の case へ処理が流れ続ける(フォールスルー)点に注意が必要です。

$type = 'error';

switch ($type) {
    case 'error':
        $icon = 'error-icon';
        // break がないため fall-through
    case 'warning':
        $icon = 'warning-icon';
        break;
    default:
        $icon = 'info-icon';
        break;
}

// $type が 'error' のときも 'warning-icon' になる

意図的なフォールスルーもありますが、代入だけしたい場面ではバグの温床になりやすいです。レガシーの switch を保守するときは、break の有無をレビュー観点に含めるとよいでしょう。

match 式とは

match は式です。評価結果を返し、PHP 8.0.0 以降で利用できます(公式マニュアル)。

$status = 200;

$message = match ($status) {
    200 => 'OK',
    404 => 'Not Found',
    default => 'Unknown',
};

match の主な特徴は次のとおりです。

観点switchmatch
種類式(戻り値がある)
比較緩い比較(==厳密な比較(===
フォールスルーあり(break で止める)なし
網羅性default は任意どの分岐にも当てはまらないと UnhandledMatchError
複数条件の ORcase を並べるカンマ区切り(例: 'a', 'b' => ...

実装例

ステータスコードからメッセージを返す

次の例は、比較表の「式として戻り値がある」点を示します。先ほどの switch 版(代入と break でおおよそ 12 行)に対し、match 版は return match の 1 式にまとまります。

function httpMessage(int $status): string
{
    return match ($status) {
        200 => 'OK',
        201 => 'Created',
        404 => 'Not Found',
        500 => 'Internal Server Error',
        default => 'Unknown',
    };
}

default を省略すると、上記のいずれにも当てはまらない値(例: 418)で UnhandledMatchError がスローされ、未捕捉のままだと実行はそこで停止します。

try {
    httpMessage(418);
} catch (UnhandledMatchError $e) {
    // Unhandled match value of type int
}

想定外の値を早期に検知したい場合は default を付けない選択もあります。その場合は呼び出し側で例外を想定するか、上位で捕捉する設計にしてください。

複数の値を 1 つの分岐にまとめる

match では左辺にカンマ区切りで複数の条件式を書けます。論理 OR と同等です。

$role = 'editor';

$label = match ($role) {
    'admin', 'superuser' => '管理者',
    'editor', 'author'   => '編集者',
    default              => 'ゲスト',
};

switch でも case 'admin': case 'superuser': のように並べられますが、match では 1 行で同じ右辺を共有できます。

default で例外を投げる

PHP 8.0 以降、throw は式として扱えるため、match の分岐の右辺に直接書けます(マニュアルの defaultthrow する例)。

function daysInMonth(string $month): int
{
    $key = strtolower(substr($month, 0, 3));

    return match ($key) {
        'jan', 'mar', 'may', 'jul', 'aug', 'oct', 'dec' => 31,
        'apr', 'jun', 'sep', 'nov' => 30,
        'feb' => 28, // うるう年判定は省略
        default => throw new InvalidArgumentException("不正な月: {$month}"),
    };
}

厳密一致以外の条件:match (true)

通常の match は制御式と左辺の 値の一致 で分岐します。match (true) は、左辺の 条件式が真になるか で分岐する別パターンです。範囲判定などに使えます(公式マニュアル「厳密な一致チェックを行わずに match 式を使う」)。

$age = 20;

$group = match (true) {
    $age >= 65 => 'シニア',
    $age >= 25 => '成人',
    $age >= 18 => '若年成人',
    default    => '未成年',
};
// $age が 20 のときは '成人'($age >= 25 が先に真になる)

条件は 上から順に試される ため、順序が結果を変えます。$age >= 18 を最上段に置くと 20 歳は「若年成人」になります。if / elseif の連鎖を式としてまとめたいときに向きます。分岐が 4〜5 以上になる、または各 arm が複数行になる場合は、別関数への切り出しも検討してください。

使い分けの目安

match を検討しやすい場面

  • 分岐の結果を 変数や戻り値に直接代入 したい
  • 型と値の両方 を区別したい(=== ベース)
  • break 忘れによるフォールスルーバグを避けたい
  • 想定外の値を UnhandledMatchErrordefaultthrow で検知したい

switch が残りやすい場面

  • case複数行の副作用(ログ出力、複数の代入、早期 return なしの処理)をまとめて実行したい
  • 意図的なフォールスルー で段階的に処理を重ねたい(レガシーコードで見かけるパターン)
  • PHP 7 系など 8.0 未満 の実行環境をサポートする必要がある

新規コードで「値から結果を返すだけ」の分岐なら、代入と break の繰り返しは減り、match ならより短く書ける場合が多いです。一方、手続き的に処理を積み上げる switch の書き方が読みやすいケースもあります。

実務での注意点

比較の違いは型の罠につながる

switch の緩い比較では、型が異なっても一致することがあります。

$value = '0';

switch ($value) {
    case 0:
        // '0' == 0 は true のため、ここに入る
        $result = 'zero as int';
        break;
    default:
        $result = 'other';
        break;
}

match では '0' === 0false なので、上記の case 0 には入りません。文字列と数値が混在する API レスポンスなどでは、どちらを使うかで結果が変わる点に注意してください。

すべての分岐が評価されるわけではない

match は最初に一致した分岐だけを評価します。マニュアルにあるとおり、一致が確定するまで左辺の条件式が順に試され、マッチした時点で右辺が評価されます。一致しなかった分岐の右辺(関数呼び出しなど)は実行されません。

preg_match などはそのままでは使えない

preg_match の戻り値は bool ではなく、一致時 1・不一致時 0・エラー時 false です。match の左辺は === で比較するため、if (preg_match(...)) のような真偽値判定をそのまま移植できません。

$text = 'hello';

// そのままでは意図どおり動かない
// match (preg_match('/hello/', $text)) { 1 => 'hit', 0 => 'miss' };

$result = match (true) {
    preg_match('/hello/', $text) === 1 => 'hit',
    preg_match('/hello/', $text) === 0 => 'miss',
    default => 'error',
};

match (true) と明示的な比較を組み合わせるか、事前に bool へ正規化してください。

次に試すこと

  1. 候補を探す — 各 case が代入と break だけ、戻り値用の一時変数がある switch を 1 件選ぶ。
  2. match に書き換える — 制御式と case の値を左辺、代入先の値を右辺へ移す。想定外の入力は default(文字列フォールバック)、throw、省略(UnhandledMatchError)のどれにするか決める。
  3. 実行して確認する — 既存の PHPUnit テストや php -r で、代表値と境界値(default に入る値)を実行する。
  4. 型の罠を確認する — 文字列 "0" と数値 0 など、===== で挙動が変わる入力はないか見る。
  5. レビュー観点を足すmatch 移行の PR では、網羅性(default の有無)、入力の型、match (true) の条件順序をチェックする。

まとめ

  • match は PHP 8.0 以降の式で、分岐結果を直接返せる。
  • switch は緩い比較・フォールスルーあり、match は厳密比較・フォールスルーなし。
  • 値からラベルやメッセージを返す処理は match で短く書けることが多い。
  • 型の違いで switchmatch の結果が変わるため、入力の型を意識する。

Source

PHPPHP

Posted by 千原 耕司