match

(PHP 8)

match 式は、値の一致をチェックした結果に基づいて評価結果を分岐します。 switch 文と似ていますが、 match 式は複数の候補と比較される制約式を持ちます。 switch 文とは異なり、 三項演算子のように値を評価します。 switch 文とは異なり、 弱い比較(==)ではなく、 型と値の一致チェック(===) に基づいて行われます。 match 式は PHP 8.0.0 以降で利用可能です。

例1 match 式の構造

<?php
$return_value
= match (制約式) {
単一の条件式 => 返却式,
条件式1, 条件式2 => 返却式,
};
?>

例2 基本式な match 式の使い方

<?php
$food
= 'cake';

$return_value = match ($food) {
'apple' => 'This food is an apple',
'bar' => 'This food is a bar',
'cake' => 'This food is a cake',
};

var_dump($return_value);
?>

上の例の出力は以下となります。

string(19) "This food is a cake"

例3 比較演算子と match 式を一緒に使う例

<?php
$age
= 18;

$output = match (true) {
$age < 2 => "Baby",
$age < 13 => "Child",
$age <= 19 => "Teenager",
$age > 19 => "Young adult",
$age >= 40 => "Old adult"
};

var_dump($output);
?>

上の例の出力は以下となります。

string(8) "Teenager"

注意: match 式の結果は、必ずしも使う必要はありません。

注意: match 式は、必ずセミコロン ; で終わらなければなりません。

match 式は、 switch 文と似ていますが、いくつかの違いがあります:

  • match 式の比較は、 switch 文が行う弱い比較ではなく、 厳密に値を比較(===) します。
  • match 式は値を返します。
  • match 式の分岐は、 switch 文のように後の分岐に抜けたりはしません。
  • match 式は、全ての場合を網羅していなければいけません。

switch 文のように、 match 式はマッチさせる分岐をひとつひとつ実行します。 はじめは、コードは何も実行されません。 以前のすべての条件式が、制約式とマッチしなかった場合に条件式が実行されます。 条件式に一致する式が評価された場合に、返却式が評価されます。 たとえば、以下のようになります:

<?php
$result
= match ($x) {
foo() => ...,
$this->bar() => ..., // foo() === $x であれば $this->bar() は呼び出されません。
$this->baz => beep(), // $x === $this->baz でなければ beep() は呼び出されません。
// などなど
};
?>

match 式の分岐は、複数の式をカンマ区切りで含めても構いません。 これは論理ORであり、複数の分岐の右辺を同じにする場合の短縮記法です。

<?php
$result
= match ($x) {
// この分岐は:
$a, $b, $c => 5,
// 以下の3つの分岐と等しい:
$a => 5,
$b => 5,
$c => 5,
};
?>

default パターンという特別な場合があります。 このパターンは前の分岐にマッチしなかったあらゆる場合にマッチします。 たとえば、以下のようになります:

<?php
$expressionResult
= match ($condition) {
1, 2 => foo(),
3, 4 => bar(),
default =>
baz(),
};
?>

注意: 複数の default パターンがあった場合、 E_FATAL_ERROR が発生します。

match 式は、全ての場合を網羅していなければいけません。 制約式がどの分岐でも処理できなかった場合、 UnhandledMatchError がスローされます。

例4 処理されない match 式の例

<?php
$condition
= 5;

try {
match (
$condition) {
1, 2 => foo(),
3, 4 => bar(),
};
} catch (
\UnhandledMatchError $e) {
var_dump($e);
}
?>

上の例の出力は以下となります。

object(UnhandledMatchError)#1 (7) {
  ["message":protected]=>
  string(33) "Unhandled match value of type int"
  ["string":"Error":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(9) "/in/ICgGK"
  ["line":protected]=>
  int(6)
  ["trace":"Error":private]=>
  array(0) {
  }
  ["previous":"Error":private]=>
  NULL
}

厳密な一致チェックを行わずに match 式を使う

制約式に true を指定することで、 厳密な一致チェックを行わずに match 式を使うことができます。

例5 整数の範囲に応じてmatch式を分岐させる一般的な使い方

<?php

$age
= 23;

$result = match (true) {
$age >= 65 => 'senior',
$age >= 25 => 'adult',
$age >= 18 => 'young adult',
default =>
'kid',
};

var_dump($result);
?>

上の例の出力は以下となります。

string(11) "young adult"

例6 文字列の内容に応じてmatch式を分岐させる一般的な使い方

<?php

$text
= 'Bienvenue chez nous';

$result = match (true) {
str_contains($text, 'Welcome') || str_contains($text, 'Hello') => 'en',
str_contains($text, 'Bienvenue') || str_contains($text, 'Bonjour') => 'fr',
// ...
};

var_dump($result);
?>

上の例の出力は以下となります。

string(2) "fr"
add a note

User Contributed Notes 9 notes

up
93
darius dot restivan at gmail dot com
4 years ago
This will allow for a nicer FizzBuzz solution:<?phpfunction fizzbuzz($num) {    print match (0) {        $num % 15 => "FizzBuzz" . PHP_EOL,        $num % 3  => "Fizz" . PHP_EOL,        $num % 5  => "Buzz" . PHP_EOL,        default   => $num . PHP_EOL,    };}for ($i = 0; $i <=100; $i++){    fizzbuzz($i);}
up
76
Anonymous
3 years ago
<?php
function days_in_month(string $month, $year): int
{
    return match(strtolower(substr($month, 0, 3))) {
        'jan' => 31,
        'feb' => is_leap($year) ? 29 : 28,
        'mar' => 31,
        'apr' => 30,
        'may' => 31,
        'jun' => 30,
        'jul' => 31,
        'aug' => 31,
        'sep' => 30,
        'oct' => 31,
        'nov' => 30,
        'dec' => 31,
        default => throw new InvalidArgumentException("Bogus month"),
    };
}
?>

can be more concisely written as

<?php
function days_in_month(string $month, $year): int
{
    return match(strtolower(substr($month, 0, 3))) {
        'apr', 'jun', 'sep',  'nov'  => 30,        
        'jan', 'mar', 'may', 'jul', 'aug',  'oct', 'dec'  => 31,
        'feb' => is_leap($year) ? 29 : 28,
        default => throw new InvalidArgumentException("Bogus month"),
    };
}
?>
up
55
Hayley Watson
4 years ago
As well as being similar to a switch, match expressions can be thought of as enhanced lookup tables — for when a simple array lookup isn't enough without extra handling of edge cases, but a full switch statement would be overweight.

For a familiar example, the following
<?php

function days_in_month(string $month): int
{
    static $lookup = [
    'jan' => 31,
    'feb' => 0,
    'mar' => 31,
    'apr' => 30,
    'may' => 31,
    'jun' => 30,
    'jul' => 31,
    'aug' => 31,
    'sep' => 30,
    'oct' => 31,
    'nov' => 30,
    'dec' => 31
    ];

    $name = strtolower(substr($name, 0, 3));

    if(isset($lookup[$name])) {
        if($name == 'feb') {
            return is_leap($year) ? 29 : 28;
        } else {
            return $lookup[$name];
        }
    }
    throw new InvalidArgumentException("Bogus month");
}

?>

with the fiddly stuff at the end, can be replaced by

<?php
function days_in_month(string $month): int
{
    return match(strtolower(substr($month, 0, 3))) {
        'jan' => 31,
        'feb' => is_leap($year) ? 29 : 28,
        'mar' => 31,
        'apr' => 30,
        'may' => 31,
        'jun' => 30,
        'jul' => 31,
        'aug' => 31,
        'sep' => 30,
        'oct' => 31,
        'nov' => 30,
        'dec' => 31,
        default => throw new InvalidArgumentException("Bogus month"),
    };
}
?>

Which also takes advantage of "throw" being handled as of PHP 8.0 as an expression instead of a statement.
up
10
tolga dot ulas at tolgaulas dot com
1 year ago
Yes it currently does not support code blocks but this hack works:match ($foo){    'bar'=>(function(){        echo "bar";    })(),    default => (function(){        echo "baz";    })()};
up
7
Sbastien
2 years ago
I use match instead of storing PDOStatement::rowCount() result and chaining if/elseif conditions or use the ugly switch/break :<?php$sql = <<<SQL    INSERT INTO ...    ON DUPLICATE KEY UPDATE ...    SQL;$upkeep = $pdo->prepare($sql);$count_untouched = 0;$count_inserted = 0;$count_updated = 0;foreach ($data as $record) {    $upkeep->execute($record);    match ($upkeep->rowCount()) {        0 => $count_untouched++,        1 => $count_inserted++,        2 => $count_updated++,    };}echo "Untouched rows : {$count_untouched}\r\n";echo "Inserted rows : {$count_inserted}\r\n";echo "Updated rows : {$count_updated}\r\n";
up
9
thomas at zuschneid dot de
2 years ago
While match allows chaining multiple conditions with ",", like:<?php$result = match ($source) {    cond1, cond2 => val1,    default => val2};?>it seems not valid to chain conditions with default, like:<?php$result = match ($source) {    cond1 => val1,    cond2, default => val2};?>
up
13
php at joren dot dev
3 years ago
If you want to execute multiple return expressions when matching a conditional expression, you can do so by stating all return expressions inside an array.<?php    $countries = ['Belgium', 'Netherlands'];    $spoken_languages = [        'Dutch' => false,        'French' => false,        'German' => false,        'English' => false,    ];    foreach ($countries as $country) {        match($country) {            'Belgium' => [                $spoken_languages['Dutch'] = true,                $spoken_languages['French'] = true,                $spoken_languages['German'] = true,            ],            'Netherlands' => $spoken_languages['Dutch'] = true,            'Germany' => $spoken_languages['German'] = true,            'United Kingdom' => $spoken_languages['English'] = true,        };    }    var_export($spoken_languages);     // array ( 'Dutch' => true, 'French' => true, 'German' => true, 'English' => false, )?>
up
4
mark at manngo dot net
3 years ago
While you can’t polyfill a language construct, you can mimic the basic behaviour with a simple array.Using example 2 above:<?php    $food = 'apple';    $return_value = match ($food) {        'apple' => 'This food is an apple',        'bar' => 'This food is a bar',        'cake' => 'This food is a cake',    };        print $return_value;?>… you can get something similar with:<?php    $food = 'apple';        $return_value = [        'apple' => 'This food is an apple',        'bar' => 'This food is a bar',        'cake' => 'This food is a cake',    ][$food];    print $return_value;?>
up
2
tm
3 years ago
If you are using a match expression for non-identity checks as described above make sure whatever you are using is actually returning `true` on success.Quite often you rely on truthy vs. falsy when using if conditions and that will not work for match (for example `preg_match`). Casting to bool will solve this issue.
To Top