[PHP]matchとは?switchの弱点を解消した厳密な分岐式を徹底解説

PHP

はじめに

前回の記事では、switch が抱える「ゆるい比較による予期しないマッチ」と「break 忘れによるフォールスルー」という2つの有名な落とし穴を解説しました。今回取り上げる match は、PHP 8.0で満を持して導入された、これらの問題を根本から解消する新しい分岐構文です。

match は、switch と似た「1つの値を複数の候補と比較する」という目的を持ちながら、厳密な比較(===相当)を行い、break が不要で、**値を返す「式」**であるという、より安全でモダンな設計になっています。本記事では基本的な使い方から、switch との明確な違い、実践的な活用例まで詳しく解説します。


構文概要

項目内容
構文名match
種別式(switchとは異なり、値を返す)
基本構文match ($value) { 候補1, 候補2 => 結果1, default => 結果2 }
比較方法厳密な比較(===相当)
フォールスルーなし(各アームは独立しており、breakは不要かつ書けない)
マッチしない場合default がなければ UnhandledMatchError がスローされる
対応バージョンPHP 8.0.0以降

switchとmatchの決定的な違い(イメージ図)

  【switch - ゆるい比較・文】
  switch ($status) {
      case 0:        ← "0"や""などもゆるくマッチしうる
          $result = '無効';
          break;      ← 書き忘れるとフォールスルー
      default:
          $result = '不明';
  }

  【match - 厳密な比較・式】
  $result = match ($status) {
      0       => '無効',   ← 型も含めて厳密に一致する場合のみマッチ
      1, 2    => '有効',   ← カンマで複数候補をOR条件のように指定
      default => '不明',
  };
  ★この記事の対象
  (breakは不要。各アームは独立しており、代入もそのまま可能)

ポイントは、match が値を返す「式」であるため、直接変数への代入や、関数の戻り値、別の式の中への埋め込みができるという点です。これにより、switch では必要だった「各ケースで変数に代入してからbreakする」という冗長な書き方が不要になります。


実践サンプル7選

例1:基本的な使い方

<?php

function describeDayNumber(int $day): string
{
    return match ($day) {
        1 => '月曜日',
        2 => '火曜日',
        3 => '水曜日',
        default => '不明',
    };
}

echo describeDayNumber(2) . PHP_EOL; // "火曜日"

例2:カンマ区切りで複数の候補をまとめる

<?php

function isWeekend(string $day): bool
{
    return match ($day) {
        // カンマで区切ることで、switchの"caseの連続"と同じ効果が得られる
        'Saturday', 'Sunday' => true,
        default => false,
    };
}

var_dump(isWeekend('Saturday')); // true
var_dump(isWeekend('Monday'));   // false

例3:厳密な比較によってswitchの問題が解消されることを確認する

<?php

function safeMatch(mixed $value): string
{
    return match ($value) {
        // matchは型も含めて厳密に比較するため、
        // switchで問題になっていた意図しないマッチが起こらない
        0 => '整数の0です',
        '0' => '文字列の"0"です',
        false => '真偽値のfalseです',
        default => 'その他',
    };
}

echo safeMatch(0) . PHP_EOL;     // "整数の0です"
echo safeMatch('0') . PHP_EOL;   // "文字列の"0"です"(switchなら誤ってマッチしていた可能性)
echo safeMatch(false) . PHP_EOL; // "真偽値のfalseです"

例4:match(true)パターンによる範囲条件の表現

<?php

function gradeFromScore(int $score): string
{
    /**
     * switch(true)と同様、match(true)とすることで
     * 各アームに任意の真偽式を書ける
     */
    return match (true) {
        $score >= 90 => 'A',
        $score >= 70 => 'B',
        $score >= 50 => 'C',
        default => 'D',
    };
}

echo gradeFromScore(75) . PHP_EOL; // "B"

例5:defaultなしでマッチしない場合に例外をスローさせる

<?php

function httpMethodToAction(string $method): string
{
    // defaultを意図的に書かないことで、
    // 想定外の値が来た場合に明示的にエラーとして検知する
    return match ($method) {
        'GET' => '取得',
        'POST' => '作成',
        'PUT' => '更新',
        'DELETE' => '削除',
    };
}

try {
    echo httpMethodToAction('PATCH') . PHP_EOL;
} catch (\UnhandledMatchError $e) {
    echo 'エラー: ' . $e->getMessage() . PHP_EOL;
}

例6:式の中への直接埋め込み

<?php

class Order
{
    public function __construct(private string $status)
    {
    }

    public function getStatusBadgeClass(): string
    {
        // match式は値を返すため、戻り値にそのまま使える
        return 'badge-' . match ($this->status) {
            'pending'   => 'warning',
            'completed' => 'success',
            'cancelled' => 'danger',
            default     => 'secondary',
        };
    }
}

$order = new Order('completed');
echo $order->getStatusBadgeClass() . PHP_EOL; // "badge-success"

例7:switchからの書き換えで見るコード量の削減

<?php

// switch版: 変数への代入とbreakが必要で冗長になりがち
function priorityLabelWithSwitch(int $level): string
{
    switch ($level) {
        case 1:
            $label = '低';
            break;
        case 2:
            $label = '中';
            break;
        case 3:
            $label = '高';
            break;
        default:
            $label = '不明';
    }
    return $label;
}

// match版: breakなしで簡潔に、かつ直接returnできる
function priorityLabelWithMatch(int $level): string
{
    return match ($level) {
        1 => '低',
        2 => '中',
        3 => '高',
        default => '不明',
    };
}

echo priorityLabelWithMatch(2) . PHP_EOL;

関連機能との比較

機能役割matchとの違い
match値に基づいて式を返す、厳密な比較を行う分岐本記事の対象。PHP 8.0以降。===相当の比較で、値を返す
switch1つの値を複数のケースと比較する「文」ゆるい比較(==)を行い、フォールスルーのリスクがある(前回記事を参照)
if / elseif任意の条件式を順番に評価するmatch(true)と似た柔軟性を持つが、matchの方が「値を返す式」として簡潔に書ける場合がある
三項演算子(?:)条件に応じて2つの値のどちらかを返す式候補が2つだけのシンプルな場合に適しており、matchは3つ以上の候補がある場合に適する
Null合体演算子(??)値がnullの場合にデフォルト値を使うnull判定に特化した、より限定的な式

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

  1. 厳密な比較(===)であることを前提に値を揃える match は型まで含めて厳密に比較するため、'1'(文字列)と 1(整数)は別の値として扱われます。switch からの移行時、ゆるい比較に依存していたコードがあると、挙動が変わってしまう可能性があるため注意が必要です(例3を参照)。
  2. defaultがない状態でマッチしない値が来ると例外になる これは switch との大きな違いです。switch ではマッチする case がなくても単に何も実行されず終わりますが、match では default がない場合、マッチしないと UnhandledMatchError という例外がスローされます。これは意図的な「想定外の値を検知する」設計として活用できますが(例5を参照)、予期せず例外が発生するリスクとしても理解しておく必要があります。
  3. アーム内に複数の文を書くことはできない match の各アーム(条件 => 結果)は、単一の式のみを受け付けます。複数行にわたる処理を書きたい場合は、無名関数やメソッド呼び出しに処理を切り出す必要があります。
  4. breakを書こうとして構文エラーになる switch に慣れていると、つい match の中でも break; を書いてしまいがちですが、match には break という概念自体がなく、書くと構文エラーになります。
  5. 複雑な条件判定にはmatch(true)パターンの可読性を考慮する match(true) は強力なパターンですが、多用しすぎると「match なのに値の比較をしていない」という点で、コードを読む人を混乱させる可能性があります。単純な範囲条件であれば問題ありませんが、複雑な論理式が並ぶ場合は if–elseif の方が読みやすいこともあります(例4を参照)。

まとめ

観点まとめ
何をする構文か値を複数の候補と厳密に比較し、マッチした結果を返す「式」
主な用途単一の変数に対する値の判定、ステータスに応じたラベルやクラス名の決定
比較方法厳密な比較(===相当)。switchのゆるい比較の問題を解消
switchとの違いbreak不要・フォールスルーなし・式として値を返せる・defaultなしで例外になる
注意点switchからの移行時の型の違いによる挙動変化、UnhandledMatchErrorのリスク

match は、switch が長年抱えてきた「ゆるい比較」と「フォールスルー」という2つの代表的な問題点を解消した、PHP 8.0以降のモダンな分岐構文です。単一の値に対する判定処理では、新規コードにおいて switch よりも match を優先して選ぶことが、より安全で簡潔なコードにつながります。

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