> Manuales > Manual de PHP

Cómo tratar las excepciones en tus aplicaciones PHP, usando la estructura try / catch y los distintos tipos de excepciones y errores que existen.

Tratar excepciones en PHP con try / catch

En un artículo anterior vimos cómo lanzar excepciones en PHP con throw, una utilidad necesaria para informar a los demás cuando ocurren errores excepcionales, que se quedan fuera del control de nuestro software.

En este artículo queremos explicar cómo capturar y tratar excepciones utilizando la estructura try / catch de PHP. Si conoces otros lenguajes verás que realmente no hay muchas cosas diferentes a cómo se manejan las excepciones en PHP.

En qué consiste el bloque try / catch

Cuando una excepción se lanza con throw, si no es capturada mediante un bloque try / catch, el programa se detiene y PHP muestra un error fatal. En la mayoría de los casos no querrás que ese error lo reciban tus usuarios en crudo, porque ofrecería una pobre experienca. Para evitarlo, usamos una sentencia try catch.

Básicamente esta sentencia nos permite definir dos bloques:

Por qué es importante capturar las excepciones

Como hemos dicho, las excepciones no capturadas harán que el programa se interrumpa brúscamente y eventualmente se muestren las descripciones de los errores al usuario, algo que generalmente no deseamos.

Los errores solamente se muestran en la página en caso que tengamos activada la salida de los errores como texto en el programa, algo que se hace en la configuración de PHP con la propiedad display_errors.

En lugar que el programa se detenga cuando se produzcan errores, podemos gestionarlos de manera personalizada con los bloques try / catch. Esto nos permite mostrar mensajes de error personalizados al usuario y producir cualquier tipo de comportamiento que necesites realizar de manera adicional, como por ejemplo registrar errores en logs para su análisis posterior.

Pero además de todo esto, lo mejor es que podremos continuar la ejecución del programa si es necesario, por ejemplo para realizar alguna acción fallback o introducir la solicitud en una cola para intentarla más adelante. Todo eso ya depende de la lógica que quieras o necesites implementar.

Ejemplo básico de try / catch

Vamos a ver un primer ejemplo de estructura try / catch. Verás que se compone de los dos bloques que hemos comentado antes, donde colocamos entre llaves las sentencias asociadas a la parte que puede producir un error y el posible tratamiento.

Pero antes de ver esta estructura vamos a ver una función que podría lanzar una excepción.

function dividir($a, $b) {
    if ($b === 0) {
        throw new Exception("No se puede dividir por cero.");
    }

    return $a / $b;
}

Si queremos invocar a esta función y capturar una posible excepción que se produzca si la invocamos con un divisor igual a cero, podríamos usar esta estructura try catch:

try {
    // Código que puede lanzar una excepción
    $resultado = dividir(10, 0);
} catch (Exception $e) {
    // Código que se ejecuta si ocurre una excepción
    echo "Ocurrió un error: " . $e->getMessage();
}

Al ejecutarse este último pedazo de código se producen estos pasos:

  1. PHP ejecuta el bloque try.
  2. Si se lanza una excepción dentro del try, PHP interrumpe su ejecución inmediatamente.
  3. PHP busca el primer bloque catch que pueda capturar el tipo de excepción lanzada.

Además, el objeto de la excepción (en este caso lo hemos recibido en la variable $e) nos permite acceder a detalles del error, como:

Múltiples bloques catch

Si lo deseamos, podemos incorprar varios bloques catch después de un bloque try, lo que nos permite capturar distintos tipos de excepciones que puedan producirse en un mismo bloque.

Este podría ser útil cuando usamos una librería particular que, ante una misma función pueda lanzar distintos tipos de errores. Por ejemplo, un sistema de pago que pueda dar error porque la tarjeta bancaria está mal formada (los datos no corrrespondan con los de una tarjeta válida), porque el pago lo ha rechazado el banco, etc.

try {
    procesarPago($pedido);
} catch (PagoInvalidoException $e) {
    // Error específico al validar el pago
    logError($e);
    mostrarMensaje("Pago no válido");
} catch (Exception $e) {
    // Cualquier otra excepción
    logError($e);
    mostrarMensaje("Ocurrió un error inesperado");
}

Este código es muy interesante porque nos permite entender que existe una excepción "raíz" de todas las excepciones que son controladas o lanzadas por nuestro propio código o el código de las librerías que usamos. Esa excepción "raíz" es de la clase Exception y puedes pensar en ella como una excepción "genérica".

Nosotros como programadores podemos crear excepciones personalizadas que heredan de Exception. Por ejemplo, en el código anterior se usó PagoInvalidoException que podría haberse definido así:

class PagoInvalidoException extends Exception {}

Además, PHP define muchas excepciones nativas (ya preparadas), agrupadas en categorías, para lanzar excepciones más personalizadas. Es toda una jerarquía de excepciones nativas. Por ejemplo, una muy típica es InvalidArgumentException. Todo esto ya se explicó en el artículo anterior de lanzar excepciones.

Ten en cuenta que el orden de los catch es importante. Los más específicos deben ir antes que los genéricos. Por tanto, si pones catch (Exception $e) al principio, capturará todo y los siguientes nunca se ejecutarán.

Captura múltiple en un solo catch en PHP 7.1+

Desde PHP 7.1 puedes capturar múltiples tipos de excepciones en un único bloque. Para ello puedes usar una sentencia como la siguiente:

try {
    ejecutarOperacion();
} catch (TipoAException | TipoBException $e) {
    logError($e);
    mostrarMensaje("Error en la operación");
}

Uso de finally

Ya que hablamos de try catch debemos también mencionar el bloque finally. Básicamente es algo más que puedes incorporar a la estructura y, si se incluye, se ejecuta siempre, haya habido excepción o no. Un uso típico podría ser liberar recursos, cerrar conexiones, etc.

try {
    conectarBaseDeDatos();
    realizarConsulta();
} catch (PDOException $e) {
    echo "Error de base de datos: " . $e->getMessage();
} finally {
    cerrarConexion();
}

Errores Vs Excepciones

A veces los errores y las excepciones pueden resultar parecidos pero no son lo mismo. Mira este bloque que podría parecer correcto pero no lo es.

try {
    // Código que puede lanzar una excepción
    $resultado = 10 / 0;
} catch (Exception $e) {
    // Código que se ejecuta si ocurre una excepción
    echo "Ocurrió un error: " . $e->getMessage();
}

Si ejecutas ese pedazo de código obtendrás el siguiente error: Fatal error: Uncaught DivisionByZeroError: Division by zero in.... Eso es porque, dividir por cero no lanza una excepción, sino que lanza un "warning" o "error" (dependiendo del entorno, aunque desde PHP 8 incluso un DivisionByZeroError).

Por tanto, debe quedar claro que:

Cómo tratar los errores

Aunque no es tan común, porque si ocurre un error lo normal es que examines tu código para ver qué has hecho mal, también sería posible tratar los errores de manera similar a como tratas las excepciones. sin embargo, en lugar de cazar excepciones de tipo Exception (o cualquier otra más específica), lo que tienes que cazar son errores de la clase Throwable, como puedes ver en el siguiente código.

try {
    $resultado = 10 / 0;
} catch (Throwable $e) {
    echo "Error atrapado: " . $e->getMessage();
}

Igual que ocurre con las excepciones, existe una jerarquía de objetos Error nativos del propio lenguaje y podrías tratarlos en varios bloques catch, por ejemplo.

<?php

try {
    // Esto lanza un DivisionByZeroError (subclase de Error)
    $resultado = 10 / 0;
    echo "Resultado: $resultado";
} catch (DivisionByZeroError $e) {
    echo "Error de división por cero: " . $e->getMessage();
} catch (Error $e) {
    // Captura cualquier otro tipo de Error
    echo "Error grave del sistema: " . $e->getMessage();
}

En el código anterior Error es un error genérico, mientras que DivisionByZeroError es un error más específico.

Ya para concluir todo lo anterior, es importante que sepas:

Buenas prácticas al capturar excepciones

Ya para acabar vamos a mencionar una serie de consejos útiles para que organices correctamente tus códigos cuando captures las excepciones.

No usar try / catch para implementar la lógica de tu programa

No abuses de las excepciones, no uses try / catch para controlar el flujo normal del programa. Son solo un medio para capturar los errores, pero la lógica no debe apoyarse en esta estructura.

Intenta ser específico al capturar las excepciones

Es útil que intentes capturar las excepciones por su nombre y actuar para cada caso de manera adecuada. Esto significa que, a ser posible, debes capturar tipos concretos de excepciones para tratarlas adecuadamente, lo que ayuda a tener un código más claro y mantenible.

Usa las excepciones para lo que son

El bloque try catch no está simplemente para que los errores no rompan tu programa. Está para que puedas tratarlos adecuadamente.

Por tanto, evita catch vacíos o que simplemente oculten el error sin registrar nada. En el caso que no desees hacer nada con el error al menos registra los errores para que en producción se muestre un mensaje genérico al usuario y se guarden los detalles en un log.

Miguel Angel Alvarez

Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...

Manual