7. Excepciones y errores
Cuando un método de una clase encuentra un error irrecuperable (archivo inexistente, base de datos sin conexión, 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 diseñadas para resolver problemas que enfrentan con frecuencia los desarrolladores.
7.4. Los errores predefinidos en PHP 7
PHP 7 define varias clases de errores:

La clase [\Error] es la clase padre de todos los errores predefinidos en PHP. La clase [ErrorException] permite encapsular una instancia de la clase [\Error] dentro de una instancia de la clase [\Exception]. Esto permite estandarizar el manejo 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 reporte 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 un array 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 un array 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 un array [SplFixedArray] provoca una excepción de tipo [RuntimeException] e interrumpe la ejecución del script;
7.6. Manejo de excepciones
El script [exceptions-02.php] muestra cómo manejar 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 a 19, que podría provocar errores, con una estructura try / catch: si el código de las líneas 8 a 21 provoca (lanza) una excepción o un error, este será manejado por la cláusula catch de las líneas 22 a 26;
- línea 22: el parámetro de la cláusula [catch] es el tipo de excepción o error que queremos manejar. Al establecer como tipo [\Throwable], que es una interfaz, se indica que se desea manejar 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] maneja aquí cualquier error o excepción encapsulada en una clase;
- línea 19: la instrucción que provoca el error y genera la excepción. Tan pronto como 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: se observan los errores de nivel [E_NOTICE] y [E_WARNING]. Estos errores no son excepciones y, por lo tanto, no son manejados por la cláusula [catch];
- línea 5: aparece el mensaje de error en la cláusula [catch]. Por lo tanto, se produjo 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 con límites fijos
$array = new \SplFixedArray(5);
try {
// índice fuera de los límites
$array[5] = 8;
} catch (\Throwable $ex) {
// muestra un 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 {
// valor fuera de rango
$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) {
// mensaje de error
print "Erreur 4 : " . $ex->getMessage() . "\n";
}
try {
// división por 0
intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
// Muestra un 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 manejar la excepción generada por el uso de un índice incorrecto con la clase [\SplFixedArray]. Hemos visto que este error genera una excepción de tipo [RuntimeException];
- línea 12: maneja un error de tipo [\Throwable]. Esto es válido, ya que el tipo [RuntimeException] deriva del tipo [\Exception], el cual implementa la interfaz [\Throwable];
- línea 20: maneja un error de tipo [\Exception]. Esto es válido, ya que el tipo [RuntimeException] deriva del tipo [\Exception];
- línea 28: maneja un error de tipo [\RuntimeException]. Este es el método preferido, ya que es el tipo exacto de la excepción generada;
- líneas 32-62: cuatro formas diferentes de manejar 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 preferido;
- línea 51: se intercepta el tipo [\Error]. Esto 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 luego la ejecución del código pasa a la cláusula [finally], la cual se ejecuta por completo;
- la cláusula [try] genera una excepción. En ese caso, se ejecuta hasta la instrucción que genera la excepción. La ejecución del código pasa entonces a la cláusula [catch], que se ejecuta por completo. Luego, la ejecución del código pasa a la cláusula [finally], que se ejecuta por completo;
Finalmente, el código de la cláusula [finally] se ejecuta de todos modos. Este escenario resulta útil en el siguiente caso:
- en la cláusula [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, es necesario liberarlos (lo que comúnmente se denomina «cerrarlos») tan pronto como sea posible;
- si la adquisición de los recursos se realizó en el [try], su liberación se llevará a cabo en el [finally]. Esto 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 cómo funciona 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 ejecutaremos en diferentes lugares;
- líneas 8-15: la excepción $e se lanza en [try] (línea 10);
- línea 11: se intercepta la excepción [\Exception] y se escribe su mensaje de error 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 un error en [try];
- líneas 18-24: no hay error en el [try]. Aquí también se debería pasar al [finally];
- líneas 27-34: hay una instrucción [return] en el try y no hay error. Entonces, cabe preguntarse si se pasará a la cláusula [finally]. La ejecución muestra que sí;
Resultados
Analicemos 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, vimos que cualquier excepción podía ser interceptada mediante una cláusula [catch (\Throwable]. Esto se recomienda si no se tiene idea del error interceptado y el tratamiento es el mismo para todos los errores. A veces es así, pero con frecuencia se necesita adaptar el tratamiento al tipo exacto del error. Por lo tanto, es necesario diferenciar los errores entre sí.
Analicemos 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 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 manejar 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 ocurrió la excepción;
- líneas 10-11: la línea en la que ocurrió 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á aprovechar esta pila;
7.10. Relanzar una excepción
Una excepción puede lanzarse varias veces, como lo 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 bloque 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. Entonces pasa a la estructura try / catch / finally del nivel superior;
- línea 14: se intercepta nuevamente;
- 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, la cual a su vez puede encapsular otra, formando finalmente una pila de excepciones. A continuación se muestra 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], la cual a su vez está encapsulada en una instancia de la clase [Exception1]. El constructor utilizado aquí es el de la clase [Exception]:
![]()
El tercer parámetro del constructor permite encapsular una excepción. Esto puede ser ú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 ocurrir otros tipos de excepciones. Para reportar un error al código que llama al método M, se encapsularán estas excepciones en el tipo [Exception1], que luego se lanzará. Esto permite no perder la información contenida en la excepción encapsulada, la cual fue la causa original del error;
- las líneas 20-27 muestran cómo manejar 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…