7. Excepciones y errores
Cuando un método de una clase encuentra un error irrecuperable (archivo inexistente, base de datos no conectada, conexión de red fuera de servicio), no muestra un error en una consola (archivo, base de datos), sino que lanza una excepción. Todas las excepciones heredan de la clase [\Exception]. Además de las excepciones, el funcionamiento interno de PHP también genera errores cuya clase base es la clase [\Error]. Ambas clases implementan la interfaz PHP [\Throwable].
7.1. El árbol de scripts

7.2. La interfaz [\Throwable]
La interfaz [\Throwable] es la siguiente:

La función de los métodos de la interfaz es la siguiente:

7.3. Las excepciones predefinidas en PHP 7
PHP 7 definen varias clases de excepciones:

- en [1], las excepciones predefinidas en PHP;
- en [2], las excepciones de la biblioteca SPL (Biblioteca estándar PHP) de PHP 7. La biblioteca SPL es una colección de clases e interfaces destinadas a resolver problemas con los que se encuentran frecuentemente los desarrolladores.
7.4. Los errores predefinidos en PHP 7
PHP 7 define varias clases de errores:

La clase [\Error] es la clase principal de todos los errores predefinidos en PHP. La clase [ErrorException] permite encapsular una instancia de la clase [\Error] en una instancia de la clase [\Exception]. Esto permite unificar la gestión de errores al tratar únicamente las excepciones.
7.5. Ejemplo 1
El primer ejemplo [exceptions-01.php] muestra tanto errores PHP como una excepción:
<?php
// visualización de todos los errores
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// código --------
$var=[];
// clave desconocida
print $var["abcd"];
// división por cero
$var=7/0;
var_dump($var);
// matriz con límites fijos
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// índice fuera de los límites
$array[5]=8;
Comentarios
- línea 4: se le pide a PHP que señale todos los errores. El segundo parámetro es el nivel de errores solicitado:


- línea 5: se solicita que se muestren los errores en la consola;
- línea 9: se accede a un elemento inexistente de la matriz [$var];
- línea 11: se realiza una división por cero;
- línea 14: se crea una instancia de la clase [SplFixedArray]. Esta clase permite crear una matriz de límites fijos e índices enteros;
- línea 18: se accede a un elemento inexistente de la matriz;
Resultados
Comentarios
- línea 1 de los resultados: acceder a una clave inexistente de una matriz provoca un error PHP de nivel [E_NOTICE]. Esto no interrumpe la ejecución del script;
- línea 3 de los resultados: dividir un número entre cero provoca un error PHP de nivel [E_WARNING]. Esto no interrumpe la ejecución del script;
- líneas 6-9 de los resultados: acceder a un índice inexistente de una matriz [SplFixedArray] provoca una excepción de tipo [RuntimeException] e interrumpe la ejecución del script;
7.6. Gestionar excepciones
El script [exceptions-02.php] muestra cómo gestionar las excepciones:
<?php
// se muestran todos los errores
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// se envuelve el código en un try / catch
try {
$var = [];
// clave desconocida
print $var["abcd"];
// división por cero
$var = 7 / 0;
var_dump($var);
// matriz con límites fijos
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// índice fuera de los límites
$array[5] = 8;
// verificación
print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
// \Throwable es la interfaz implementada por la mayoría de los errores y excepciones
// se muestra la excepción
print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}
Comentarios
- el script es el que se presentó en el párrafo anterior. Solo que ahora se ha rodeado el código de las líneas 8-19, susceptible de provocar errores, con una estructura try / catch: si el código de las líneas 8-21 provoca (lanza) una excepción o un error, este será gestionado por la cláusula catch de las líneas 22-26;
- línea 22: el parámetro de la cláusula [catch] es el tipo de excepción o error que se desea gestionar. Al establecer como tipo [\Throwable], que es una interfaz, se indica que se desea gestionar cualquier instancia de clase que implemente la interfaz [\Throwable]. Dado que todas las clases de errores y excepciones implementan esta interfaz, la cláusula [catch] gestiona aquí cualquier error o excepción encapsulado en una clase;
- línea 19: la instrucción que desencadena el error y da lugar a la excepción. En cuanto se produce una excepción, se realiza una ramificación a la cláusula [catch]. Por lo tanto, el código que sigue a la línea 19 no se ejecutará;
Resultados
Comentarios sobre los resultados
- líneas 1 y 3: aparecen los errores de nivel [E_NOTICE] y [E_WARNING]. Estos errores no son excepciones y, por lo tanto, no son gestionados por la cláusula [catch];
- línea 5: aparece el mensaje de error escrito en la cláusula [catch]. Por lo tanto, se ha producido una excepción derivada de [\Exception] o un error derivado de [\Error]. Aquí vemos que se trata de la clase [\RuntimeException];
7.7. Parámetros de la cláusula [catch]
Analicemos el siguiente script [exceptions-03.php]:
<?php
// se muestran todos los errores
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// una matriz de tamaño fijo
$array = new \SplFixedArray(5);
try {
// índice fuera de los límites
$array[5] = 8;
} catch (\Throwable $ex) {
// Mostrar mensaje de error
print "Erreur 1 : " . $ex->getMessage() . "\n";
}
try {
// índice fuera de los límites
$array[5] = 8;
} catch (\Exception $ex) {
// visualización de mensaje de error
print "Erreur 2 : " . $ex->getMessage() . "\n";
}
try {
// índice fuera de los límites
$array[5] = 8;
} catch (\RuntimeException $ex) {
// visualización de mensaje de error
print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
// división por 0
intdiv(5, 0);
} catch (\Throwable $ex) {
// visualización de mensaje de error
print "Erreur 4 : " . $ex->getMessage() . "\n";
}
try {
// división por 0
intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
// visualización de mensaje de error
print "Erreur 5 : " . $ex->getMessage() . "\n";
}
try {
// división por 0
intdiv(5, 0);
} catch (\Error $ex) {
// visualización de mensaje de error
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
try {
// división por 0
intdiv(5, 0);
} catch (\Exception $ex) {
// visualización de mensaje de error
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
Comentarios
- líneas 8-31: tres formas diferentes de gestionar la excepción generada por el uso de un índice incorrecto con la clase [\SplFixedArray]. Hemos visto que este error generaba una excepción de tipo [RuntimeException];
- línea 12: gestiona un error de tipo [\Throwable]. Esto es válido, ya que el tipo [RuntimeException] deriva del tipo [\Exception], que implementa la interfaz [\Throwable];
- línea 20: gestiona un error de tipo [\Exception]. Es válido, ya que el tipo [RuntimeException] deriva del tipo [\Exception];
- línea 28: gestiona un error de tipo [\RuntimeException]. Es el método preferible, ya que es el tipo exacto de la excepción generada;
- líneas 32-62: 4 formas diferentes de gestionar la excepción generada por la función [intdiv] cuando se le pasa un divisor igual a 0. La función [ intdiv ( int $dividend , int $divisor ) : int] realiza la división entera $dividend / $divisor. Cuando el divisor es cero, se lanza la excepción [\DivisionByzeroError];
- línea 35: se intercepta cualquier error que implemente la interfaz [\Throwable]. Esto es válido;
- línea 43: se intercepta el tipo exacto del error: este es el método preferible;
- línea 51: se intercepta el tipo [\Error]. Es válido, ya que la clase [DivisionByzeroError] extiende la clase [Error];
- línea 59: se intercepta el tipo [\Exception]. Esto no es válido porque la clase [DivisionByzeroError] no tiene ninguna relación con la clase [\Exception];
Resultados
7.8. Cláusula [finally]
La estructura try / catch puede tener un tercer elemento y convertirse en una estructura try / catch / finally. El código de la cláusula [finally] se ejecuta en los dos casos siguientes:
- la cláusula [try] no lanza una excepción. En ese caso, se ejecuta por completo y, a continuación, la ejecución del código pasa a la cláusula [finally], que se ejecuta por completo;
- la cláusula [try] lanza una excepción. En ese caso, se ejecuta hasta la instrucción que lanza la excepción. La ejecución del código pasa entonces a la cláusula [catch], que se ejecuta por completo. A continuación, la ejecución del código pasa a la cláusula [finally], que se ejecuta por completo;
Por último, el código de la cláusula [finally] se ejecuta en todo momento. Este escenario resulta útil en el siguiente caso:
- en [try], el código ha obtenido recursos (archivos, bases de datos, conexiones de red, colas). Por lo general, estos recursos consumen mucha memoria. Por lo tanto, hay que devolverlos (lo que se suele denominar «cerrar») tan pronto como sea posible;
- si la adquisición de los recursos se ha realizado en [try], su liberación se incluirá en [finally]. Esto nos garantiza que, en todos los casos (haya error o no), los recursos adquiridos se devuelvan al sistema;
El siguiente script [exemples/exceptions/exceptions-04.php] nos muestra el funcionamiento de la cláusula [finally] en diversas situaciones:
<?php
// o crea una instancia de excepción
$e = new \Exception("Erreur…");
var_dump($e);
// primera prueba
try {
print "Premier test\n";
throw $e;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// segunda prueba
try {
print "Second test\n";
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// tercera prueba
try {
print "Troisième test\n";
return;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
Comentarios del código
- línea 4: $e es una instancia de la clase predefinida [\Exception]. La vamos a ejecutar en diferentes lugares;
- líneas 8-15: la excepción $e se lanza en [try] (línea 10);
- línea 11: la excepción [\Exception] se intercepta y su mensaje de error se escribe en la consola;
- líneas 13-15: la cláusula [finally] escribe un mensaje. Según lo dicho anteriormente, este mensaje debería escribirse siempre, haya o no error en [try];
- líneas 18-24: no hay ningún error en [try]. Aquí también deberíamos pasar a [finally];
- líneas 27-34: hay una instrucción [return] en el try y no hay error. Cabe preguntarse entonces si se pasará a la cláusula [finally]. La ejecución muestra que sí;
Resultados
Examinemos otro caso [exceptions-05.php]:
<?php
// cuarta prueba
try {
print "Quatrième test\n";
exit;
} finally {
print "Terminé\n";
}
Comentarios
- línea 6: la instrucción [exit] detiene inmediatamente la ejecución del script: la cláusula [finally] no se ejecuta;
- líneas 4-9: un ejemplo de try / catch / finally sin la cláusula [catch]. Es posible;
Resultados
7.9. Crear tus propias clases de excepciones
En un proyecto de cierta envergadura, resulta útil diferenciar los distintos errores encapsulándolos en diferentes clases de excepciones. En el script anterior, hemos visto que cualquier excepción podía ser interceptada por una cláusula [catch (\Throwable]. Esto se recomienda si no se tiene ni idea del error interceptado y el tratamiento es el mismo para todos los errores. A veces es así, pero a menudo es necesario adaptar el tratamiento al tipo exacto de error. Por lo tanto, hay que diferenciar los errores entre sí.
Examinemos el siguiente script [exceptions-06.php]:
<?php
// definimos nuestra propia familia de excepciones
class Exception1 extends \RuntimeException {
}
class Exception2 extends \RuntimeException {
}
// o utilizamos nuestras excepciones
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);
// primera prueba
print ("premier test\n");
try {
// lanzamos un 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";
}
// segunda prueba
print ("second test\n");
try {
// lanzamos un 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";
}
// tercera prueba
print ("troisième test\n");
try {
// se lanza un tipo Exception1
throw $e1;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
// cuarta prueba
print ("quatrième test\n");
try {
// se lanza un tipo Exception2
throw $e2;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
Comentarios
- líneas 4-10: se definen dos clases, [Exception1] y [Exception2], ambas derivadas de la clase predefinida [\RuntimeException]. El cuerpo de estas clases está vacío. En otras palabras, solo se utilizan por sus tipos: es precisamente porque tienen tipos diferentes por lo que podremos diferenciar estas dos excepciones en las cláusulas [catch];
- líneas 13-16: se definen dos variables $e1 y $e2 que tienen, respectivamente, los tipos [Exception1] y [Exception2];
- líneas 20-29: se utiliza una estructura try / catch / catch. Esto permite gestionar diferentes tipos de excepciones con diferentes cláusulas [catch];
- línea 23: intercepta las excepciones de tipo [Exception1];
- línea 26: intercepta las excepciones de tipo [Exception2];
- línea 49: intercepta las excepciones de tipo [Exception1] o (|) [Exception2];
Resultados
Comentarios sobre los resultados
- líneas 1-17: el «contenido» de una excepción:
- líneas 2-3: el mensaje de error;
- líneas 6-7: el código de error;
- líneas 8-9: el nombre del archivo en el que se produjo la excepción;
- líneas 10-11: la línea en la que se produjo la excepción;
- líneas 15-16: la excepción anterior. Una excepción puede encapsular otra excepción y definir así una pila de excepciones. El atributo [previous] permitirá explotar esta pila;
7.10. Relanzar una excepción
Una excepción puede lanzarse varias veces, como muestra el siguiente script [exceptions-07.php]:
<?php
try {
try {
// se lanza una excepción
throw new \Exception("test");
} catch (\Exception $ex) {
// se vuelve a lanzar la excepción interceptada
throw $ex;
} finally {
// se ejecutará correctamente el finally
print "finally 1\n";
}
} catch (\Exception $ex2) {
// se recupera correctamente la excepción inicial
print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
// se ejecutará correctamente el bloque finally
print "finally 2\n";
}
Comentarios
- línea 6: se lanza una excepción;
- línea 7: se intercepta;
- línea 9: se vuelve a lanzar. A continuación, pasa a la estructura try / catch / finally del nivel superior;
- línea 14: se intercepta de nuevo;
- líneas 10-12: la ejecución muestra que, incluso después del [throw] de la línea 9, se pasa correctamente a la cláusula [finally] del try / catch / finally;
Resultados
7.11. Aprovechamiento de una pila de excepciones
Una excepción puede encapsular otra excepción, que a su vez puede encapsular otra, formando finalmente una pila de excepciones. He aquí un ejemplo [exceptions-08.php]:
Comentarios
- líneas 4-14: definen tres clases de excepciones derivadas de la excepción predefinida [RuntimeException];
- línea 17: una instancia de la clase [Exception3] está encapsulada en una instancia de la clase [Exception2], que a su vez está encapsulada en una instancia de la clase [Exception1]. El constructor utilizado aquí es el constructor de la clase [Exception]:
![]()
El tercer parámetro del constructor permite encapsular una excepción. Esto puede resultar útil en el siguiente escenario:
- se define un método M que puede generar una excepción de tipo [Exception1] y solo de este tipo por razones de compatibilidad, por ejemplo, con una interfaz;
- sin embargo, en el método M pueden producirse otros tipos de excepciones. Para reenviar un error al código que llama al método M, encapsularemos entonces estas excepciones en el tipo [Exception1] que lanzaremos. Esto permite no perder la información contenida en la excepción encapsulada y que fue la causa original del error;
- las líneas 20-27 muestran cómo gestionar la pila de excepciones internas de una excepción;
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…