7. Exceções e erros
Quando um método de uma classe encontra um erro irrecuperável (ficheiro inexistente, base de dados não ligada, ligação de rede indisponível), não apresenta um erro numa consola (ficheiro, base de dados), mas lança uma exceção. Todas as exceções estendem a classe [\Exception]. Para além das exceções, o funcionamento interno da classe PHP também gera erros cuja classe base é a classe [\Error]. Ambas as classes implementam a interface PHP [\Throwable].
7.1. A estrutura hierárquica dos scripts

7.2. A interface [\Throwable]
A interface [\Throwable] é a seguinte:

A função dos métodos da interface é a seguinte:

7.3. As exceções predefinidas em PHP 7
PHP 7 definem várias classes de exceções:

- em [1], as exceções predefinidas em PHP;
- Na biblioteca [2], as exceções da biblioteca SPL (Biblioteca Padrão PHP) da PHP 7. A biblioteca SPL é uma coleção de classes e interfaces destinadas a resolver problemas frequentemente encontrados pelos programadores.
7.4. Os erros predefinidos na PHP 7
PHP 7 define várias classes de erros:

A classe [\Error] é a classe pai de todos os erros predefinidos em PHP. A classe [ErrorException] permite encapsular uma instância da classe [\Error] numa instância da classe [\Exception]. Isto permite uniformizar a gestão de erros, tratando apenas de exceções.
7.5. Exemplo 1
O primeiro exemplo, [exceptions-01.php], apresenta simultaneamente erros PHP e uma exceção:
<?php
// exibição de todos os erros
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// código --------
$var=[];
// chave desconhecida
print $var["abcd"];
// divisão por zero
$var=7/0;
var_dump($var);
// matriz com limites fixos
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// índice fora dos limites
$array[5]=8;
Comentários
- linha 4: solicita-se ao PHP que sinalize todos os erros. O segundo parâmetro é o nível de erros solicitado:


- linha 5: solicita-se que os erros sejam apresentados na consola;
- linha 9: acede-se a um elemento inexistente da tabela [$var];
- linha 11: é efetuada uma divisão por zero;
- linha 14: cria-se uma instância da classe [SplFixedArray]. Esta classe permite criar um tabuleiro com limites fixos e índices inteiros;
- linha 18: acede-se a um elemento inexistente da matriz;
Resultados
Comentários
- linha 1 dos resultados: aceder a uma chave inexistente de um tabuleiro provoca um erro PHP de nível [E_NOTICE]. Isto não interrompe a execução do script;
- linha 3 dos resultados: dividir um número por zero provoca um erro PHP de nível [E_WARNING]. Isto não interrompe a execução do script;
- linhas 6-9 dos resultados: aceder a um índice inexistente de uma matriz [SplFixedArray] provoca uma exceção do tipo [RuntimeException] e interrompe a execução do script;
7.6. Gerir exceções
O script [exceptions-02.php] mostra como gerir as exceções:
<?php
// são apresentados todos os erros
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// o código é envolvido por um try / catch
try {
$var = [];
// chave desconhecida
print $var["abcd"];
// divisão por zero
$var = 7 / 0;
var_dump($var);
// matriz com limites fixos
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// índice fora dos limites
$array[5] = 8;
// verificação
print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
// \Throwable é a interface implementada pela maioria dos erros e exceções
// a exceção é apresentada
print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}
Comentários
- o script é o mesmo que foi apresentado no parágrafo anterior. Só que agora o código das linhas 8-19, suscetível de provocar erros, foi envolvido por uma estrutura try/catch: se o código das linhas 8-21 provocar (lançar) uma exceção ou um erro, este será gerido pela cláusula catch das linhas 22-26;
- linha 22: o parâmetro da cláusula [catch] é o tipo de exceção ou erro que se pretende gerir. Ao definir como tipo [\Throwable], que é uma interface, indica-se que se pretende gerir qualquer instância de classe que implemente a interface [\Throwable]. Como todas as classes de erros e exceções implementam esta interface, a cláusula [catch] gere aqui qualquer erro/exceção encapsulada numa classe;
- linha 19: a instrução que desencadeia o erro e dá origem à exceção. Assim que ocorre uma exceção, há um desvio para a cláusula [catch]. O código a seguir à linha 19 não será, portanto, executado;
Resultados
Comentários aos resultados
- linhas 1 e 3: encontram-se os erros de nível [E_NOTICE] e [E_WARNING]. Estes erros não são exceções e, por isso, não são geridos pela cláusula [catch];
- linha 5: temos a mensagem de erro indicada na cláusula [catch]. Por conseguinte, ocorreu uma exceção derivada de [\Exception] ou um erro derivado de [\Error]. Vemos aqui que se trata da classe [\RuntimeException];
7.7. Parâmetros da cláusula [catch]
Analisemos o seguinte script [exceptions-03.php]:
<?php
// são apresentados todos os erros
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// um array com limites fixos
$array = new \SplFixedArray(5);
try {
// índice fora dos limites
$array[5] = 8;
} catch (\Throwable $ex) {
// exibição de mensagem de erro
print "Erreur 1 : " . $ex->getMessage() . "\n";
}
try {
// índice fora dos limites
$array[5] = 8;
} catch (\Exception $ex) {
// exibição de mensagem de erro
print "Erreur 2 : " . $ex->getMessage() . "\n";
}
try {
// valor fora dos limites
$array[5] = 8;
} catch (\RuntimeException $ex) {
// exibição de mensagem de erro
print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
// divisão por 0
intdiv(5, 0);
} catch (\Throwable $ex) {
// exibição de mensagem de erro
print "Erreur 4 : " . $ex->getMessage() . "\n";
}
try {
// divisão por 0
intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
// exibição de mensagem de erro
print "Erreur 5 : " . $ex->getMessage() . "\n";
}
try {
// divisão por 0
intdiv(5, 0);
} catch (\Error $ex) {
// exibição de mensagem de erro
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
try {
// divisão por 0
intdiv(5, 0);
} catch (\Exception $ex) {
// exibição de mensagem de erro
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
Comentários
- linhas 8-31: três formas diferentes de gerir a exceção gerada pela utilização de um índice incorreto com a classe [\SplFixedArray]. Vimos que este erro gerava uma exceção do tipo [RuntimeException];
- linha 12: trata um erro do tipo [\Throwable]. Isto é válido, uma vez que o tipo [RuntimeException] deriva do tipo [\Exception], que implementa a interface [\Throwable];
- linha 20: trata um erro do tipo [\Exception]. Isto é válido, uma vez que o tipo [RuntimeException] deriva do tipo [\Exception];
- linha 28: trata um erro do tipo [\RuntimeException]. Este é o método a privilegiar, uma vez que corresponde exatamente ao tipo da exceção gerada;
- linhas 32-62: 4 formas diferentes de gerir a exceção gerada pela função [intdiv] quando se passa a esta função um divisor igual a 0. A função [ intdiv ( int $dividend , int $divisor ) : int] realiza a divisão inteira $dividend / $divisor. Quando o divisor é nulo, é lançada a exceção [\DivisionByzeroError];
- linha 35: intercepta-se qualquer erro que implemente a interface [\Throwable]. Isto é válido;
- linha 43: intercepta-se o tipo exato do erro: este é o método a privilegiar;
- linha 51: intercepta-se o tipo [\Error]. Isto é válido, uma vez que a classe [DivisionByzeroError] estende a classe [Error];
- linha 59: intercepta-se o tipo [\Exception]. Isto é inválido, pois a classe [DivisionByzeroError] não tem qualquer ligação com a classe [\Exception];
Resultados
7.8. Cláusula [finally]
A estrutura try/catch pode ter um terceiro elemento e tornar-se uma estrutura try/catch/finally. O código da cláusula [finally] é executado nos dois casos seguintes:
- a cláusula [try] não lança uma exceção. É, então, executada na íntegra e, em seguida, a execução do código passa para a cláusula [finally], que é executada na íntegra;
- a cláusula [try] lança uma exceção. É então executada até à instrução que lança a exceção. A execução do código passa então para a cláusula [catch], que é executada na íntegra. Em seguida, a execução do código passa para a cláusula [finally], que é executada na íntegra;
Por fim, o código da cláusula [finally] continua a ser executado. Este cenário é útil no seguinte caso:
- na cláusula [try], o código obteve recursos (ficheiros, bases de dados, ligações de rede, filas). Em geral, estes recursos consomem muita memória. Por isso, devem ser devolvidos (o que se costuma chamar de «fechar») assim que possível;
- se a aquisição dos recursos tiver sido efetuada no [try], a sua devolução será realizada no [finally]. Isto garante que, em todos os casos (com ou sem erro), os recursos adquiridos sejam devolvidos ao sistema;
O seguinte script [exemples/exceptions/exceptions-04.php] mostra-nos o funcionamento da cláusula [finally] em diversas situações:
<?php
// ou cria uma instância de exceção
$e = new \Exception("Erreur…");
var_dump($e);
// primeiro teste
try {
print "Premier test\n";
throw $e;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// segundo teste
try {
print "Second test\n";
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// terceiro teste
try {
print "Troisième test\n";
return;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
Comentários ao código
- linha 4: $e é uma instância da classe predefinida [\Exception]. Iremos executá-la em diferentes pontos;
- linhas 8-15: a exceção $e é lançada no [try] (linha 10);
- linha 11: a exceção [\Exception] é interceptada e a sua mensagem de erro é gravada na consola;
- linhas 13-15: a cláusula [finally] escreve uma mensagem. De acordo com o que foi referido anteriormente, esta mensagem deveria ser sempre escrita, independentemente de haver ou não um erro no [try];
- linhas 18-24: não há erro no [try]. Aqui também se deveria passar para o [finally];
- linhas 27-34: existe uma instrução [return] no «try» e não há erro. Podemos então questionar-nos se se vai passar para a cláusula [finally]. A execução mostra que sim;
Resultados
Analisemos outro caso, o [exceptions-05.php]:
<?php
// quarto teste
try {
print "Quatrième test\n";
exit;
} finally {
print "Terminé\n";
}
Comentários
- linha 6: a instrução [exit] interrompe imediatamente a execução do script: a cláusula [finally] não é executada;
- linhas 4-9: um exemplo de try / catch / finally sem a cláusula [catch]. É possível;
Resultados
7.9. Criar as suas próprias classes de exceções
Num projeto de alguma envergadura, é útil diferenciar os vários erros, encapsulando-os em diferentes classes de exceções. No script anterior, vimos que qualquer exceção podia ser interceptada por uma cláusula [catch (\Throwable]. Isto é recomendado se não se tiver qualquer ideia do erro interceptado e se o tratamento for o mesmo para todos os erros. Por vezes, é esse o caso, mas muitas vezes é necessário adaptar o tratamento ao tipo exato do erro. Nesse caso, é preciso diferenciar os erros entre si.
Analisemos o seguinte script [exceptions-06.php]:
<?php
// definimos a nossa própria família de exceções
class Exception1 extends \RuntimeException {
}
class Exception2 extends \RuntimeException {
}
// ou utilizamos as nossas exceções
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);
// primeiro teste
print ("premier test\n");
try {
// lançamos um tipo Exception1
throw $e1;
} catch (Exception1 $ex1) {
print "Exception 1" . "\n";
print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
print "Exception 2" . "\n";
print $ex2->getMessage() . "\n";
}
// segundo teste
print ("second test\n");
try {
// lançamos um tipo Exception2
throw $e2;
} catch (Exception1 $ex1) {
print "Exception 1" . "\n";
print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
print "Exception 2" . "\n";
print $ex2->getMessage() . "\n";
}
// terceiro teste
print ("troisième test\n");
try {
// lança-se um tipo Exception1
throw $e1;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
// quarto teste
print ("quatrième test\n");
try {
// lança-se um tipo Exception2
throw $e2;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
Comentários
- linhas 4-10: definem-se duas classes, [Exception1] e [Exception2], ambas derivadas da classe predefinida [\RuntimeException]. O corpo destas classes está vazio. Por outras palavras, utilizam-se apenas pelos seus tipos: é precisamente porque têm tipos diferentes que será possível distinguir estas duas exceções nas cláusulas [catch];
- linhas 13-16: definem-se duas variáveis, $e1 e $e2, com os tipos [Exception1] e [Exception2], respetivamente;
- linhas 20-29: existe uma estrutura try / catch / catch. Isto permite gerir diferentes tipos de exceção com diferentes cláusulas [catch];
- linha 23: intercepta as exceções do tipo [Exception1];
- linha 26: intercepta as exceções do tipo [Exception2];
- linha 49: intercepta as exceções do tipo [Exception1] ou (|) [Exception2];
Resultados
Comentários aos resultados
- linhas 1-17: o «conteúdo» de uma exceção:
- linhas 2-3: a mensagem de erro;
- linhas 6-7: o código de erro;
- linhas 8-9: o nome do ficheiro em que ocorreu a exceção;
- linhas 10-11: a linha em que ocorreu a exceção;
- linhas 15-16: a exceção anterior. Uma exceção pode encapsular outra exceção, definindo assim uma pilha de exceções. O atributo [previous] permitirá explorar essa pilha;
7.10. Reativar uma exceção
Uma exceção pode ser lançada várias vezes, como mostra o seguinte script [exceptions-07.php]:
<?php
try {
try {
// lança-se uma exceção
throw new \Exception("test");
} catch (\Exception $ex) {
// relança-se a exceção interceptada
throw $ex;
} finally {
// entraremos corretamente no finally
print "finally 1\n";
}
} catch (\Exception $ex2) {
// a exceção inicial é recuperada corretamente
print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
// passaremos corretamente para o finally
print "finally 2\n";
}
Comentários
- linha 6: lança-se uma exceção;
- linha 7: a exceção é interceptada;
- linha 9: relança-se a exceção. Esta passa então para a estrutura try / catch / finally do nível superior;
- linha 14: é novamente interceptada;
- linhas 10-12: a execução mostra que, mesmo após o [throw] da linha 9, se passa efetivamente para a cláusula [finally] do try / catch / finally;
Resultados
7.11. Exploração de uma pilha de exceções
Uma exceção pode encapsular outra exceção, que por sua vez pode encapsular outra, formando, por fim, uma pilha de exceções. Eis um exemplo [exceptions-08.php]:
Comentários
- linhas 4-14: definem três classes de exceções derivadas da exceção predefinida [RuntimeException];
- linha 17: uma instância da classe [Exception3] está encapsulada numa instância da classe [Exception2], que, por sua vez, está encapsulada numa instância da classe [Exception1]. O construtor utilizado aqui é o construtor da classe [Exception]:
![]()
O terceiro parâmetro do construtor permite encapsular uma exceção. Isto pode ser útil no seguinte cenário:
- define-se um método M que pode gerar uma exceção do tipo [Exception1] e apenas desse tipo, por razões de compatibilidade, por exemplo, com uma interface;
- no entanto, no método M podem ocorrer outros tipos de exceções. Para reportar um erro ao código que chama o método M, encapsularemos então essas exceções no tipo [Exception1], que será lançado. Isto permite não perder a informação contida na exceção encapsulada e que foi a causa original do erro;
- as linhas 20-27 mostram como gerir a pilha de exceções internas a uma exceção;
Resultados
object(Exception1)#1 (7) {
["message":protected]=>
string(11) "Erreur 1…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(1)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception2)#2 (7) {
["message":protected]=>
string(11) "Erreur 2…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(2)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception3)#3 (7) {
["message":protected]=>
string(11) "Erreur 3…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
NULL
}
}
}
Erreur 1…
Erreur 2…
Erreur 3…