Menu

PHP try catch: capturar exceções, finally e Throwable

No PHP, código que pode falhar vai num bloco try { }, e catch (Exception $e) { } roda se ele lançar uma exceção, com o motivo em $e->getMessage(). Veja como capturar vários tipos de exceção, finally, por que catch (Exception) não pega TypeError nem DivisionByZeroError (capture Throwable), relançar e um handler global.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

No PHP, você coloca o código que pode falhar dentro de try { ... } e trata a falha em catch (Exception $e) { ... }. Se qualquer coisa no bloco try lançar uma exceção, o PHP pula direto para o catch correspondente, e $e->getMessage() diz o que deu errado. Se nada for lançado, o bloco catch é pulado.

Troque divide(10, 0) por divide(10, 5) e rode de novo: nenhuma exceção, então not printed é impresso e o bloco catch é pulado. Sem try, a exceção encerraria o script com um erro fatal "Uncaught". Como lançar as suas próprias exceções e escrever classes de exceção está na página sobre exceções.

A sintaxe do try catch

A forma completa tem três partes. Só o try é obrigatório, mais pelo menos um catch ou um finally:

try {
    // code that may throw
} catch (SomeException $e) {
    // runs if SomeException (or a subclass) was thrown
} catch (OtherException | ThirdException $e) {
    // runs for either of these types
} finally {
    // always runs, thrown or not
}

O tipo no catch decide o que ele captura: a própria classe e todas as classes que a estendem. Desde o PHP 8.0, a variável é opcional quando você não precisa dela: catch (JsonException) { ... }.

Capturar Exception, Error ou Throwable

Desde o PHP 7, falhas que o próprio PHP detecta (chamar um método que não existe, um tipo de argumento errado, divisão por zero) lançam um Error, não uma Exception, e o PHP 8 transformou muitos warnings antigos em TypeError e ValueError. Os dois implementam a interface Throwable, mas nenhum estende o outro, então catch (Exception $e) não captura um Error:

Throwable
├── Exception                 (problems a program expects and handles)
│   ├── InvalidArgumentException, RuntimeException, JsonException, ...
└── Error                     (engine errors, usually a bug in the code)
    ├── TypeError
    ├── ValueError
    ├── ArithmeticError
    │   └── DivisionByZeroError
    └── UnhandledMatchError, ...

Rode isto para ver qual catch trata cada problema:

Então qual capturar?

  • catch (SomeSpecificException $e) quando você sabe o que pode dar errado e o que fazer a respeito. Isso é a maior parte do código.
  • catch (Exception $e) para "qualquer coisa que uma biblioteca ou o meu código lançou de propósito".
  • catch (Throwable $e) no nível mais externo (um handler de requisições, um executor de jobs) para registrar toda falha, incluindo os erros do próprio PHP, e mostrar uma mensagem amigável.

Capturar vários tipos de exceção

Escreva vários blocos catch para tratar tipos diferentes de jeitos diferentes. O PHP os testa de cima para baixo e usa o primeiro que bate, então um tipo específico precisa vir antes da classe pai, senão a classe pai captura tudo primeiro. Quando vários tipos recebem o mesmo tratamento, junte-os num catch com |:

As três classes estendem Exception, então um único catch (Exception $e) também as capturaria, mas aí o código não conseguiria mais responder 404 para uma e 400 para as outras.

finally: código que sempre roda

Um bloco finally roda depois de try e catch, não importa como terminaram: normalmente, com uma exceção capturada, com uma exceção que ninguém capturou ou com um return. É o lugar para liberar o que o bloco try adquiriu: fechar um arquivo, liberar um lock, parar um cronômetro.

Para good.txt, o arquivo é fechado antes de saved ser impresso, porque o finally roda antes de a função de fato retornar. Para bad.txt não há catch dentro de process(): o finally fecha o arquivo, e depois a exceção continua até o catch de quem chamou.

Evite return dentro do finally: ele substitui o valor que o bloco try retornou e descarta em silêncio uma exceção que estava a caminho:

function f(): string
{
    try {
        return 'from try';
    } finally {
        return 'from finally';
    }
}

function g(): string
{
    try {
        throw new RuntimeException('lost');
    } finally {
        return 'no exception reaches the caller';
    }
}

echo f(); // from finally
echo g(); // no exception reaches the caller

Relançar ou envolver uma exceção

Às vezes um bloco catch só consegue tratar parte de um problema: registrar, limpar e depois deixá-lo seguir. throw $e; relança a mesma exceção. Mais comum é você querer adicionar contexto mantendo a causa original, e é para isso que serve o terceiro argumento do construtor, $previous:

Quem chama lida com uma ConfigException e não precisa saber que a configuração é JSON, enquanto getPrevious() guarda o detalhe de baixo nível para o log.

Exceções não capturadas e um handler global

Uma exceção que nenhum catch trata encerra o script com um erro fatal que inclui a classe, a mensagem, o arquivo e a linha, e um stack trace:

<?php
// No try/catch anywhere in this file
function charge(int $cents): void
{
    if ($cents <= 0) {
        throw new DomainException("Amount must be positive, got $cents");
    }
}

charge(500);
echo "first charge ok\n";
charge(-1);
echo "never printed\n";

O PHP imprime first charge ok e depois:

PHP Fatal error:  Uncaught DomainException: Amount must be positive, got -1 in /home/index.php:6
Stack trace:
#0 /home/index.php(12): charge()
#1 {main}
  thrown in /home/index.php on line 6

Num site no ar, essa mensagem nunca deve chegar aos visitantes. set_exception_handler() registra uma função que recebe qualquer exceção que ninguém capturou, um único lugar para registrá-la e imprimir uma página educada:

O handler roda e depois o script para; não há como continuar depois do throw. Se o texto de erro do próprio PHP aparece ou não na página é controlado pelo display_errors, explicado em exibição de erros.

Warnings não são exceções

O try/catch só vê o que é lançado. Muitas funções antigas do PHP não lançam nada: retornam false ou null e, no máximo, imprimem um warning. O json_decode() é um caso típico, e envolvê-lo num try não muda nada até você pedir que ele lance exceções:

Para funções sem essa flag, verifique o valor de retorno (if ($handle === false)) ou verifique a pré-condição antes (file_exists(), isset()). Um set_error_handler() que lança ErrorException transforma todo warning numa exceção de verdade, se você quiser uma regra única para tudo.

Receita: validar entrada de formulário com exceções

Um padrão prático: as funções de validação lançam exceções, a página as captura e mostra a mensagem ao lado do formulário. Rode, digite uma idade no formulário e clique em Send: o mesmo script roda de novo com $_POST preenchido.

Teste um campo vazio, twelve, 9 e 30. Cada regra que falha lança uma exceção com a própria mensagem, e um único catch transforma todas na mesma linha vermelha. A página sobre formulários parte daqui com vários campos e erros ao lado de cada um.

Perguntas frequentes

Como funciona o try catch no PHP?

O PHP executa o código dentro de try { }. Se algo ali lançar uma exceção, o PHP pula o resto do bloco try e executa o primeiro bloco catch cujo tipo bate, com o objeto lançado na variável: catch (Exception $e) { echo $e->getMessage(); }. Se nada for lançado, os blocos catch são pulados.

Como capturo todas as exceções e erros no PHP?

Capture Throwable: catch (Throwable $e). catch (Exception $e) só captura exceções, não erros do motor como TypeError, ValueError ou DivisionByZeroError, que estendem Error. Tanto Exception quanto Error implementam Throwable.

Como capturo várias exceções num único bloco catch no PHP?

Separe os tipos com uma barra vertical: catch (InvalidArgumentException | RangeException $e). Você também pode escrever vários blocos catch em sequência; o PHP usa o primeiro que bate, então coloque os tipos mais específicos primeiro.

O finally roda se houver um return no try?

Sim. O finally roda depois de try e catch, tenha o código retornado, lançado uma exceção ou terminado normalmente, inclusive quando o try contém return. Se o próprio finally retornar um valor, esse valor substitui o do try, então evite return dentro do finally.

O try catch captura warnings no PHP?

Não. Warnings e notices (como ler uma chave de array ausente) não são exceções, então o catch nunca os vê. Verifique a condição antes da chamada, use o modo de exceção da função quando ela tiver um (json_decode(..., flags: JSON_THROW_ON_ERROR)), ou converta os warnings com set_error_handler() e ErrorException.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR