- PHP
PHP match式の使い方|switchとの違いとif elseifの使い分け
PHP の match 式とは、値を厳密な比較(===)で照合し、一致した腕(アーム)の結果を「値として返す」条件分岐です。 PHP 8.0 で追加されました。switch と似ていますが、break が不要で、どの条件にも一致しないときは UnhandledMatchError という例外で知らせてくれる点が大きく違います。
この記事では、同じ条件分岐を if elseif・switch・match の3通りで書いて並べ、比較方法・break・戻り値・該当なし時の挙動を表で整理します。"0" と 0 を渡したときに switch と match で結果が変わる例、default を書かずに UnhandledMatchError が出る例は、PHP 8.4.21 で実際に動かした出力をそのまま載せています。match(true) での範囲判定、複数値のカンマ指定、enum との組み合わせ、複数行の処理を書きたいときの対処まで、コピーして動かせる形でまとめました。
if elseif・switch・match の違い早見表
| 比べる点 | if / elseif | switch | match |
|---|---|---|---|
| 種類 | 文 | 文 | 式(値を返す) |
| 使える PHP | すべて | すべて | 8.0 以降 |
| 比較方法 | 自分で書く(=== も == も >= も可) |
緩い比較(==) |
厳密な比較(===) |
| break | 不要 | 必要(書かないと次の case へ進む) | 不要(次の腕へは進まない) |
| 戻り値 | なし(変数に代入するか return する) | なし(同左) | あり(そのまま代入・return できる) |
| 複数の値をまとめる | || でつなぐ |
case を並べる | カンマで区切る |
| どれにも一致しないとき | 何も起きない(else を書けば実行) | 何も起きない(default を書けば実行) | UnhandledMatchError(default を書けば実行) |
| 1つの分岐に書ける量 | 複数行 | 複数行 | 式を1つ |
| 範囲や複雑な条件 | 得意 | switch (true) で可能 |
match (true) で可能 |
| 向いている場面 | 条件がそれぞれ違う形のとき | 1つの分岐で複数の処理をしたいとき | 値に応じて値を決めたいとき |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「PHP match式の使い方|switchとの違いとif elseifの使い分け」
同じ条件分岐を3通りで書いて比べる
記事の状態(status)を表す文字列から、画面に出すラベルを決める処理を、3つの書き方で用意しました。そのまま three.php として保存して実行できます。
<?php
declare(strict_types=1);
function labelByIf(string $status): string
{
if ($status === 'draft') {
return '下書き';
} elseif ($status === 'review' || $status === 'pending') {
return '確認待ち';
} elseif ($status === 'published') {
return '公開中';
} else {
return '不明';
}
}
function labelBySwitch(string $status): string
{
switch ($status) {
case 'draft':
$label = '下書き';
break;
case 'review':
case 'pending':
$label = '確認待ち';
break;
case 'published':
$label = '公開中';
break;
default:
$label = '不明';
}
return $label;
}
function labelByMatch(string $status): string
{
return match ($status) {
'draft' => '下書き',
'review', 'pending' => '確認待ち',
'published' => '公開中',
default => '不明',
};
}
foreach (['draft', 'pending', 'published', 'archived'] as $s) {
printf("%-10s if=%s switch=%s match=%s\n", $s, labelByIf($s), labelBySwitch($s), labelByMatch($s));
}
php three.php
draft if=下書き switch=下書き match=下書き
pending if=確認待ち switch=確認待ち match=確認待ち
published if=公開中 switch=公開中 match=公開中
archived if=不明 switch=不明 match=不明
結果は3つとも同じです。違いは書き方に出ています。
| 見るところ | if / elseif | switch | match |
|---|---|---|---|
| 関数本体の行数 | 9行 | 15行 | 6行 |
$status を書く回数 |
4回 | 1回 | 1回 |
| 結果の受け取り方 | 分岐ごとに return | 変数に入れてから return | 式の結果をそのまま return |
| 複数の値 | || |
case を2行 | 'review', 'pending' |
「1つの値を見て、対応する値を決める」処理は、match がいちばん短く書けます。
match 式の基本の書き方
$結果 = match (調べる値) {
条件1 => 返す値1,
条件2, 条件3 => 返す値2,
default => どれにも一致しないときの値,
};
| 部品 | 説明 |
|---|---|
match (調べる値) |
かっこの中の値を、上から順に各条件と === で比べる |
条件 => 返す値 |
この1行を「腕(アーム)」と呼ぶ。一致したら => の右側の式を評価して返す |
条件2, 条件3 |
カンマで区切ると「どれかに一致したら」という意味になる |
default |
どの条件にも一致しないときに選ばれる。書けるのは1つだけ |
末尾の ; |
match は式なので、文として終わらせるセミコロンが必要 |
最後の腕のあとの , |
付けても付けなくてもよい |
式なので、どこにでも書ける
match は値を返すので、代入の右辺だけでなく、return・関数の引数・文字列連結の途中にも直接書けます。
<?php
$code = 404;
echo 'HTTP ' . $code . ' ' . match ($code) {
200, 201, 204 => 'OK系',
301, 302 => 'リダイレクト',
404 => 'Not Found',
default => 'その他',
}, PHP_EOL;
HTTP 404 Not Found
上から順に評価し、最初に一致した腕だけが動く
条件側に関数呼び出しを書いた場合、一致した時点でそれより下は評価されません。
<?php
function check(string $name, bool $r): bool { echo " {$name} を評価\n"; return $r; }
$r = match (true) {
check('A', false) => 'A',
check('B', true) => 'B',
check('C', true) => 'C',
};
echo "結果: {$r}\n";
A を評価
B を評価
結果: B
C は評価されていません。条件は「先に書いたものが優先」なので、範囲判定では並べる順番が結果を左右します(後述)。
条件側には変数や式も書ける
<?php
$min = 1; $max = 5;
$n = 5;
echo match ($n) { $min => '最小', $max => '最大', default => '中間' }, PHP_EOL;
最大
switch の case と同じく、条件はリテラルに限られません。
switch と match の違い1:比較方法(== と ===)
switch は緩い比較(==)、match は厳密な比較(===)で照合します。型まで見るかどうかが違いです。
"0" と 0 で結果が変わる例
次のコードを zero.php として保存して実行します。case と腕の並びは同じです。
<?php
function bySwitch(mixed $v): string
{
switch ($v) {
case 0:
return '数値の0';
case '0':
return '文字列の"0"';
case null:
return 'null';
default:
return 'その他';
}
}
function byMatch(mixed $v): string
{
return match ($v) {
0 => '数値の0',
'0' => '文字列の"0"',
null => 'null',
default => 'その他',
};
}
$inputs = ['0', 0, '00', 0.0, '', null, false, 'abc', '1e1'];
foreach ($inputs as $v) {
printf("%-8s switch=%-14s match=%s\n", var_export($v, true), bySwitch($v), byMatch($v));
}
'0' switch=数値の0 match=文字列の"0"
0 switch=数値の0 match=数値の0
'00' switch=数値の0 match=その他
0.0 switch=数値の0 match=その他
'' switch=null match=その他
NULL switch=数値の0 match=null
false switch=数値の0 match=その他
'abc' switch=その他 match=その他
'1e1' switch=その他 match=その他
| 渡した値 | switch の結果 | match の結果 | なぜ違うか |
|---|---|---|---|
'0'(文字列) |
数値の0 | 文字列の"0" | '0' == 0 は true なので、switch は先に書いた case 0 に入る |
0(整数) |
数値の0 | 数値の0 | どちらも一致 |
'00' |
数値の0 | その他 | 数値として読める文字列は数値に変換して比べられる |
0.0(浮動小数点) |
数値の0 | その他 | 0.0 == 0 は true、0.0 === 0 は false |
''(空文字) |
null | その他 | '' == null は true |
null |
数値の0 | null | null == 0 は true なので case 0 に入る |
false |
数値の0 | その他 | false == 0 は true |
'abc' |
その他 | その他 | PHP 8 では数値でない文字列と 0 は等しくない |
switch では null や false を渡しても「数値の0」と判定され、case '0' や case null まで届きません。 match は型も含めて一致したものだけを選ぶので、書いたとおりの腕に入ります。
文字列同士でも switch は数値として比べることがある
両方が数値として読める文字列の場合、switch は数値に変換して比べます。
<?php
function sw(string $v): string { switch ($v) { case '1.0': return "case '1.0'"; case '1': return "case '1'"; default: return 'default'; } }
function ma(string $v): string { return match ($v) { '1.0' => "arm '1.0'", '1' => "arm '1'", default => 'default' }; }
echo sw('1'), ' / ', ma('1'), PHP_EOL;
echo sw('01'), ' / ', ma('01'), PHP_EOL;
case '1.0' / arm '1'
case '1.0' / default
'1' を渡すと、switch は '1' == '1.0'(数値として等しい)で最初の case に入りました。郵便番号や会員番号のように「数字だけの文字列」を扱うときは、match か === を使った if にすると意図どおりに分かれます。
厳密な比較ならではの気をつけどころ
match は型が違うと一致しません。フォームや URL から届く値は文字列なので、整数の腕とは一致しない点に気をつけます。
<?php
$id = '5';
echo match ($id) { 5 => 'int', '5' => 'string' }, PHP_EOL;
var_dump(match (1.0) { 1 => 'int 1', default => 'default' });
string
string(7) "default"
| 入力 | 対処 |
|---|---|
$_GET['page'] などの数字の文字列 |
(int) で変換してから match に渡す、または腕を文字列で書く |
浮動小数点の 1.0 |
整数の 1 とは一致しない。腕を 1.0 と書くか、match (true) で範囲として判定する |
| 大文字小文字が混ざる文字列 | match (strtolower(trim($v))) のように整えてから渡す |
switch と match の違い2:break とフォールスルー
switch は、break を書かないと次の case の処理まで続けて実行します(フォールスルー)。match は一致した腕だけを実行します。
<?php
$rank = 'gold';
switch ($rank) {
case 'gold':
echo "送料無料\n";
case 'silver':
echo "ポイント2倍\n";
break;
default:
echo "特典なし\n";
}
echo match ($rank) {
'gold' => "送料無料\n",
'silver' => "ポイント2倍\n",
default => "特典なし\n",
};
送料無料
ポイント2倍
送料無料
switch では case 'gold' に break がないため、「ポイント2倍」まで出力されました。意図してそう書くこともありますが、break の書き忘れと見分けにくいのが switch の特徴です。match には break もフォールスルーもないので、この種の取り違えが起きません。
「gold と silver で同じ処理をしたい」ときは、match ではカンマで並べます。
$benefit = match ($rank) {
'gold', 'silver' => '送料無料',
default => '特典なし',
};
なお、ループの中の switch で continue を書くと break と同じ動きになり、PHP が警告を出します。
PHP Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in switch_continue.php on line 5
switch と match の違い3:戻り値
switch は文なので、結果を使うには変数に代入するか、各 case で return します。match は式なので、結果をそのまま受け取れます。
| 書き方 | switch | match |
|---|---|---|
| 変数に入れる | 各 case で $label = ...; break; |
$label = match (...) { ... }; |
| 関数から返す | 各 case で return ...; |
return match (...) { ... }; |
| 代入し忘れたとき | 変数が未定義のまま進む | 構文上、必ず値が決まる |
switch で default を書かず、どの case にも一致しなかった場合を実行してみます。
<?php
$status = 'archived';
switch ($status) {
case 'draft': $label = '下書き'; break;
case 'published': $label = '公開中'; break;
}
var_dump($label ?? null);
echo $label;
NULL
PHP Warning: Undefined variable $label in nodupe.php on line 9
switch 自体は何も知らせず、あとで変数を使ったところで初めて警告が出ます。match では次の節のとおり、その場で例外になります。
switch と match の違い4:該当なし時の挙動(UnhandledMatchError)
default を書かずに一致しない値を渡す
<?php
$status = 'archived';
$label = match ($status) {
'draft' => '下書き',
'published' => '公開中',
};
echo $label, PHP_EOL;
PHP Fatal error: Uncaught UnhandledMatchError: Unhandled match case of type string in unhandled.php:4
Stack trace:
#0 {main}
thrown in unhandled.php on line 4
どの腕にも一致せず default もないとき、match は UnhandledMatchError を投げて処理を止めます。 行番号つきで知らせてくれるので、条件の書きもれにすぐ気づけます。
エラーメッセージは php.ini の設定で変わる
上の出力では値が of type string と型だけの表示になっています。これは検証環境の php.ini が zend.exception_ignore_args = On(本番向け php.ini の値)だったためです。設定を変えて同じコードを動かすと、メッセージが変わりました。
| 実行方法 | 文字列 'archived' を渡したときのメッセージ |
|---|---|
検証環境の php.ini のまま(zend.exception_ignore_args=On) |
Unhandled match case of type string |
php -d zend.exception_ignore_args=0(文字列の表示長は php.ini の 0 のまま) |
Unhandled match case '...' |
php -n(php.ini を読まず、PHP の既定値で実行) |
Unhandled match case 'archived' |
php -n で、いろいろな型の値を渡した結果です。
UnhandledMatchError: Unhandled match case 'archived'
UnhandledMatchError: Unhandled match case 5
UnhandledMatchError: Unhandled match case 3.5
UnhandledMatchError: Unhandled match case NULL
UnhandledMatchError: Unhandled match case true
UnhandledMatchError: Unhandled match case of type array
UnhandledMatchError: Unhandled match case of type stdClass
UnhandledMatchError: Unhandled match case 'xxxxxxxxxxxxxxx...'
30文字の文字列は15文字で切られて表示されました。メッセージに値が出ないときは、php.ini の zend.exception_ignore_args を確認すると原因がわかります。本番向けの設定では、例外メッセージに入力値を出さないようになっています。
UnhandledMatchError は Error の仲間
var_dump((new UnhandledMatchError) instanceof Error, (new UnhandledMatchError) instanceof Exception);
bool(true)
bool(false)
UnhandledMatchError は Exception ではなく Error を継承しています。catch (Exception $e) では受け取れないので、捕まえるなら catch (\UnhandledMatchError $e) か catch (\Throwable $e) と書きます。
3つの対処と選び方
| 対処 | 書き方 | 向いている場面 |
|---|---|---|
| default で既定値を返す | default => '不明', |
想定外の値でも画面を出し続けたい |
| default で意味のある例外を投げる | default => throw new InvalidArgumentException(...) |
想定外の値は呼び出し側の誤りとして知らせたい |
| あえて default を書かない | (何も書かない) | enum のように取りうる値が決まっていて、書きもれを見つけたい |
2つ目の例です。throw は PHP 8.0 から式として書けるので、腕にそのまま置けます。
<?php
declare(strict_types=1);
function parsePriority(string $p): int
{
return match (strtolower(trim($p))) {
'high', 'h' => 3,
'middle', 'm' => 2,
'low', 'l' => 1,
default => throw new InvalidArgumentException("優先度が不明です: {$p}"),
};
}
echo parsePriority(' High '), PHP_EOL;
try {
parsePriority('urgent');
} catch (InvalidArgumentException $e) {
echo $e->getMessage(), PHP_EOL;
}
3
優先度が不明です: urgent
match(true) で範囲判定をする
match は === で照合するため、>= のような範囲の条件はそのままでは書けません。そこで 調べる値を true にし、各腕に条件式を書く方法を使います。「最初に true になった腕」が選ばれます。
<?php
declare(strict_types=1);
function shippingFee(int $total): int
{
return match (true) {
$total >= 10000 => 0,
$total >= 5000 => 300,
$total >= 1 => 600,
default => throw new InvalidArgumentException("合計金額が不正です: {$total}"),
};
}
foreach ([12000, 10000, 9999, 5000, 800] as $t) {
printf("%6d円 -> 送料 %d円\n", $t, shippingFee($t));
}
try {
shippingFee(0);
} catch (InvalidArgumentException $e) {
echo $e->getMessage(), PHP_EOL;
}
12000円 -> 送料 0円
10000円 -> 送料 0円
9999円 -> 送料 300円
5000円 -> 送料 300円
800円 -> 送料 600円
合計金額が不正です: 0
範囲判定で押さえる2つのポイント
| ポイント | 理由 |
|---|---|
| 条件は厳しいものから順に書く | 上から評価されるため。$total >= 1 を先頭に書くと、すべてそこに入る |
| 各腕の条件は bool を返す式にする | true と === で比べるため。1 や "yes" は一致しない |
2つ目は、bool 以外を返す関数でつまずきやすいところです。preg_match() は一致したとき整数の 1 を返すので、そのままでは true と一致しません。
<?php
$text = 'order-123';
try {
echo match (true) {
preg_match('/^order-\d+$/', $text) => "注文番号\n",
str_starts_with($text, 'user-') => "会員番号\n",
};
} catch (\UnhandledMatchError $e) {
echo get_class($e), ': ', $e->getMessage(), PHP_EOL;
}
// 対処: === 1 で bool にする
echo match (true) {
preg_match('/^order-\d+$/', $text) === 1 => "注文番号\n",
str_starts_with($text, 'user-') => "会員番号\n",
default => "不明\n",
};
UnhandledMatchError: Unhandled match case of type bool
注文番号
| bool 以外を返す関数の例 | 戻り値 | match(true) での書き方 |
|---|---|---|
preg_match() |
1 / 0 / false | preg_match(...) === 1 |
strpos() |
位置(int)/ false | strpos(...) !== false、または str_contains() を使う |
count() |
件数(int) | count($a) > 0 |
in_array()、str_contains()、is_int() など |
bool | そのまま書ける |
型ごとに分岐する
match (true) は、is_int() や instanceof と組み合わせると型ごとの分岐もすっきり書けます。
<?php
declare(strict_types=1);
function describe(mixed $v): string
{
return match (true) {
is_int($v) => "整数 {$v}",
is_string($v) => "文字列 '{$v}'",
$v instanceof DateTimeInterface => '日時 ' . $v->format('Y-m-d'),
is_array($v) => '配列 ' . count($v) . '件',
default => get_debug_type($v),
};
}
foreach ([42, 'abc', new DateTimeImmutable('2026-10-03'), [1, 2, 3], 1.5, null] as $v) {
echo describe($v), PHP_EOL;
}
整数 42
文字列 'abc'
日時 2026-10-03
配列 3件
float
null
enum と match を組み合わせる
PHP 8.1 で追加された enum(列挙型)は、取りうる値があらかじめ決まっているため、match との相性がとてもよい組み合わせです。
<?php
declare(strict_types=1);
enum TaskStatus: string
{
case Todo = 'todo';
case Doing = 'doing';
case Done = 'done';
public function label(): string
{
return match ($this) {
self::Todo => '未着手',
self::Doing => '進行中',
self::Done => '完了',
};
}
public function isOpen(): bool
{
return match ($this) {
self::Todo, self::Doing => true,
self::Done => false,
};
}
}
foreach (TaskStatus::cases() as $s) {
printf("%-5s %s open=%s\n", $s->value, $s->label(), var_export($s->isOpen(), true));
}
$fromDb = TaskStatus::from('doing');
echo $fromDb->label(), PHP_EOL;
var_dump(TaskStatus::tryFrom('archived'));
todo 未着手 open=true
doing 進行中 open=true
done 完了 open=false
進行中
NULL
enum では default を書かないのがおすすめ
すべてのケースを腕に書き、default をあえて省くと、あとで enum にケースを足したときに、対応していない match が UnhandledMatchError で見つかります。Archived を追加し、label 側を直し忘れた状態を再現しました。
<?php
enum TaskStatus: string
{
case Todo = 'todo';
case Doing = 'doing';
case Done = 'done';
case Archived = 'archived'; // 後から追加したケース
}
function label(TaskStatus $s): string
{
return match ($s) {
TaskStatus::Todo => '未着手',
TaskStatus::Doing => '進行中',
TaskStatus::Done => '完了',
};
}
echo label(TaskStatus::Archived), PHP_EOL;
PHP Fatal error: Uncaught UnhandledMatchError: Unhandled match case of type TaskStatus in enum_missing.php:15
Stack trace:
#0 enum_missing.php(19): label()
#1 {main}
thrown in enum_missing.php on line 15
zend.exception_ignore_args=0 で実行すると、メッセージは Unhandled match case TaskStatus::Archived となり、どのケースが足りないかまで表示されました。default を書いていると「不明」などの既定値で静かに通ってしまうので、ケースが決まっている enum では default なしのほうが書きもれに気づけます。テストで全ケースを一度ずつ通しておくと、さらに確実です。
外から来た文字列は tryFrom で enum にしてから
| メソッド | 一致しない値を渡したとき | 使いどころ |
|---|---|---|
TaskStatus::from('x') |
ValueError を投げる | 必ず正しい値が入っている前提のとき(DB の値など) |
TaskStatus::tryFrom('x') |
null を返す | フォームや URL の値など、想定外がありうるとき |
tryFrom() で enum に変換し、null なら入力エラーとして扱い、以降は enum だけを match する、という流れにすると分岐が整理されます。
複数行の処理を書きたいときの対処
match の腕に書けるのは式を1つだけです。波かっこでブロックを書くと構文エラーになります。
<?php
$x = match (1) {
1 => { echo 'a'; return 1; },
};
PHP Parse error: syntax error, unexpected token "{" in err_block.php on line 3
複数行の処理が必要なときは、次の3つの方法があります。
<?php
declare(strict_types=1);
function notifyDone(string $title): string
{
$message = "「{$title}」が完了しました";
$message .= '(' . date('Y') . '年)'; // 複数行の処理は関数にまとめる
return $message;
}
$status = 'done';
$title = '棚卸し';
// 1. 関数・メソッドに切り出す
$result = match ($status) {
'todo' => '未着手です',
'done' => notifyDone($title),
default => '状態不明',
};
echo $result, PHP_EOL;
// 2. その場で無名関数を呼ぶ
$result = match ($status) {
'done' => (function () use ($title) {
$upper = mb_strtoupper($title);
return "完了: {$upper}";
})(),
default => '状態不明',
};
echo $result, PHP_EOL;
// 3. 関数を返して後で呼ぶ
$handler = match ($status) {
'todo' => fn(string $t) => "{$t} を開始します",
'done' => fn(string $t) => "{$t} を記録しました",
default => fn(string $t) => "{$t} は対象外です",
};
echo $handler($title), PHP_EOL;
「棚卸し」が完了しました(2026年)
完了: 棚卸し
棚卸し を記録しました
| 方法 | 読みやすさ | 向いている場面 |
|---|---|---|
| 1. 関数・メソッドに切り出す | 高い | 基本はこれ。処理に名前が付き、テストもしやすい |
| 2. その場で無名関数を呼ぶ | 中 | 数行だけで、ほかから呼ばない処理 |
| 3. 関数を返して後で呼ぶ | 中 | 「どの処理をするか」を先に決め、実行は後にしたい |
| switch や if に戻す | 高い | 分岐ごとに行う処理の形がばらばらなとき |
各分岐で何行も処理をするなら、無理に match へ押し込まず switch や if elseif を選ぶのも立派な判断です。match は「値を決める」場面に絞ると、コードが読みやすくまとまります。
戻り値を使わない match
match を文のように使い、結果を捨てることもできます。この場合も末尾のセミコロンは必要です。
<?php
$log = [];
$action = 'save';
match ($action) {
'save' => $log[] = '保存しました',
'delete' => $log[] = '削除しました',
default => $log[] = '何もしません',
};
print_r($log);
Array
(
[0] => 保存しました
)
よく見るエラーと直し方
| エラーメッセージ(PHP 8.4.21 で再現) | 原因 | 直し方 |
|---|---|---|
Uncaught UnhandledMatchError: Unhandled match case ... |
どの腕にも一致せず default がない | 腕を足す/default を書く/渡す値の型を確認 |
Parse error: syntax error, unexpected token "{" |
腕にブロックを書いた | 関数に切り出す |
Parse error: syntax error, unexpected token "echo"(match の次の行) |
match の閉じかっこのあとの ; がない |
}; で終える |
Fatal error: Match expressions may only contain one default arm |
default を2つ書いた | 1つにまとめる |
Unhandled match case of type bool(match (true) で) |
腕の条件が bool 以外(1 など)を返している | === 1 や !== false で bool にする |
セミコロンを忘れたときの出力です。エラーの行番号は match の次の行を指すので、1つ上の } を確認します。
PHP Parse error: syntax error, unexpected token "echo" in err_semi.php on line 5
直す前と後:数字の文字列を渡して UnhandledMatchError
// 直す前:$_GET の値は文字列なので、整数の腕と一致しない
$page = $_GET['page'] ?? '1';
$title = match ($page) {
1 => '1ページ目',
2 => '2ページ目',
};
// 直した後:整数に変換し、想定外の値は default で受ける
$page = (int) ($_GET['page'] ?? 1);
$title = match ($page) {
1 => '1ページ目',
2 => '2ページ目',
default => 'ページが見つかりません',
};
(この2つは Web サーバー経由の $_GET を使うため未実行です。型が違うと一致しないこと自体は、前の節の $id = '5' の例で確認しています。)
if elseif・switch・match の使い分け
| こんなとき | おすすめ | 理由 |
|---|---|---|
| 1つの値に応じて、返す値を決めたい | match | 短く書け、書きもれは例外でわかる |
| enum のケースごとに値を決めたい | match(default なし) | ケース追加時に未対応の箇所が見つかる |
| 金額や点数などの範囲で値を決めたい | match (true) | 条件と結果が表のように並ぶ |
| 条件ごとに見る変数が違う、条件が長い | if / elseif | 自由に条件を書ける |
| 分岐ごとに複数の処理をしたい | switch または if / elseif | ブロックに何行でも書ける |
| 上の case の処理に続けて下の case も実行したい | switch | フォールスルーを使える |
緩い比較(==)で分けたい |
switch または if | match は常に === |
| キーと値の単純な対応だけ | 配列 | $map[$key] ?? '既定値' で済む |
| PHP 7.4 以前でも動かす必要がある | switch / if | match は 8.0 以降 |
最後の「配列」は、条件も結果も固定の値のときに使える書き方です。
<?php
declare(strict_types=1);
const LABELS = ['draft' => '下書き', 'published' => '公開中'];
echo LABELS['archived'] ?? '不明', PHP_EOL;
不明
switch を match に書き換えるときのチェックリスト
| 確認すること | 理由 |
|---|---|
| 渡す値と腕の型がそろっているか | == から === に変わるため、'1' と 1 は別物になる |
| break を省いてフォールスルーさせている case がないか | match では次の腕へ進まない。カンマでまとめるか処理を分ける |
| default がない switch ではないか | match では UnhandledMatchError になる。default を足すか、全パターンを腕に書く |
| 各 case が複数行の処理をしていないか | 腕は式1つ。関数へ切り出すか、switch のままにする |
| case の中で return・continue を使っていないか | 腕には書けない。return match (...) の形に組み替える |
よくある質問
Q. match 式はどの PHP から使えますか? A. PHP 8.0 以降で使えます。この記事のコードは PHP 8.4.21 で動かして確認しました。enum と組み合わせる例は、enum が追加された PHP 8.1 以降が必要です。
Q. switch はもう使わないほうがよいのでしょうか? A. いいえ、switch にも向いている場面があります。分岐ごとに複数行の処理をしたいときや、フォールスルーを使いたいときは switch が書きやすい選択です。「値を決めるなら match、処理を並べるなら switch や if」と役割で分けると選びやすくなります。
Q. match で「10以上20未満」のような範囲を判定できますか?
A. match (true) を使うと判定できます。各腕に $n >= 10 && $n < 20 => ... のように bool を返す条件式を書きます。上から順に評価されるので、厳しい条件を先に並べるのがポイントです。
Q. UnhandledMatchError を try-catch で捕まえるべきですか? A. 多くの場合は、捕まえるよりも default を書くか、腕を足して起きないようにするのがおすすめです。UnhandledMatchError は「条件の書きもれ」を知らせる合図なので、想定外の入力がありうるなら default で既定値や分かりやすい例外を用意しておくと、原因を追いやすくなります。
まとめ
- match は値を返す式。
===で照合し、break は不要 - switch は
==で比べるため、'0'・null・false がすべてcase 0に入る。match は型まで一致した腕だけを選ぶ - どの腕にも一致せず default もないと UnhandledMatchError。書きもれにその場で気づける
- 範囲判定は match (true)。条件は厳しい順に並べ、bool を返す式にする
- 複数の値はカンマで並べる。enum は default を省くとケース追加時の対応もれが見つかる
- 腕に書けるのは式1つ。複数行の処理は関数に切り出すか、switch・if を選ぶ
- 値を決めるなら match、条件が自由な形なら if elseif、処理を並べるなら switch
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「PHP match式の使い方|switchとの違いとif elseifの使い分け」
株式会社RJCでは、match 式をはじめとした PHP の条件分岐の書き方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。