Cómo hacer un framework ligero en PHP (III) – Control de errores y excepciones

El control de errores… uno de mis temas favoritos y sobretodo uno de los temas peor resueltos en PHP como lenguaje y especialmente por parte de la inmensa mayoría de desarrolladores de PHP.

Uno puede pensar que tampoco es muy importante, que total, si peta pues peta, que eso no pasa casi nunca y ya está pero en webs de alta disponibilidad o aplicaciones con mucha concurrencia de usuarios pasan cosas extrañas y si no tenemos un buen control de errores nos podemos volver locos buscando por qué en algunas ocasiones falla nuestra aplicación.

Los puntos principales a tener en cuenta son:

Exterminio absoluto de die salvo necesidad imperiosa

Qué tiempos aquellos en los que hacíamos al principio de las páginas lo siguiente:

<?php
   $db = @mysql_connect('localhost', 'root', '') or die('Error conectando a servidor de BBDD');
   @mysql_select_db('mi_base_datos', $db) or die('Error seleccionando BBDD');
   $sql = 'SELECT * FROM tabla';
   $rs = @mysql_query($sql, $db) or die('Error SQL '. $sql. ' Mensaje:'.mysql_error());
?>

Este código puede parecernos bien, sirve para cuando das clase de iniciación a la programación de webs (y no queremos asustar a la gente con PDO y demás abstracciones) y todos hemos empezado haciendo cosas de este tipo.

Aparentemente, nos avisa de cualquier error conectando a la BBDD, el operador @ nos suprime los warnings de PHP si algo falla y tenemos salidas limpias de la aplicación.

Pero este código tiene varios problemas:

– No es demasiado mantenible, aunque podemos poner esto en funciones o clases y quedaría algo mejor.

– No lanza excepciones de ningún tipo y es difícil recoger el error y hacer un redirect a las tan de moda actualmente páginas de error 500 y error 400 (Como por ejemplo esta).

– Y LO PEOR DE TODO. Estamos devolviendo un Status Code 200 a nivel de Apache. Y diréis y qué más me da. Pues supongamos que tenemos un blog humilde como éste y que Google no nos tiene demasiado aprecio y pasa una vez a la semana o menos. Supongamos que en ese momento que pasa Google, nuestro ISP ha recibido un ataque de esos maravillosos que tiran todo el MySQL y en ese momento, nuestro fantástico control de errores reacciona y nuestras páginas lucen esplendorosas con el ‘Error conectando a servidor de BBDD’. Pues bien, Google interpretará ese mensaje de error como una página perfectamente válida (debido al status 200), todo nuestro site tendrá el mismo contenido duplicado y nuestro flojo pagerank se hundirá estrepitosamente. Ya no nos gusta tanto, ¿verdad?

Sin embargo, die o exit a veces pueden ser buenas opciones si estamos desarrollando una API de conexión y en algunos casos queremos devolver un mensaje concreto a los que consuman nuestros datos.

Y si nuestra página no es muy grande y no queremos matarnos, bastaría con lanzar un header con el status code correspondiente (500 para errores, 400 para bad requests, etc) antes de hacer el die o exit y Google ya no indexará ese mensaje de error como una nueva versión de la página.

En cualquier caso, recomiendo imperiosamente hacer lo que comento en los dos puntos siguientes y así nuestra aplicación quedará molona y no quedará ningún error por tratar.

Convertir los warnings y otros errores de PHP en excepciones

PHP nunca se ha caracterizado por tener un buen control de excepciones como Java. Cuando un fopen o un mysql_connect fallan, PHP devuelve un warning pero sigue ejecutándose el script. Esto en pequeñas aplicaciones es sencillo de controlar pero en aplicaciones grandes se vuelve algo inmanejable.

Para conseguir convertir los errores de PHP en Exceptions hay que hacer lo siguiente:

<?php   
   set_error_handler('myErrorHandler');
   function myErrorHandler($code, $error, $file = NULL, $line = NULL) {
      throw new Exception($error . ' encontrado en '. $file.', línea '.$line);
   }
 
   try {
        $fd = fopen('ficheronoexiste', 'r');
   } catch(Exception $ex) {
         echo 'Error cacheado '. $ex->getMessage().PHP_EOL;
   }
?>

Con este código veremos que en vez de obtener el clásico
PHP Warning: fopen(ficheronoexiste): failed to open stream: No such file or directory in fichero.php on line 8

Se captura la excepción correctamente y entramos al catch, para acabar viendo
Error cacheado fopen(ficheronoexiste): failed to open stream: No such file or directory encontrado en fichero.php, línea 8

Esto no parece un gran avance pero si somos algo ingeniosos podemos capturar los errores de PHP y notificar por mail, realizar algún handling mejor y hacer redirects limpios a páginas de error 500.

Tener un único punto de try / catch y intentar tener varios tipos de excepciones

Vale, no es necesario tener un único punto de try/catch pero muchas veces se abusa sin saber cuando no es necesario. Con un único try / catch en nuestro archivo index.php si se lanza una excepción desde cualquier clase de las profundidades, ese error sube y es capturado correctamente desde el archivo base.

Además puede ser interesante hacer algo como lo siguiente:

<?php
   // Creamos una exception especial
   class PHPException extends Exception { }
 
   // Deberíamos hacer que nuestra clase de BBDD en vez de lanzar Exceptions genéricas lance DBExceptions...
   class DBException extends Exception { }
 
   // Separamos las excepciones de código
   set_error_handler('myErrorHandler');
   function myErrorHandler($code, $error, $file = NULL, $line = NULL) {
      throw new PHPException($error . ' encontrado en '. $file.', línea '.$line);
   }
 
   try {
 
    // Todo el código que queramos instanciando N clases, con herencias y todo lo que queráis
 
   } catch(PHPException $ex) {
       // Informamos al equipo de desarrollo, pasando por ejemplo el print_debug_backtrace()
       // headers y enviar a alguna página de error "bonita"
   } catch(DBException $ex) {
       // Informamos al DB Admin via mail por ejemplo
       // headers y enviar a alguna página de error "bonita"
   } catch(Exception $ex) {
       // Aquí llega cualquier otro tipo de Exception, veremos lo que hacemos
       // por norma general header 500 y página bonita
   }
 
?>

Urgh… esto empieza a parecerse a Java pensarán algunos. Pues… sí y no. En Java hay que controlar los N-mil tipos de Exceptions que pueden saltar por leer un fichero (aunque creo que en Java6 esto ha mejorado).

En cambio en PHP, es algo diferente. Cualquier tipo Exception o hija de Exception (que ahí esta la gracia) entra en el tercer catch, y las dos primeras, que son algo especiales, las tratamos algo diferente.

Queda claro que se puede tener un control de excepciones bastante digno en PHP, algo menos verbose que en Java y prácticamente igual de potente y funcional. Si revisáis muchos proyectos open-source veréis que casi nadie hace esto y es difícil capturar los errores correctamente. Afortunadamente, muchos frameworks ya hacen esto por nosotros y poco a poco las cosas van mejorando.

Espero que os haya gustado, próximamente más sobre los frameworks ligeros y un ejemplo de cómo se comunican controlador -> modelo -> vista.

You may also like...

7 Responses

  1. fidel says:

    bien, estoy esperando la 4 parte. Y una corrección: java tiene un buen árbol de herencia de excepciones, tampoco exagere de ‘verbo’ a java ni decir que “N-mil excepciones”. Si alguien se pone a programar N-mil catchs es porque no conoce el árbol y seguro que hay una excepción padre que puede agrupar muchas de las que pertende atrapar.

  2. Luis says:

    Amigo, he aprendido mucho leyendo esto, espero con ganas el 4 : )

    Saludos

  3. Pedro says:

    Muy buenos tutoriales!
    Para cuando una parte 4?

    Gracias!

  4. Very shortly this site will be famous amid all blog
    visitors, due to it’s nice articles

  5. You actually make it seem so easy with your presentation but I find this matter to be actually something that I think I would never understand.
    It seems too complex and very broad for me. I’m looking forward for your next post, I’ll
    try to get the hang of it!

  6. agustin says:

    Saludos, estoy realizando mis primeras pruebas con MVC, tengo infinidad de dudas… pero para empezar… lo que me causa mas conflicto… por lo general guardo en cada directorio las imagenes que corresponden a ese directorio, en este caso, supongamos que mi directorio es

    midominio.algo/servicios/rentadeautos/

    ahi tengo mis imagienes en una web tradicional

    pero si uso solo un index y todas las imagenes estan cargadas en

    midominio.algo/imagenes

    es posible “engañar” al usuario/navegador/google etc… que las imagenes estan cargadas en la ruta

    midominio.algo/servicios/rentadeautos/

    aun y cuando estan en

    midominio.algo/imagenes/

    ¿?

  1. 11/12/2011

    Workout Of Celebrities…

    […in the middle of the woods. I mean check out this link and tell…]…