PHP Conference Japan 2024

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 blocks 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 doPHP 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 blococatch 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.

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";
}

// 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";
}

// 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á:

finally

Exemplo #5 Exceções alinhadas

<?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á:

string(4) "foo!"

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á:

string(11) "MyException"

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.";
}
?>

Exemplo #8 Throw as an expression

Only permitted in PHP 8.0.0 and later.

<?php

function test() {
do_something_risky() or throw new Exception('It did not work');
}

try {
test();
} catch (
Exception $e) {
print
$e->getMessage();
}
?>
adicione uma nota

Notas Enviadas por Usuários (em inglês) 13 notes

up
125
ask at nilpo dot com
15 years ago
If you intend on creating a lot of custom exceptions, you may find this code useful. I've created an interface and an abstract exception class that ensures that all parts of the built-in Exception class are preserved in child classes. It also properly pushes all information back to the parent constructor ensuring that nothing is lost. This allows you to quickly create new exceptions on the fly. It also overrides the default __toString method with a more thorough one.

<?php
interface IException
{
/* Protected methods inherited from Exception class */
public function getMessage(); // Exception message
public function getCode(); // User-defined Exception code
public function getFile(); // Source filename
public function getLine(); // Source line
public function getTrace(); // An array of the backtrace()
public function getTraceAsString(); // Formated string of trace

/* Overrideable methods inherited from Exception class */
public function __toString(); // formated string for display
public function __construct($message = null, $code = 0);
}

abstract class
CustomException extends Exception implements IException
{
protected
$message = 'Unknown exception'; // Exception message
private $string; // Unknown
protected $code = 0; // User-defined exception code
protected $file; // Source filename of exception
protected $line; // Source line of exception
private $trace; // Unknown

public function __construct($message = null, $code = 0)
{
if (!
$message) {
throw new
$this('Unknown '. get_class($this));
}
parent::__construct($message, $code);
}

public function
__toString()
{
return
get_class($this) . " '{$this->message}' in {$this->file}({$this->line})\n"
. "{$this->getTraceAsString()}";
}
}
?>

Now you can create new exceptions in one line:

<?php
class TestException extends CustomException {}
?>

Here's a test that shows that all information is properly preserved throughout the backtrace.

<?php
function exceptionTest()
{
try {
throw new
TestException();
}
catch (
TestException $e) {
echo
"Caught TestException ('{$e->getMessage()}')\n{$e}\n";
}
catch (
Exception $e) {
echo
"Caught Exception ('{$e->getMessage()}')\n{$e}\n";
}
}

echo
'<pre>' . exceptionTest() . '</pre>';
?>

Here's a sample output:

Caught TestException ('Unknown TestException')
TestException 'Unknown TestException' in C:\xampp\htdocs\CustomException\CustomException.php(31)
#0 C:\xampp\htdocs\CustomException\ExceptionTest.php(19): CustomException->__construct()
#1 C:\xampp\htdocs\CustomException\ExceptionTest.php(43): exceptionTest()
#2 {main}
up
12
tianyiw at vip dot qq dot com
1 year ago
Easy to understand `finally`.
<?php
try {
try {
echo
"before\n";
1 / 0;
echo
"after\n";
} finally {
echo
"finally\n";
}
} catch (
\Throwable) {
echo
"exception\n";
}
?>
# Print:
before
finally
exception
up
80
Johan
13 years ago
Custom error handling on entire pages can avoid half rendered pages for the users:

<?php
ob_start
();
try {
/*contains all page logic
and throws error if needed*/
...
} catch (
Exception $e) {
ob_end_clean();
displayErrorPage($e->getMessage());
}
?>
up
6
jlherren
10 months ago
As noted elsewhere, throwing an exception from the `finally` block will replace a previously thrown exception. But the original exception is magically available from the new exception's `getPrevious()`.

<?php
try {
try {
throw new
RuntimeException('Exception A');
} finally {
throw new
RuntimeException('Exception B');
}
}
catch (
Throwable $exception) {
echo
$exception->getMessage(), "\n";
// 'previous' is magically available!
echo $exception->getPrevious()->getMessage(), "\n";
}
?>

Will print:

Exception B
Exception A
up
24
Shot (Piotr Szotkowski)
16 years ago
‘Normal execution (when no exception is thrown within the try block, *or when a catch matching the thrown exception’s class is not present*) will continue after that last catch block defined in sequence.’

‘If an exception is not caught, a PHP Fatal Error will be issued with an “Uncaught Exception …” message, unless a handler has been defined with set_exception_handler().’

These two sentences seem a bit contradicting about what happens ‘when a catch matching the thrown exception’s class is not present’ (and the second sentence is actually correct).
up
15
christof+php[AT]insypro.com
7 years ago
In case your E_WARNING type of errors aren't catchable with try/catch you can change them to another type of error like this:

<?php
set_error_handler
(function($errno, $errstr, $errfile, $errline){
if(
$errno === E_WARNING){
// make it more serious than a warning so it can be caught
trigger_error($errstr, E_ERROR);
return
true;
} else {
// fallback to default php error handler
return false;
}
});

try {
// code that might result in a E_WARNING
} catch(Exception $e){
// code to handle the E_WARNING (it's actually changed to E_ERROR at this point)
} finally {
restore_error_handler();
}
?>
up
15
daviddlowe dot flimm at gmail dot com
7 years ago
Starting in PHP 7, the classes Exception and Error both implement the Throwable interface. This means, if you want to catch both Error instances and Exception instances, you should catch Throwable objects, like this:

<?php

try {
throw new
Error( "foobar" );
// or:
// throw new Exception( "foobar" );
}
catch (
Throwable $e) {
var_export( $e );
}

?>
up
25
Edu
11 years ago
The "finally" block can change the exception that has been throw by the catch block.

<?php
try{
try {
throw new
\Exception("Hello");
} catch(
\Exception $e) {
echo
$e->getMessage()." catch in\n";
throw
$e;
} finally {
echo
$e->getMessage()." finally \n";
throw new
\Exception("Bye");
}
} catch (
\Exception $e) {
echo
$e->getMessage()." catch out\n";
}
?>

The output is:

Hello catch in
Hello finally
Bye catch out
up
15
Simo
9 years ago
#3 is not a good example. inverse("0a") would not be caught since (bool) "0a" returns true, yet 1/"0a" casts the string to integer zero and attempts to perform the calculation.
up
16
telefoontoestel at nospam dot org
10 years ago
When using finally keep in mind that when a exit/die statement is used in the catch block it will NOT go through the finally block.

<?php
try {
echo
"try block<br />";
throw new
Exception("test");
} catch (
Exception $ex) {
echo
"catch block<br />";
} finally {
echo
"finally block<br />";
}

// try block
// catch block
// finally block
?>

<?php
try {
echo
"try block<br />";
throw new
Exception("test");
} catch (
Exception $ex) {
echo
"catch block<br />";
exit(
1);
} finally {
echo
"finally block<br />";
}

// try block
// catch block
?>
up
11
mlaopane at gmail dot com
6 years ago
<?php

/**
* You can catch exceptions thrown in a deep level function
*/

function employee()
{
throw new
\Exception("I am just an employee !");
}

function
manager()
{
employee();
}

function
boss()
{
try {
manager();
} catch (
\Exception $e) {
echo
$e->getMessage();
}
}

boss(); // output: "I am just an employee !"
up
8
Tom Polomsk
10 years ago
Contrary to the documentation it is possible in PHP 5.5 and higher use only try-finally blocks without any catch block.
up
8
Sawsan
13 years ago
the following is an example of a re-thrown exception and the using of getPrevious function:

<?php

$name
= "Name";

//check if the name contains only letters, and does not contain the word name

try
{
try
{
if (
preg_match('/[^a-z]/i', $name))
{
throw new
Exception("$name contains character other than a-z A-Z");
}
if(
strpos(strtolower($name), 'name') !== FALSE)
{
throw new
Exception("$name contains the word name");
}
echo
"The Name is valid";
}
catch(
Exception $e)
{
throw new
Exception("insert name again",0,$e);
}
}

catch (
Exception $e)
{
if (
$e->getPrevious())
{
echo
"The Previous Exception is: ".$e->getPrevious()->getMessage()."<br/>";
}
echo
"The Exception is: ".$e->getMessage()."<br/>";
}

?>
To Top