Match expression: switch sem falhas de tipo

1. Introdução à Match Expression no PHP 8.0

A expressão match foi introduzida no PHP 8.0 como uma evolução moderna do tradicional switch. Enquanto switch é uma estrutura de controle que executa blocos de código, match é uma expressão — ou seja, ela sempre retorna um valor. Essa distinção fundamental resolve um dos maiores problemas do switch: a comparação frouxa de tipos e a necessidade manual de break para evitar falhas (fallthrough).

A sintaxe básica é simples e intuitiva:

$resultado = match($valor) {
    1 => 'um',
    2 => 'dois',
    3 => 'três',
    default => 'outro'
};

Note que não há break, não há case, e o resultado é atribuído diretamente à variável $resultado.

2. Comparação Direta: match vs switch

O switch utiliza comparação frouxa (==), o que pode levar a comportamentos inesperados. Veja o exemplo clássico:

$valor = "1"; // string

switch ($valor) {
    case 1:
        echo "Inteiro 1";
        break;
    case "1":
        echo "String 1";
        break;
}
// Saída: Inteiro 1 (compara como 1 == "1" → true)

Com match, a comparação é estrita (===):

$valor = "1";

$resultado = match($valor) {
    1 => 'Inteiro 1',
    "1" => 'String 1',
};
echo $resultado; // Saída: String 1

Isso elimina ambiguidades e torna o código mais previsível, especialmente em APIs que recebem dados de fontes externas (JSON, formulários, etc.).

3. Comportamento Exaustivo e Sem break

No switch, esquecer um break causa fallthrough — a execução continua para o próximo caso, muitas vezes gerando bugs difíceis de rastrear. No match, isso é impossível: apenas um braço é executado e seu valor é retornado imediatamente.

Além disso, match exige exaustividade: todos os valores possíveis devem ser cobertos. Se um valor não for correspondido, um UnhandledMatchError é lançado:

$cor = 'roxo';

$hex = match($cor) {
    'vermelho' => '#FF0000',
    'verde' => '#00FF00',
    'azul' => '#0000FF',
};
// UnhandledMatchError: Unhandled match value of type string

Para evitar isso, use default:

$hex = match($cor) {
    'vermelho' => '#FF0000',
    'verde' => '#00FF00',
    'azul' => '#0000FF',
    default => '#000000',
};

4. Expressão vs Declaração: Retorno de Valor

A capacidade de retornar valor é uma das maiores vantagens do match. Enquanto no switch você precisa de variáveis auxiliares e break:

// switch tradicional
$desconto = 0;
switch ($categoria) {
    case 'premium':
        $desconto = 20;
        break;
    case 'gold':
        $desconto = 15;
        break;
    default:
        $desconto = 5;
}

Com match, tudo fica em uma única expressão:

$desconto = match($categoria) {
    'premium' => 20,
    'gold' => 15,
    default => 5,
};

Isso permite criar funções mais enxutas e evitar efeitos colaterais indesejados.

5. Casos Múltiplos e Condições Complexas

O match permite agrupar múltiplos valores em um mesmo braço usando vírgula:

$nivel = match($pontos) {
    1, 2, 3 => 'baixo',
    4, 5, 6 => 'médio',
    7, 8, 9 => 'alto',
    default => 'extremamente alto',
};

Também é possível usar expressões condicionais nos braços:

$idade = 25;
$faixa = match(true) {
    $idade < 12 => 'criança',
    $idade < 18 => 'adolescente',
    $idade < 60 => 'adulto',
    default => 'idoso',
};

Note que não é possível usar || ou && diretamente nos braços — para condições complexas, use match(true) com expressões booleanas.

6. Tratamento de Tipos Misto e Union Types

O match brilha quando trabalhamos com union types. Suponha uma função que pode receber diferentes tipos:

function processar(mixed $input): string {
    return match(true) {
        is_int($input) => "Inteiro: " . ($input * 2),
        is_string($input) => "String: " . strtoupper($input),
        is_null($input) => "Valor nulo",
        default => "Tipo desconhecido",
    };
}

echo processar(10);     // Inteiro: 20
echo processar("php");  // String: PHP
echo processar(null);   // Valor nulo

Com switch, isso exigiria múltiplos if aninhados ou verificações manuais de tipo.

7. Casos de Uso Avançados e Boas Práticas

Substituição de switch aninhados

// Antes: switch aninhado
switch ($user->role) {
    case 'admin':
        switch ($user->status) {
            case 'active': $permission = 'full'; break;
            case 'suspended': $permission = 'read'; break;
        }
        break;
    case 'editor':
        $permission = 'write';
        break;
}

// Depois: match encadeado
$permission = match($user->role) {
    'admin' => match($user->status) {
        'active' => 'full',
        'suspended' => 'read',
    },
    'editor' => 'write',
    default => 'none',
};

Combinação com Enum do PHP 8.1

enum Status: string {
    case Ativo = 'ativo';
    case Inativo = 'inativo';
    case Pendente = 'pendente';
}

function mensagemStatus(Status $status): string {
    return match($status) {
        Status::Ativo => 'Usuário ativo',
        Status::Inativo => 'Usuário inativo',
        Status::Pendente => 'Aguardando confirmação',
    };
}

Quando evitar match

Em loops muito grandes (milhões de iterações), o match pode ser ligeiramente mais lento que um switch otimizado. Para a grande maioria dos casos, porém, a diferença é irrelevante — priorize legibilidade.

8. Erros Comuns e Armadilhas

Erro 1: Exaustividade ignorada

$valor = 5;
match($valor) {
    1 => 'um',
    2 => 'dois',
};
// UnhandledMatchError: 5 não foi coberto

Erro 2: Vírgula vs => em casos múltiplos

// ERRADO
match($x) {
    1, 2, 3 => 'baixo',
};

// CORRETO
match($x) {
    1, 2, 3 => 'baixo',
};

A confusão ocorre quando se tenta usar => entre os valores agrupados.

Erro 3: break ou continue dentro de match

foreach($itens as $item) {
    match($item) {
        'pular' => continue, // Erro! continue não permitido
        default => processar($item),
    };
}

Para contornar, use if dentro do braço ou reestruture a lógica.

Conclusão

A expressão match do PHP 8.0 representa uma evolução significativa em relação ao switch. Com comparação estrita, ausência de fallthrough, retorno de valor e exaustividade obrigatória, ela torna o código mais seguro, legível e previsível. Ao migrar projetos legados ou escrever novo código, priorize match sempre que precisar de uma estrutura condicional baseada em valores discretos.

Referências