Escrito e revisado por Jefferson Oliveira. Os exemplos são os do post original de 2023, corrigidos e atualizados pro PHP 8.1.
Toda função que cresceu demais tem a mesma cara: um if dentro de outro, um else no fim e três jeitos de sair. Quem lê precisa guardar todas as condições na cabeça pra saber o que volta. Escrevi este post em 2023 com três saídas pra isso. Duas continuam iguais. A terceira eu trocaria.
Retorno antecipado
Começa pelo caso mais comum, uma função que pergunta se o usuário é admin:
public function isAdmin($user) { if (isset($user->role)) { if ($user->role === 'admin') { return 'user is administrator'; } else { return 'user is not administrator'; } } else { return 'role does not exist'; }}
Dois níveis de if e dois else pra responder sim ou não. Invertendo as condições, cada exceção sai logo no começo e o caminho principal fica na última linha, sem indentação:
public function isAdmin(object $user): string{ if (! isset($user->role)) { return 'role does not exist'; } if ($user->role !== 'admin') { return 'user is not administrator'; } return 'user is administrator';}
Enum no lugar da escada de else if
Agora uma função que traduz o código de status de um pedido em nome:
public function getStatusPayment($cart) { if (isset($cart->status)) { if ($cart->status === 0) { return 'Pending'; } else if ($cart->status === 1) { return 'Paid'; } else if ($cart->status === 2) { return 'Delivered'; } else if ($cart->status === 3) { return 'Shipped'; } else if ($cart->status === 4) { return 'Canceled'; } }}
Talvez você diga que ninguém escreve código assim. Escreve, e bastante. Um valor que vira outro é o caso clássico de enum. Desde o PHP 8.1 o enum pode ter valor próprio, e a busca já vem pronta:
enum StatusPayment: int{ case Pending = 0; case Paid = 1; case Delivered = 2; case Shipped = 3; case Canceled = 4;}
public function getStatusPayment(object $cart): string{ if (! isset($cart->status)) { return 'status not defined'; } return StatusPayment::tryFrom($cart->status)?->name ?? 'status does not exist';}
Enum resolve quando a lista é fixa. Se o status vem de uma tabela que alguém edita pelo painel, o lugar dele é o banco.
match no lugar do switch
O terceiro caso é uma sequência de if comparando sempre a mesma variável, um por meio de pagamento: cartão de crédito, boleto, débito e pix. Em 2023 eu sugeri switch. Hoje eu iria de match:
public function payment(object $payment): string{ if (! isset($payment->method)) { return 'payment not defined'; } return match ($payment->method) { 'credit_card' => $this->payWithCreditCard($payment), 'ticket' => $this->payWithTicket($payment), 'debit_card' => $this->payWithDebitCard($payment), 'pix' => $this->payWithPix($payment), default => 'payment method not supported', };}
A diferença que mais pesa é a comparação. O switch usa ==, então a string “1” e o inteiro 1 caem no mesmo case [3]. O match usa ===. E cada braço já devolve o valor, sem break esquecido no meio.
Quando manter o if
Nem todo if precisa sair. Uma condição simples, sem else e sem aninhamento, se lê de primeira. A meta é reduzir os caminhos que quem lê precisa seguir. A palavra if pode ficar.
Perguntas frequentes
Vários returns na mesma função não é ruim?
match substitui todo switch?
Meu projeto ainda está no PHP 7.4. E agora?
Referências
- [1] Documentação oficial · PHP Manual: match (abre em nova aba) php.net/manual/pt_BR/control-structures.match.php
- [2] Documentação oficial · PHP Manual: Backed enumerations (abre em nova aba) php.net/manual/pt_BR/language.enumerations.backed.php
- [3] Documentação oficial · PHP Manual: switch (abre em nova aba) php.net/manual/pt_BR/control-structures.switch.php
- [4] Documentação oficial · PHP Manual: Enumerations (abre em nova aba) php.net/manual/pt_BR/language.enumerations.php
Fontes conferidas em out 2026.