Skip to content

7. Exceções e erros

Quando um método de uma classe encontra um erro irrecuperável (arquivo inexistente, banco de dados desconectado, conexão de rede indisponível), ele não exibe um erro no console (arquivo, banco de dados), mas lança uma exceção. Todas as exceções estendem a classe [\Exception]. Além das exceções, o funcionamento interno de PHP também gera erros cuja classe base é a classe [\Error]. Ambas as classes implementam a interface PHP [\Throwable].

7.1. A árvore de scripts

Image

7.2. A interface [\Throwable]

A interface [\Throwable] é a seguinte:

Image

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

Image

7.3. As exceções predefinidas em PHP 7

PHP 7 definem várias classes de exceções:

Image

  • 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 desenvolvedores.

7.4. Os erros predefinidos na PHP 7

PHP 7 define várias classes de erros:

Image

A classe [\Error] é a classe pai de todos os erros predefinidos em PHP. A classe [ErrorException] permite encapsular uma instância da classe [\Error] em uma instância da classe [\Exception]. Isso permite uniformizar o gerenciamento de erros, tratando apenas exceções.

7.5. Exemplo 1

O primeiro exemplo, [exceptions-01.php], mostra tanto erros PHP quanto 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:

Image

Image

  • linha 5: solicita-se que os erros sejam exibidos no console;
  • linha 9: acessa-se um elemento inexistente da matriz [$var];
  • linha 11: é feita uma divisão por zero;
  • linha 14: cria-se uma instância da classe [SplFixedArray]. Essa classe permite criar um array com limites fixos e índices inteiros;
  • linha 18: acessa-se um elemento inexistente da matriz;

Resultados

1
2
3
4
5
6
7
8
9
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 9

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 11
float(INF)

Fatal error: Uncaught RuntimeException: Index invalid or out of range in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php:18
Stack trace:
#0 {main}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 18

Comentários

  • linha 1 dos resultados: acessar uma chave inexistente de um array gera um erro PHP de nível [E_NOTICE]. Isso não interrompe a execução do script;
  • linha 3 dos resultados: dividir um número por zero gera um erro PHP de nível [E_WARNING]. Isso não interrompe a execução do script;
  • linhas 6 a 9 dos resultados: acessar um índice inexistente de uma matriz [SplFixedArray] gera uma exceção do tipo [RuntimeException] e interrompe a execução do script;

7.6. Gerenciamento de exceções

O script [exceptions-02.php] mostra como gerenciar exceções:


<?php

// exibe 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 é exibida
  print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}

Comentários

  • o script é o mesmo apresentado no parágrafo anterior. Só que agora o código das linhas 8 a 19, suscetível de causar erros, foi envolvido por uma estrutura try/catch: se o código das linhas 8 a 21 causar (lançar) uma exceção ou um erro, este será gerenciado pela cláusula catch das linhas 22 a 26;
  • linha 22: o parâmetro da cláusula [catch] é o tipo de exceção ou erro que se deseja tratar. Ao definir como tipo [\Throwable], que é uma interface, indica-se que se deseja tratar qualquer instância de classe que implemente a interface [\Throwable]. Como todas as classes de erros e exceções implementam essa interface, a cláusula [catch] gerencia aqui qualquer erro/exceção encapsulada em uma classe;
  • linha 19: a instrução que desencadeia o erro e gera a exceção. Assim que ocorre uma exceção, há um desvio para a cláusula [catch]. O código após a linha 19, portanto, não será executado;

Resultados

1
2
3
4
5
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 10

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 12
float(INF)
erreur, message : Index invalid or out of range, type : RuntimeException

Comentários sobre os resultados

  • linhas 1 e 3: observam-se os erros de nível [E_NOTICE] e [E_WARNING]. Esses erros não são exceções e, portanto, não são tratados pela cláusula [catch];
  • linha 5: temos a mensagem de erro descrita na cláusula [catch]. Portanto, 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]

Vamos examinar o seguinte script [exceptions-03.php]:


<?php

// exibimos 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 maneiras diferentes de lidar com a exceção gerada pelo uso de um índice incorreto com a classe [\SplFixedArray]. Vimos que esse erro gerava uma exceção do tipo [RuntimeException];
    • linha 12: trata um erro do tipo [\Throwable]. Isso é válido, pois o tipo [RuntimeException] deriva do tipo [\Exception], que implementa a interface [\Throwable];
    • linha 20: trata um erro do tipo [\Exception]. Isso é válido, pois o tipo [RuntimeException] deriva do tipo [\Exception];
    • linha 28: trata um erro do tipo [\RuntimeException]. Esse é o método a ser preferido, pois é o tipo exato da exceção gerada;
  • linhas 32-62: quatro maneiras diferentes de lidar com a exceção gerada pela função [intdiv] quando se passa a ela um divisor igual a 0. A função [ intdiv ( int $dividend , int $divisor ) : int] realiza a divisão inteira $dividend / $divisor. Quando o divisor é zero, a exceção [\DivisionByzeroError] é lançada;
    • linha 35: intercepta-se qualquer erro que implemente a interface [\Throwable]. Isso é válido;
    • linha 43: intercepta-se o tipo exato do erro: esse é o método preferencial;
    • linha 51: intercepta-se o tipo [\Error]. Isso é válido, pois a classe [DivisionByzeroError] estende a classe [Error];
    • linha 59: intercepta-se o tipo [\Exception]. Isso é inválido, pois a classe [DivisionByzeroError] não tem nenhuma relação com a classe [\Exception];

Resultados

Erreur 1 : Index invalid or out of range
Erreur 2 : Index invalid or out of range
Erreur 3 : Index invalid or out of range
Erreur 4 : Division by zero
Erreur 5 : Division by zero
Erreur 6 : Division by zero

Fatal error: Uncaught DivisionByZeroError: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php:58
Stack trace:
#0 C:\Data\st-2019\dev\php7\php5-exemplos\exemplos\exceções\exceptions-03.php(58): intdiv(5, 0)
#1 {main}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php on line 58

7.8. Cláusula [finally]

A estrutura try/catch pode ter um terceiro elemento e se tornar uma estrutura try/catch/finally. O código da cláusula [finally] é executado nos dois casos a seguir:

  • a cláusula [try] não lança uma exceção. Ela é, 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. Ela é, então, executada até a 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] ainda é executado. Esse cenário é útil no seguinte caso:

  • na cláusula [try], o código obteve recursos (arquivos, bancos de dados, conexões de rede, filas). Em geral, esses recursos consomem muita memória. Portanto, é necessário liberá-los (o que geralmente se denomina “fechar”) assim que possível;
  • se a aquisição dos recursos tiver sido feita no [try], a devolução desses recursos será realizada no [finally]. Isso garante que, em todos os casos (com ou sem erro), os recursos adquiridos sejam devolvidos ao sistema;

O script a seguir, [exemples/exceptions/exceptions-04.php], mostra como a cláusula [finally] funciona 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 sobre o código

  • linha 4: $e é uma instância da classe predefinida [\Exception]. Vamos chamá-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 sua mensagem de erro é gravada no console;
  • linhas 13-15: a cláusula [finally] grava uma mensagem. De acordo com o que foi dito anteriormente, essa mensagem deveria ser gravada sempre, independentemente de haver ou não erro no [try];
  • linhas 18-24: não há erro no [try]. Também deveríamos passar para o [finally];
  • linhas 27-34: há uma instrução [return] no bloco “try” e não há erro. Podemos nos perguntar, então, se vamos passar para a cláusula [finally]. A execução mostra que sim;

Resultados

1
2
3
4
5
6
7
Premier test
Erreur…
Terminé
Second test
Terminé
Troisième test
Terminé

Vamos examinar outro caso, [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]. Isso é possível;

Resultados

Quatrième test

7.9. Criar suas próprias classes de exceções

Em um projeto um pouco mais complexo, é útil diferenciar os diversos erros, encapsulando-os em diferentes classes de exceções. No script anterior, vimos que qualquer exceção poderia ser interceptada por uma cláusula [catch (\Throwable]. Isso é recomendado quando não se tem nenhuma ideia do erro interceptado e o tratamento é o mesmo para todos os erros. Às 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.

Vamos examinar o seguinte script [exceptions-06.php]:


<?php

// definimos nossa própria família de exceções
class Exception1 extends \RuntimeException {
  
}

class Exception2 extends \RuntimeException {
  
}

// ou usamos 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çamos uma exceção do 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 uma exceção do 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 dessas classes está vazio. Em outras palavras, elas são utilizadas apenas por seus tipos: é por terem tipos diferentes que poderemos diferenciar essas duas exceções nas cláusulas [catch];
  • linhas 13-16: definimos duas variáveis, $e1 e $e2, que possuem, respectivamente, os tipos [Exception1] e [Exception2];
  • linhas 20-29: há uma estrutura try / catch / catch. Isso permite lidar com diferentes tipos de exceção por meio de diferentes cláusulas [catch];
  • linha 23: intercepta as exceções do tipo [Exception1];
  • linha 26: intercepta exceções do tipo [Exception2];
  • linha 49: intercepta as exceções do tipo [Exception1] ou (|) [Exception2];

Resultados

object(Exception1)#1 (7) {
  ["message":protected]=>
  string(10) "Erreur1…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(13)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
object(Exception2)#2 (7) {
  ["message":protected]=>
  string(10) "Erreur2…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(15)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
premier test
Exception 1
Erreur1…
second test
Exception 2
Erreur2…
troisième test
Exception 1 ou 2
Erreur1…
quatrième test
Exception 1 ou 2
Erreur2…

Comentários sobre os 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 arquivo 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 e, assim, definir uma pilha de exceções. O atributo [previous] permitirá explorar essa pilha;

7.10. Reenviar uma exceção

Uma exceção pode ser lançada várias vezes, conforme mostra o script [exceptions-07.php] a seguir:


<?php

try {
  try {
    // lança-se uma exceção
    throw new \Exception("test");
  } catch (\Exception $ex) {
    // relançamos a exceção interceptada
    throw $ex;
  } finally {
    // entraremos normalmente 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 {
  // entraremos normalmente no finally
  print "finally 2\n";
}

Comentários

  • linha 6: lança-se uma exceção;
  • linha 7: ela é interceptada;
  • linha 9: ela é lançada novamente. Em seguida, ela passa pela estrutura try / catch / finally do nível superior;
  • linha 14: ela é interceptada novamente;
  • linhas 10-12: a execução mostra que, mesmo após o [throw] da linha 9, o código passa, de fato, para a cláusula [finally] do try / catch / finally;

Resultados

1
2
3
finally 1
test dans try / catch / finally externe
finally 2

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. Veja um exemplo [exceptions-08.php]:

<?php

// definimos nossa própria família de exceções
class Exception1 extends \RuntimeException {

}

class Exception2 extends \RuntimeException {

}

class Exception3 extends \RuntimeException {

}

// ou utilizamos nossas exceções
$e1 = new Exception1("Erreur 1…", 1, new Exception2("Erreur 2…", 2, new Exception3("Erreur 3…")));
var_dump($e1);
// tratamento da exceção atual
print $e1->getMessage() . "\n";
$e = $e1;
while ($e->getPrevious() !== NULL) {
   // exceção anterior
  $e = $e->getPrevious();
   // mensagem de erro
  print $e->getMessage() . "\n";
}

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 em uma instância da classe [Exception2], que, por sua vez, está encapsulada em uma instância da classe [Exception1]. O construtor utilizado aqui é o construtor da classe [Exception]:

Image

O terceiro parâmetro do construtor permite encapsular uma exceção. Isso 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 motivos de compatibilidade, por exemplo, com uma interface;
  • porém, 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 essas exceções no tipo [Exception1], que será lançado. Isso permite não perder as informações contidas na exceção encapsulada, que foi a causa original do erro;
  • as linhas 20-27 mostram como gerenciar 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…