PHP 8.4.1 Released!

match

(PHP 8)

Der match-Ausdruck verzweigt die Auswertung auf der Grundlage einer Identitätsprüfung eines Wertes. Ähnlich wie eine switch-Anweisung, hat ein match-Ausdruck ein Subjekt, welcher mit mehreren Fällen verglichen wird. Im Gegensatz zu switch ist der Vergleich typsicher (===) und nicht typschwache (==). Match-Ausdrücke sind ab PHP 8.0.0 verfügbar.

Beispiel #1 Struktur eines match-Ausdrucks

<?php
$return_value
= match (subjekt) {
einfacher_bedingter_ausdruck => rückgabe_ausdruck,
bedingter_ausdruck1, bedingter_ausdruck2 => rückgabe_ausdruck,
};
?>

Beispiel #2 Grundlegende Benutzung von match

<?php
$lebensmittel
= 'kuchen';

$return_value = match ($lebensmittel) {
'apfel' => 'Das Lebensmittel ist ein Apfel',
'schokolade' => 'Das Lebensmittel ist Schokolade',
'kuchen' => 'Das Lebensmittel ist ein Kuchen',
};

var_dump($return_value);
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

string(31) "Das Lebensmittel ist ein Kuchen"

Beispiel #3 Beispiel für die Benutzung von match mit Vergleichsoperatoren

<?php
$age
= 18;

$output = match (true) {
$age < 2 => "Baby",
$age < 13 => "Kind",
$age <= 19 => "Teenager",
$age >= 40 => "Alter Erwachsener",
$age > 19 => "Junger Erwachsener",
};

var_dump($output);
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

string(8) "Teenager"

Hinweis: Das Ergebnis eines match-Ausdrucks muss nicht verwendet werden.

Hinweis: Ein match-Ausdruck muss mit einem Semikolon ; abgeschlossen werden.

Der Ausdruck match ist ähnlich wie eine switch-Anweisung, hat aber einige wesentliche Unterschiede:

  • Ein match-Fall vergleicht Werte typsicher (===) statt typschwach, wie die switch-Anweisung.
  • Ein match-Ausdruck gibt einen Wert zurück.
  • match-Fälle fallen nicht auf spätere Fälle durch, so wie es switch-Fälle tun können.
  • Ein match-Ausdruck muss vollständig sein.

Wie switch-Anweisungen, werden match-Ausdrücke Fall für Fall ausgeführt. Zu Beginn wird kein Code ausgeführt. Die bedingten Ausdrücke werden nur ausgewertet, wenn alle vorherigen bedingten Ausdrücke nicht mit dem betreffenden Ausdruck übereinstimmen. Nur der Rückgabeausdruck, der dem passenden bedingten Ausdruck entspricht, wird ausgewertet. Ein Beispiel dafür:

<?php
$result
= match ($x) {
foo() => ...,
$this->bar() => ..., // $this->bar() wird nicht aufgerufen, wenn foo() === $x
$this->baz => beep(), // beep() wird nur aufgerufen, wenn $x === $this->baz
// etc.
};
?>

match-Bedingungen können mehrere Ausdrücke, die durch Kommata getrennt sind, enthalten. Diese Ausdrücke werden durch ein logisches ODER getrennt und sind ein Kürzel für mehrere match-Fälle, welche die gleiche rechte Seite haben.

<?php
$result
= match ($x) {
// Dieser Fall:
$a, $b, $c => 5,
// ist der gleiche Fall wie:
$a => 5,
$b => 5,
$c => 5,
};
?>

Ein Sonderfall ist die default-Klausel. Diese fängt alle Fälle ab, die nicht durch einen der anderen Fälle behandelt wurden. Zum Beispiel:

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

Hinweis: Mehrere default-Anweisungen erzeugen einen E_COMPILE_ERROR-Fehler.

Ein match-Ausdruck muss vollständig sein. Wenn die Bedingung von keinem Fall behandelt wird, wird eine UnhandledMatchError-Exception geworfen.

Beispiel #4 Beispiel für einen unbehandelten Vergleichsausdruck

<?php
$bedingung
= 5;

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

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

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
}

Verwendung von Ausdrücken zur Handhabung von Nicht-Vergleichsprüfungen

Es ist möglich, einen match-Ausdruck zu verwenden, um nicht-vergleichende Fälle zu behandeln, indem true als Bedingung verwendet wird.

Beispiel #5 Prüfung von ganzzahligen Bereichen

<?php

$alter
= 23;

$result = match (true) {
$alter >= 65 => 'Senior',
$alter >= 25 => 'Erwachsener',
$alter >= 18 => 'Junger Erwachsener',
default =>
'Kind',
};

var_dump($result);
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

string(18) "Junger Erwachsener"

Beispiel #6 Prüfung eines Textes auf enthaltene Zeichenfolgen

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

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

string(2) "fr"
add a note

User Contributed Notes 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
3 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
8 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