Exceções
Índice
O PHP possui um modelo de exceções similar ao de outras linguagens
de programação. Uma exceção pode ser lançada (throw) e capturada (catch).
Código pode ser envolvido por um bloco try para facilitar a captura
de exceções potenciais. Cada bloco try precisa ter ao menos um bloco
catch ou finally correspondente.
Se uma exceção é lançada e o escopo atual não possui nenhum block catch,
a exceção irá "desempilhar" o call stack pelas funções chamadoras
até encontrar um bloco catch compatível. Todos os blocos finally encontrados
pelo caminho serão executados. Se a pilha é esvaziada até o
escopo global sem encontrar um block catch compatível, o programa irá
terminar com um erro fatal a não ser que um manipulador global de exceções tenha sido configurado.
O objeto lançado precisa ser uma instanceof Throwable.
Tentar lançar um objeto sem essa ascendência resultará em um erro fatal.
A partir do PHP 8.0.0, a palavra chave throw é uma expressão e pode ser utilizada em qualquer
contexto de expressão. Em versões anteriores era considerado uma instrução e portanto precisava constar em sua própria linha.
catch
Um bloco catch define como o código responde a uma exceção lançada. Um bloco catch
define um ou mais tipos de exceções ou erros que ele pode processar, e
opcionalmente uma variável para receber a exceção lançada. (A variável era
requerida anteriormente ao PHP 8.0.0.) O primeiro bloco catch que uma exceção
ou erro lançados encontram que seja compatível com o tipo objeto lançado irá
processar esse objeto.
Múltiplos blocos catch podem ser utilizados para capturar exceções
diferentes. A execução normal (quando nenhuma exceção é lançada dentro de um
try) irão continuar a execução após o último catch definido em sequência.
Exceções podem ser lançadas ou relançadas (throw) dentro um bloco catch. Caso contrário,
a execução irá continuar após o bloco catch que foi acionado.
Quando uma exceção é lançada o código seguinte não é executado,
e o PHP tentará encontrar o primeiro bloco catch coincidente.
Se uma exceção não for capturada, um erro PHP fatal será lançado com a mensagem
"Uncaught Exception ..." na ausência de uma função
definida com set_exception_handler().
A partir do PHP 7.1 um bloco catch pode especificar múltiplas exceções
usando o caractere pipe (|). Isto é útil quando
diferentes exceções de diferentes hierarquias de classes são tratadas
da mesma forma.
A partir do PHP 8.0.0, o nome de variável que captura a exceção é opcional.
Se não especificada, o bloco catch compatível ainda executará, mas não
terá acesso ao objeto lançado.
finally
Um bloco finally pode ser especificado após
ou no lugar de blocos catch. Códigos dentro de finally sempre serão
executados depois do try ou catch, independente se houve o
lançamento de uma exceção, e antes que a execução normal continue.
Uma interação notável ocorre entre um bloco finally e a instrução return.
Se uma instrução return é encontrada dentro um bloco try ou catch,
o bloco finally ainda assim será executado. Além disso, a instrução return é avaliada
no ponto que é encontrada, mas o resultado só será retornado após o bloco finally
ser executado. Se o bloco finally também tiver uma instrução return,
o valor da instrução de finally que será retornado.
Outra interação notável é entre uma exceção lançada de dentro de um bloco try
e uma lançada de dentro de um bloco finally. Se ambos os blocos lançarem uma exceção,
a que for lançada de dentro do bloco finally será a propagada,
e a lançada de dentro do bloco try será usada como sua exceção anterior.
Manipulador de exceções global
Se uma exceção alcança o escopo global, ela ainda pode ser capturada
por um manipulador global de exceções, opcional. A função set_exception_handler()
pode configurar uma função que será chamada no lugar de um bloco catch na ausẽncia
de outros blocos. O efeito é essencialmente o mesmo que ter o programa inteiro
envelopado dentro de um bloco try-catch e a função informada funcionando como o catch.
Notas
Nota:
Funções internas ao PHP utilizam principalmente
aviso de erros. Apenas extensões
orientadas a objetos
utilizam exceções. No entanto, os erros podem ser transformados em
exceções com ErrorException.
Essa técnica não funciona para erros fatais.
Exemplo #1 Convertendo avisos de erros em exceções
<?php
function exceptions_error_handler($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
}
set_error_handler('exceptions_error_handler');
?>
Exemplos
Exemplo #2 Lançando uma exceção
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Divisão por zero.');
}
return 1/$x;
}
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Exceção capturada: ', $e->getMessage(), "\n";
}
// A execução continua
echo "Olá mundo\n";
?>
O exemplo acima produzirá:
0.2
Exceção capturada: Divisão por zero.
Olá mundo
Exemplo #3 Manipulação de exceções com bloco finally
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Divisão por zero.');
}
return 1/$x;
}
try {
echo inverse(5) . "\n";
} catch (Exception $e) {
echo 'Exceção capturada: ', $e->getMessage(), "\n";
} finally {
echo "Primeiro finaly.\n";
}
try {
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Exceção capturada: ', $e->getMessage(), "\n";
} finally {
echo "Segundo finally.\n";
}
// A execução continua
echo "Olá mundo\n";
?>
O exemplo acima produzirá:
0.2
Primeiro finally.
Exceção capturada: Divisão por zero.
Segundo finally.
Olá mundo
Exemplo #4 Interação entre blocos finally e return
<?php
function test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo test();
?>
O exemplo acima produzirá:
Exemplo #5 Exceção aninhada
<?php
class MyException extends Exception { }
class Test {
public function testing() {
try {
try {
throw new MyException('foo!');
} catch (MyException $e) {
// rethrow it
throw $e;
}
} catch (Exception $e) {
var_dump($e->getMessage());
}
}
}
$foo = new Test;
$foo->testing();
?>
O exemplo acima produzirá:
Exemplo #6 Manipulação de múltiplas exceções no mesmo bloco catch
<?php
class MyException extends Exception { }
class MyOtherException extends Exception { }
class Test {
public function testing() {
try {
throw new MyException();
} catch (MyException | MyOtherException $e) {
var_dump(get_class($e));
}
}
}
$foo = new Test;
$foo->testing();
?>
O exemplo acima produzirá:
Exemplo #7 Omissão da variável de captura de exceções
Permitido a partir do PHP 8.0.0.
<?php
class SpecificException extends Exception {}
function test() {
throw new SpecificException('Oopsie');
}
try {
test();
} catch (SpecificException) {
print "Uma exceção SpecificException foi lançada, mas os detalhes não importam.";
}
?>
O exemplo acima produzirá:
Uma exceção SpecificException foi lançada, mas os detalhes não importam.
Exemplo #8 Throw como uma expressão
Permitido apenas no PHP 8.0.0 e posteriores.
<?php
function test() {
do_something_risky() or throw new Exception('Não funcionou');
}
function do_something_risky() {
return false; // Simula falha
}
try {
test();
} catch (Exception $e) {
print $e->getMessage();
}
?>
O exemplo acima produzirá:
Exemplo #9 Exceção em bloco try e bloco finally
<?php
try {
try {
throw new Exception(message: 'Terceira', previous: new Exception('Quarta'));
} finally {
throw new Exception(message: 'Primeira', previous: new Exception('Segunda'));
}
} catch (Exception $e) {
var_dump(
$e->getMessage(),
$e->getPrevious()->getMessage(),
$e->getPrevious()->getPrevious()->getMessage(),
$e->getPrevious()->getPrevious()->getPrevious()->getMessage(),
);
}
O exemplo acima produzirá:
string(5) "Primeira"
string(6) "Segunda"
string(5) "Terceira"
string(6) "Quarta"