Skip to content

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

Image

7.2. La interfaz [\Throwable]

La interfaz [\Throwable] es la siguiente:

Image

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

Image

7.3. Las excepciones predefinidas en PHP 7

PHP 7 definen varias clases de excepciones:

Image

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

Image

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:

Image

Image

  • 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

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

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

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

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

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-ejemplos\ejemplos\excepciones\excepciones-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]

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

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

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

Quatrième test

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

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

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

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

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

<?php

// definimos nuestra propia familia de excepciones
class Exception1 extends \RuntimeException {

}

class Exception2 extends \RuntimeException {

}

class Exception3 extends \RuntimeException {

}

// o utilizamos nuestras excepciones
$e1 = new Exception1("Erreur 1…", 1, new Exception2("Erreur 2…", 2, new Exception3("Erreur 3…")));
var_dump($e1);
// explotación de la excepción actual
print $e1->getMessage() . "\n";
$e = $e1;
while ($e->getPrevious() !== NULL) {
  // excepción anterior
  $e = $e->getPrevious();
  // mensaje de error
  print $e->getMessage() . "\n";
}

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

Image

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…