PHP 8.4.2 Released!

match

(PHP 8)

Выражение match выбирает ветвь вычисления на основе проверки, которая показывает, идентично ли значение условного выражения значению входного условного выражения. Аналогично инструкции switch выражение match принимает на вход выражение, которое сравнивается с набором альтернатив. В отличие от инструкции switch, выражение сопоставления оценивает значение в стиле, который больше похож на тернарный оператор. Выражение выполняет строгое сравнение ===, а не слабую проверку на равенство ==, как это делает инструкция switch. Выражения сопоставления доступны с PHP 8.0.0.

Пример #1 Структура выражения match

<?php

$return_value
= match (subject_expression) {
single_conditional_expression => return_expression,
conditional_expression1, conditional_expression2 => return_expression,
};

?>

Пример #2 Базовый пример сопоставления значений в выражении match

<?php

$food
= 'cake';

$return_value = match ($food) {
'apple' => 'На столе лежит яблоко',
'banana' => 'На столе лежит банан',
'cake' => 'На столе стоит торт',
};

var_dump($return_value);

?>

Результат выполнения приведённого примера:

string(35) "На столе стоит торт"

Пример #3 Пример сопоставления значений в выражении match с операторами сравнения

<?php

$age
= 18;

$output = match (true) {
$age < 2 => "Младенец",
$age < 13 => "Ребёнок",
$age <= 19 => "Подросток",
$age >= 40 => "Взрослый",
$age > 19 => "Молодой человек",
};

var_dump($output);

?>

Результат выполнения приведённого примера:

string(8) "Подросток"

Замечание: Результат выражения match использовать не обязательно.

Замечание: Выражение match должно завершаться точкой с запятой ;.

Выражение match похоже на инструкцию switch, за исключением отдельных ключевых отличий:

  • В отличие от нестрогого сравнения в инструкции switch, в ветке сопоставления значений в выражении match значения сравниваются строго — ===
  • Выражение match возвращает значение
  • В выражении match выполнение кода не переходит к следующей ветви сопоставления значений после первого совпадения значений, в отличие от провалов к следующему случаю в инструкции switch
  • Выражение match должно быть исчерпывающим

Как и инструкциях switch, в выражениях match ветви сопоставления значений выполняются одна за другой. В начале никакой код не выполняется. Условные выражения оцениваются, только если предыдущие условные выражения не соответствуют входному выражению. Выражение match вычислит только то выражение возврата, которое соответствует условному выражению, значение которого совпало. Приведём пример:

<?php

$result
= match ($x) {
foo() => ...,
$this->bar() => ..., // Метод $this->bar() не вызывается, если значение возврата функции foo() === $x
$this->baz => beep(), // Функция beep() выполнится, если только входное выражение $x === $this->baz
// и т. д.
};

?>

Ветвям сопоставления значений в выражении match разрешается содержать множественные выражения, разделённые запятыми. Множественные выражения проверяются по условию логического ИЛИ и представляют короткую запись для множественных ветвей сопоставления значений с одним и тем выражением в правой части.

<?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 должно быть исчерпывающим. Выражение выбрасывает исключение 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

Выражение match умеет обрабатывать случаи с нетождественными условиями. Для этого входное выражении указывают как значение true.

Пример #5 Пример обобщения выражения match для ветвления на основе целочисленных диапазонов

<?php

$age
= 23;

$result = match (true) {
$age >= 65 => 'пожилой',
$age >= 25 => 'взрослый',
$age >= 18 => 'совершеннолетний',
default =>
'ребёнок',
};

var_dump($result);

?>

Результат выполнения приведённого примера:

string(11) "совершеннолетний"

Пример #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 notes

up
92
darius dot restivan at gmail dot com
3 years ago
This will allow for a nicer FizzBuzz solution:

<?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);
}
up
73
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
54
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
thomas at zuschneid dot de
1 year 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
6
Sbastien
1 year 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
7
tolga dot ulas at tolgaulas dot com
9 months ago
Yes it currently does not support code blocks but this hack works:

match ($foo){
'bar'=>(function(){
echo "bar";
})(),
default => (function(){
echo "baz";
})()
};
up
13
php at joren dot dev
2 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
2 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
1
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