(PHP 8)
L'expression match permet d'effectuer une évaluation basée sur le
contrôle d'identité d'une valeur.
De la même manière qu'une instruction switch, une expression
match a une expression sujet qui est
comparée à plusieurs alternatives. Contrairement à switch,
elle s'évalue à une valeur, comme les expressions ternaires.
Contrairement à switch, la comparaison est une vérification d'identité
(===) plutôt qu'un contrôle d'égalité faible (==).
Les expressions Match sont disponibles à partir de PHP 8.0.0.
Exemple #1 Structure d'une expression match
<?php
$return_value = match (subject_expression) {
single_conditional_expression => return_expression,
conditional_expression1, conditional_expression2 => return_expression,
};Exemple #2 Utilisation de base des expressions match
<?php
$food = 'cake';
$return_value = match ($food) {
'apple' => 'Cet aliment est une pomme',
'bar' => 'Cet aliment est une barre',
'cake' => 'Cet aliment est un gâteau',
};
var_dump($return_value);L'exemple ci-dessus va afficher :
string(26) "Cet aliment est un gâteau"
Exemple #3 Exemple d'utilisation de match avec des opérateurs de comparaison
<?php
$age = 18;
$output = match (true) {
$age < 2 => "Bébé",
$age < 13 => "Enfant",
$age <= 19 => "Adolescent",
$age >= 40 => "Adulte",
$age > 19 => "Jeune adulte",
};
var_dump($output);L'exemple ci-dessus va afficher :
string(10) "Adolescent"
Note:
Le résultat d'une expression
matchn'a pas besoin d'être utilisé.
Note:
Lorsqu'une expression
matchest utilisée comme une expression autonome, elle doit être terminée par un point-virgule;.
L'expression match est similaire à une instruction
switch mais présente quelques différences essentielles :
Une expression match compare les valeurs de manière stricte (===)
et non de manière faible comme le fait l'instruction switch.
Une expression match renvoie une valeur.
Les expressions match ne passent pas aux cas suivants comme le font les
instructions switch.
Une expression match doit être exhaustive.
Comme les instructions switch, les expressions match
sont exécutées bras par bras. Au début, aucun code n'est exécuté.
Les expressions conditionnelles ne sont évaluées que si toutes les expressions conditionnelles
précédentes ne correspondent pas à l'expression de l'objet.
Seule l'expression de retour correspondant à l'expression conditionnelle correspondante sera évaluée.
Par exemple :
<?php
$result = match ($x) {
foo() => 'value',
$this->bar() => 'value', // $this->bar() n'est pas appelé si foo() === $x
$this->baz => beep(), // beep() n'est pas appelé sauf si $x === $this->baz
// etc.
};
Les bras de l'expression de match peuvent contenir plusieurs expressions
séparées par une virgule. Il s'agit d'un OU logique et d'une abréviation pour plusieurs bras
qui utilisent la même expression comme résultat.
<?php
$result = match ($x) {
// Ce bras
$a, $b, $c => 5,
// Est équivalent à ces trois bras :
$a => 5,
$b => 5,
$c => 5,
};
Le motif default est un cas particulier.
Ce motif correspond à tout ce qui n'a pas été recherché précédemment.
Par exemple :
<?php
$expressionResult = match ($condition) {
1, 2 => foo(),
3, 4 => bar(),
default => baz(),
};Note:
L'utilisation de plusieurs motifs par défaut entraînera une erreur
E_FATAL_ERROR.
Une expression match doit être exhaustive. Si l'expression
n'est traitée par aucun bras de match, une erreur
UnhandledMatchError est lancée.
Exemple #4 Exemple d'une expression match non gérée
<?php
$condition = 5;
try {
match ($condition) {
1, 2 => foo(),
3, 4 => bar(),
};
} catch (\UnhandledMatchError $e) {
var_dump($e);
}L'exemple ci-dessus va afficher :
object(UnhandledMatchError)#1 (7) {
["message":protected]=>
string(22) "Unhandled match case 5"
["string":"Error":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(6) "script"
["line":protected]=>
int(5)
["trace":"Error":private]=>
array(0) {
}
["previous":"Error":private]=>
NULL
}
Il est possible d'utiliser une expression match pour traiter
les cas conditionnels de non-identité en utilisant true comme expression sujet.
Exemple #5 Utilisation d'une expression match généralisée pour effectuer des branchements sur des plages d'entiers
<?php
$age = 23;
$result = match (true) {
$age >= 65 => 'senior',
$age >= 25 => 'adult',
$age >= 18 => 'young adult',
default => 'kid',
};
var_dump($result);L'exemple ci-dessus va afficher :
string(11) "young adult"
Exemple #6 Utilisation d'une expression match généralisée pour effectuer des branchements sur le contenu d'une chaîne de caractères
<?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);L'exemple ci-dessus va afficher :
string(2) "fr"
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);
}<?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"),
};
}
?>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.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
};
?>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, )
?>Yes it currently does not support code blocks but this hack works:
match ($foo){
'bar'=>(function(){
echo "bar";
})(),
default => (function(){
echo "baz";
})()
};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";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;
?>Since `match` is a better alternative in MOST cases for modern software, adding this comment so that if someone googles "`switch` in PHP" then this page also shows in the search results.In response to yawarmushtaq52 at gmail dot com....
Switches were only ugly due to how people used them. Lot's of logic INSIDE THE SWITCH is never a good idea. K&R, as opposed to GNU or Whitesmiths doesn't help. The inability of people to think freely / pragmatically about what they've been taught.
First things first, matches can be ugly too.
<?php
match ($action) {
'save' => saveProduct(),
'update' => (static function() {
Log::info('Updating product...');
return updateProduct();
})(),
default => throw new InvalidArgumentException("Unknown action: $action"),
};
?>
Here is how most people see switches.
<?php
switch (true) {
case $age < 18:
$discount = 0.20; // 20% minor discount
break;
case $age >= 65:
$discount = 0.15; // 15% senior discount
break;
case $hasMembership && $age < 30:
$discount = 0.10; // 10% young adult member discount
break;
default:
$discount = 0.00;
break;
}
?>
All 17 lines of it. But it always could've looked like...
<?php
switch (true)
{
case $age < 18: { $discount = 0.20; break; } // 20% minor discount
case $hasMembership && $age < 30: { $discount = 0.10; break; } // 10% young adult member discount
case $age < 30:
case $age < 40:
case $age < 50: { $discount = check_for_specials($age); break; }
case $age < 65: { $discount = mid_years_specials($age); break; }
case $age >= 65: { $discount = 0.15; break; } // 15% senior discount
default: { $discount = 0.00; }
}
?>
Capable of more than the first example, easier to read, and 6 less lines.
Additionally, "falling through" means it's easy for me to see from 19 to 50 and know what's going on without having to read from side to side. It's an easy visual indicator to the logic. The equivalent match is more verbose.
<?php
$discount = match (true)
{
$age < 18 => 0.20,
$hasMembership && $age < 30 => 0.10,
$age < 30 => check_for_specials($age),
$age < 40 => check_for_specials($age),
$age < 50 => check_for_specials($age),
$age < 65 => mid_years_specials($age),
$age >= 65 => 0.15,
default => 0.00,
};
?>
Sure! We could collapse 19 > 50 for both examples.
<?php
switch (true)
{
case $age < 18: { $discount = 0.20; break; }
case $hasMembership && $age < 30: { $discount = 0.10; break; }
case $age < 50: { $discount = check_for_specials($age); break; }
case $age < 65: { $discount = mid_years_specials($age); break; }
case $age >= 65: { $discount = 0.15; break; }
default: { $discount = 0.00; break; }
}
$discount = match (true)
{
$age < 18 => 0.20,
$hasMembership && $age < 30 => 0.10,
$age < 50 => check_for_specials($age),
$age < 65 => mid_years_specials($age),
$age >= 65 => 0.15,
default => 0.00,
};
?>
Do those really look that different? Is Switch really that ugly?You can enforce a type return on match by wrapping in a self-calling function, like this:
<?php
$operationId = 3;
$operationLabel = (fn (): string => match($operationId) {
1 => 'Add',
2 => 'Subtract',
3 => ['Not string'], // Invalid case,
default => 'Unknown'
})()
?>
In the above example, the following error will be throwed:
Fatal error: Uncaught TypeError: (...): Return value must be of type string, array returned in /home/user/scripts/code.php:9
Stack trace: ...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.