PHP Conference Japan 2024

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 >= 40 => "Old adult",
$age > 19 => "Young adult",
};

var_dump($output);
?>

上述範例會輸出

string(8) "Teenager"

注意 `match` 表達式的結果不一定要使用。

注意 `match` 表達式*必須*以分號 `;` 結尾。

`match` 表達式類似於 `switch` 陳述式,但有一些關鍵差異

  • `match` 分支使用嚴格比較 (`===`) 來比較值,而不是像 `switch` 陳述式那樣使用鬆散比較。
  • `match` 表達式會回傳一個值。
  • `match` 分支不會像 `switch` 陳述式那樣穿透到後續的 case。
  • `match` 表達式必須是窮盡的 (exhaustive)。

如同 `switch` 陳述式,`match` 表達式會逐個分支地執行。一開始,不會執行任何程式碼。只有當所有先前的條件表達式都未能匹配主體表達式時,才會評估條件表達式。只有與匹配的條件表達式相對應的回傳表達式會被評估。例如:

<?php
$result
= match ($x) {
foo() => ...,
$this->bar() => ..., // 如果 foo() === $x,則不會呼叫 $this->bar()
$this->baz => beep(), // 除非 $x === $this->baz,否則不會呼叫 beep()
// etc.
};
?>

match 表達式的分支 (arm) 可以包含多個以逗號分隔的表達式。這代表邏輯 OR,並且是多個具有相同右側的分支的簡寫。

<?php
$result
= match ($x) {
// 這個分支:
$a, $b, $c => 5,
// 等同於這三個分支:
$a => 5,
$b => 5,
$c => 5,
};
?>

一個特例是 default 模式。這個模式會匹配任何先前未匹配的內容。例如:

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

注意 多個 default 模式會引發 E_FATAL_ERROR 錯誤。

match 表達式必須是窮盡的(exhaustive)。如果主體表達式未被任何分支處理,則會拋出 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"
新增註解

使用者貢獻的註解 9 則註解

darius dot restivan at gmail dot com
3 年前
這將允許更簡潔的 FizzBuzz 解法

<?php

function 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);
}
匿名
3 年前
<?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"),
};
}
?>

可以寫得更簡潔

<?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"),
};
}
?>
Hayley Watson
3 年前
除了類似 switch 語句之外,match 表達式也可以被視為增強的查找表 — 適用於簡單的陣列查找不足以處理邊緣情況,但完整的 switch 語句又過於繁瑣的場景。

舉一個熟悉的例子,以下
<?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");
}

?>

結尾那些繁瑣的程式碼,可以被以下取代

<?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"),
};
}
?>

這也利用了從 PHP 8.0 開始,「throw」被作為表達式而不是語句來處理的特性。
thomas at zuschneid dot de
1 年前
雖然 match 允許使用「,」串接多個條件,例如:
<?php
$result
= match ($source) {
cond1, cond2 => val1,
default =>
val2
};
?>
但似乎無法使用 default 串接條件,例如:
<?php
$result
= match ($source) {
cond1 => val1,
cond2, default => val2
};
?>
Sbastien
1 年前
我使用 match 來取代儲存 PDOStatement::rowCount() 的結果,然後再串接 if/elseif 條件或使用難看的 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";
tolga dot ulas at tolgaulas dot com
8 個月前
是的,它目前不支援程式碼區塊,但這個小技巧可以運作

match ($foo){
'bar'=>(function(){
echo "bar";
})(),
default => (function(){
echo "baz";
})()
};
php at joren dot dev
2 年前
如果您想要在匹配條件表達式時執行多個返回表達式,您可以透過將所有返回表達式放在一個陣列中來實現。

<?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, )

?>
mark at manngo dot net
2 年前
雖然您無法對語言結構進行 polyfill,但您可以使用簡單的陣列來模仿基本行為。

使用上面範例 2

<?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;
?>

… 您可以使用以下程式碼獲得類似的結果

<?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;
?>
tm
3 年前
如果您正在使用 match 表達式進行非恆等檢查(如上所述),請確保您所使用的任何東西在成功時實際返回 `true`。

在使用 if 條件時,您經常依賴真值與假值,但这不適用於 match(例如 `preg_match`)。將其轉換為布林值將解決此問題。
To Top