miércoles, 21 de agosto de 2013

Excepciones en PHP

Una excepción es un objeto derivado de la clase Exception de PHP que se encarga de mantener e informar de cualquier error producido. Por lo tanto su uso principal es para detener la ejecución del programa, notificar de errores y ayudar a depurar información.
Una excepción se creará ante una situación anómala en la ejecución del programa que provoca que este no pueda continuar con normalidad.
La clase Excepción recibe en su constructor dos argumentos opcionales: mensaje y código de error a mostrar. Algunos de los métodos más importantes de la clase Excepcion y que debemos de conocer son los siguientes:
- getMessage(): Devuelve la cadena de error que le llega al constructor de la clase Excepción.
- getCodeError(): Devuelve el entero que representa el código de error que llega al constructor de la clase Exception.
- getFile(): Devuelve el fichero donde se ha producido la excepción.
- getLine(): Devuelve la linea donde se produjo la excepción.
- getTrace(): Devuelve un array multidimensional con información del fichero y linea donde se produce la excepción, además de la función/método donde se produce la misma y los argumentos de entrada.
- getTraceAsString(): Devuelve la información de la función anterior pero en formato de cadena.

Crear y capturar una excepción

Crear una excepción es tan sencillo como utilizar la siguiente sintaxis en el lugar que nos parezca adecuado:

 throw new Exception('Mensaje a mostrar' );  

Como podemos ver hemos usado uno de los dos parámetros opciones que se han comentado anteriormente.
Tras haber lanzado la excepción, necesitamos un mecanismo para capturar esta excepción y poder mostrar la información que queramos del error y actuar en consecuencia al error. Para ello existe una estructura de control llamada try/catch. De esta forma dividiremos el manejo de excepciones en dos partes:
- Dentro del bloque try se insertará el código que puede provocar una excepción. Ya que este bloque es el encargado de parar la ejecución del script y pasar el control al bloque 'catch' cuando se produzca una excepción.
- Dentro de 'catch' introduciremos el código que controlará la excepción. Mostrando el error y aplicando la funcionalidad necesaria para actuar ante el error.

Es importante tener en cuenta que dentro de un bloque 'try', cuando una excepción es lanzada, el código siguiente a dicha excepción no será ejecutado. Pero si que se ejecutará el código tras el bloque try/catch. Por lo que si queremos detener totalmente el programa deberemos usar la función exit() tras mostrar el error, en un bloque 'catch'.

 try  
 {  
   // Codigo que puede lanzar excepciones  
 }  
 catch ( Exception $excepcion )  
 {  
   // Codigo para controlar la excepcion  
 }  
 echo "esto si que se ejecuta";

Por lo tanto el uso de las excepciones se basa en dos etapas:
- Cuando ocurra algún error, una excepción será lanzada en un punto del script.
- Para mostrar el error y actuar en consecuencia, es necesario capturar dicha excepción.

Vamos a ver que pasaría si solo se lanza una excepción pero no se captura:

 class Prueba{  
   protected $id;  
   function __construct($id) {  
     if($this->validId($id)) $this->id= $id;  
     else throw new Exception('Identificiador no es un entero');  
   }  
   private function validId($id){  
     if(!is_int($id)) return false;  
     return true;  
   }  
 }  
 $p = new Prueba('prueba');  

Fatal error: Uncaught exception 'Exception' with message 'Identificiador no es un entero' in /home/ivan/public_html/prueba/excepciones.php:8 Stack trace: #0 /home/ivan/public_html/prueba/excepciones.php(17): Prueba->__construct('prueba') #1 {main} thrown in /home/ivan/public_html/prueba/excepciones.php on line 8

Lo que ratifica lo comentado anteriormente. PHP intentará encontrar el primer bloque 'catch' coincidente con la excepción lanzada. Si lanzamos una excepción estamos obligados a capturarla. Por lo que vamos a mejorar el ejemplo para captura dicha excepción.

 class Prueba {  
   protected $id;  
   function __construct($id) {  
     try {  
       if ($this->validId($id))  
         $this->id = $id;  
       else  
         throw new Exception('Identificiador no es un entero');  
     } catch (Exception $e) {  
       echo $e->getMessage();
       print_r($e->getTrace());  
       //exit();  
     }  
   }  
   private function validId($id) {  
     if (!is_int($id))  
       return false;  
     return true;  
   }  
 }  
 $p = new Prueba('prueba');  

Ahora si que se mostrará la excepción:

 Identificiador no es un entero  
 Array ( [0] => Array ( [file] => /home/ivan/public_html/prueba/excepciones.php [line] => 28 [function] => __construct [class] => Prueba [type] => -> [args] => Array ( [0] => prueba ) ) )  

En el ejemplo anterior se ha mostrado como lanzar y capturar una excepción en un mismo método. Pero también es habitual que los métodos lancen las excepciones y estas se capturen al utilizar el objeto instanciado de la clase.

 class Prueba {  
   protected $id;  
   function __construct($id) {  
     if ($this->validId($id))  
       $this->id = $id;  
     else  
       throw new Exception('Identificiador no es un entero');  
   }  
   private function validId($id) {  
     if (!is_int($id))  
       return false;  
     return true;  
   }  
 }  
 try{  
   $p = new Prueba('prueba');   
 } catch (Exception $e){  
   echo $e->getMessage() . "<br/>";  
   echo $e->getTraceAsString();   
   exit();  
 }  

Extendiendo la clase Exception

Podemos crear nuestras propias excepciones heredando de la clase Exception. De esta forma tendremos a nuestra disposición todos sus atributos y métodos. Y evidentemente podremos añadir nuestros atributos y métodos propios.
Una de las razones para crear excepciones propias es para obtener más información de un error concreto, que la proporcionada por una excepción general.

 class Logger {   
   static function Log($msg) {  
     $texto = date('d-m-Y H:i:s') . ': ' . $msg;  
     $file = fopen('./log.txt', 'a+');  
     fwrite($file, $texto . "\r\n");  
     fclose($file);  
   }  
 }  
 class DBException extends Exception {  
   public function getError() {  
     $msg = $this->getMessage();  
     Logger::Log($msg);  
     return $msg;  
   }  
 }  

Vamos a ver como lanzar y capturar la excepción propia definida.

 class Test {  
   const servidor = "localhost";  
   const usuario_db = "usuario";  
   const pwd_db = "1234";  
   const nombre_db = "demo";  
   private $conn = NULL;  
   public function __construct() {  
     $this->conectarDB();  
   }  
   private function conectarDB() {  
     try {  
       $this->conn = new mysqli(self::servidor, self::usuario_db, self::pwd_db, self::nombre_db);  
       if ($this->conn->connect_error) {  
         throw new DBException('Fallo en la conexion con la BD: ' . $this->conn->connect_error);  
       }  
     } catch (DBException $e) {  
       echo $e->getError();  
     }  
   }  
 }  
 $t = new Test();  

Como vemos ahora controlamos si hay algún problema con la conexión de la base de datos. Por lo que ahora, en el bloque 'catch' no esperamos un objeto Exception, sino que esperamos un objeto DBException. Que es una instancia de nuestra excepción.


Múltiples bloques catch

Si queremos capturar diferentes tipos específicos de excepciones podemos usar múltiples bloques 'catch'.
Simplemente hay que especificar el tipo de excepción exacta en cada sentencia 'catch'.

 class Test {  
   const servidor = "localhost";  
   const usuario_db = "usuario";  
   const pwd_db = "1234";  
   const nombre_db = "demo";  
   private $conn = NULL;  
   public function __construct() {  
     $this->conectarDB();  
   }  
   private function conectarDB() {  
     $this->conn = new mysqli(self::servidor, self::usuario_db, self::pwd_db, self::nombre_db);  
     if ($this->conn->connect_error) {  
       throw new DBException('Fallo en la conexion con la BD: ' . $this->conn->connect_error);  
     }  
   }  
 }  

 try {  
   if (!file_exists('./log.txt'))  
     throw new Exception('El fichero de log no se creo correctamente');  
   $t = new Test();  
 } catch (DBException $e) {  
   echo $e->getError();  
 } catch (Exception $e) {  
   echo $e->getMessage();  
 }  

Hay que tener en cuenta que Exception es la clase base de todas las excepciones propias creadas (clases hijas). Por lo tanto si solo tenemos una sentencia 'catch' y especificamos que queremos capturar un objeto Exception (excepción general), capturaremos todas las excepciones. Sin embargo no podremos acceder a los métodos declarados en las clases hijas. Recuerda que una clase padre no puede utilizar métodos de las clases hijas.
Evidentemente una excepción solo se captura una vez: si hemos capturado un objeto DBException mediante una sentencia 'cath' (ejemplo anterior), no la vamos a volver a capturar mediante la siguiente sentencia 'catch'. Aunque esta capture objetos de la clase base Exception.


Entradas relacionadas

PHP orientado a objetos - Introducción
PHP orientado a objetos - Herencia

2 comentarios:

  1. Excelente! No encontré mejor explicación en ninguna otra página. Saludos!

    ResponderEliminar
  2. interesante, he entendido algo de aqui, he visitado muchas paginas con el objetivo de aprender a capturas las excepciones y ninguno lo entendia, ahora entiendo un poco, por supuesto no lo suficiente, si tuvieras algun otro ejemplo que aportar sería genial. muchas gracias

    ResponderEliminar