Dutch PHP Conference 2027

match

(PHP 8)

A expressão match ramifica a avaliação baseada em uma verificação de identidade de um valor. Semelhante a uma declaração switch, uma expressão match possui uma expressão sujeito que é comparada com múltiplas alternativas. Ao contrário da switch, ela irá avaliar para um valor assim como as expressões ternárias. Diferente da switch, a comparação é uma verificação de identidade (===) em vez de uma comparação de igualdade fraca (==). Expressões match estão disponíveis a partir do PHP 8.0.0.

Exemplo #1 Estrutura de uma expressão match

<?php
$valor_de_retorno = match (expressao_sujeito) {
    expressao_condicional_unica => expressao_de_retorno,
    expressao_condicional1, expressao_condicional2 => expressao_de_retorno,
};

Exemplo #2 Uso básico de match

<?php
$comida = 'bolo';

$valor_de_retorno = match ($comida) {
    'apple' => 'Essa comida é uma maçã',
    'bar' => 'Essa comida é um bar',
    'bolo' => 'Essa comida é um bolo',
};

var_dump($valor_de_retorno);

O exemplo acima produzirá:

string(19) "Essa comida é um bolo"

Exemplo #3 Exemplo de uso de match com operadores de comparação

<?php
$age = 18;

$output = match (true) {
    $age < 2 => "Bebê",
    $age < 13 => "Criança",
    $age <= 19 => "Adolescente",
    $age >= 40 => "Adulto"
    $age > 19 => "Adulto Jovem"
};

var_dump($output);

O exemplo acima produzirá:

string(11) "Adolescente"

Nota:

O resultado de uma expressão match não precisa ser usado.

Nota:

Quando uma expressão match é usada como uma expressão única, ela precisa ser terminada com um ponto-e-vírgula ;.

A expressão match é similar a uma declaração switch mas tem algumas diferenças importantes:

  • Um braço match compara valores estritamente (===) em vez de frouxamente como a declaração switch faz.

  • Uma expressão match retorna um valor.

  • Braços match não seguem para casos posteriores da maneira que declarações switch seguem.

  • Uma expressão match deve ser exaustiva.

Assim como as declarações switch, expressões match são executadas a cada ramo de correspondência. No início, nenhum código é executado. As expressões condicionais só são avaliadas se todas as expressões condicionais anteriores não corresponderem à expressão sujeito. Apenas a expressão de retorno pertencente à expressão condicional de correspondência será avaliada. Por exemplo:

<?php
$resultado = match ($x) {
    foo() => 'value',
    $this->bar() => 'value', // $this->bar() não é chamado se foo() === $x
    $this->baz => beep(), // beep() não é chamado a não ser que $x === $this->baz
    // etc.
};

Braços da expressão match podem conter múltiplas expressões separadas por uma vírgula. Isso é um OR lógico, e é uma forma abreviada para múltiplos braços de correspondência com o mesmo lado direito.

<?php
$resultado = match ($x) {
    // Este braço de correspondência:
    $a, $b, $c => 5,
    // É equivalente a estes três braços de correspondência:
    $a => 5,
    $b => 5,
    $c => 5,
};

Um caso especial é o padrão default. Esse padrão atende a qualquer coisa que não tenha sido correspondida anteriormente. Por exemplo:

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

Nota:

Múltiplos padrões 'default' irão emitir um erro E_FATAL_ERROR.

Uma expressão match deve ser exaustiva. Se a expressão sujeito não for tratada por nenhum braço de correspondência um UnhandledMatchError é lançado.

Exemplo #4 Exemplo de uma expressão match não tratada

<?php
$condicao = 5;

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

O exemplo acima produzirá:

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
}

Usando expressões match para tratar verificações sem identidade

É possível usar uma expressão match para tratar casos condicionais sem identidade usando true como a expressão sujeito.

Exemplo #5 Usando uma expressão match generalizada para ramificar com base em intervalos de inteiros

<?php
$idade = 23;

$resultado = match (true) {
    $idade >= 65 => 'senhor',
    $idade >= 25 => 'adulto',
    $idade >= 18 => 'adolescente',
    default => 'criança',
};

var_dump($resultado);

O exemplo acima produzirá:

string(11) "adolescente"

Exemplo #6 Usando uma expressão match generalizada para ramificar com base no conteúdo de strings

<?php

$texto = 'Bienvenue chez nous';

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

var_dump($resultado);

O exemplo acima produzirá:

string(2) "fr"
adicionar nota

Notas de Usuários 12 notes

up
101
darius dot restivan at gmail dot com
5 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
84
Anonymous
5 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
58
Hayley Watson
5 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
16
thomas at zuschneid dot de
3 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
19
php at joren dot dev
4 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
7
tolga dot ulas at tolgaulas dot com
2 years ago
Yes it currently does not support code blocks but this hack works:

match ($foo){
    'bar'=>(function(){
        echo "bar";
    })(),
    default => (function(){
        echo "baz";
    })()
};
up
6
Sbastien
3 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
7
mark at manngo dot net
4 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
rodrigoliveirasantos11 at gmail dot com
3 months ago
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: ...
up
0
bdkr at protonmail dot com
21 days ago
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?
up
0
yawarmushtaq52 at gmail dot com
7 months ago
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.
up
1
tm
4 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