Qué son las excepciones en PHP y cuándo se deben levantar en nuestras aplicaciones o en las librerías que estamos desarrollando. Ejemplos sobre levantar excepciones, o lanzar errores, con la sentencia throw de PHP.
Todos los lenguajes de programación ofrecen mecanismos para el tratamiento de excepciones, es decir errores excepcionales o inesperados que están lejos del ámbito de las posibilidades y responsabilidades que deberíamos tratar en nuestra lógica. Por supuesto PHP no es una excepción y con el lenguaje podemos lanzar errores o excepciones de una manera sencilla, que vamos a mostrar en este artículo.
Qué son situaciones excepcionales
Antes de ponernos a mostrar código sobre cómo se lanzan errores o excepciones en PHP convendría definir qué es un error excepcional. Pues bien no es más que un error que no debería ocurrir en circunstancias normales, que rompe el flujo lógico de nuestros programas, quedando fuera del alcance de nuestra responsabilidad.
Quizás puede quedar un poco ambigua esta descripción pero seguro que con unos ejemplos lo podemos entender perfectamente.
- Acceder a un recurso de red cuando la red no está disponible.
- Conectar con una base de datos cuando el servidor está caído
- Se intenta acceder a un fichero que ya no existe
Todas las anteriores situaciones pueden ocurrir a veces, pero no son responsabilidad de nuestra aplicación. Ante estos casos lo común es levantar excepciones, especialmente si estamos desarrollando un código que va a ser consumido por otros clientes, como por ejemplo una biblioteca de código que hace ciertas cosas.
Por ejemplo, imaginemos que estamos desarrollando una librería que permite conectarse con el API de Youtube para extraer información de vídeos y, de repente, Youtube deja de responder ¿es acaso nuestra responsabilidad? entonces simplemente levantamos un error y listo.
Excepciones contra programación defensiva
Alguien se preguntará: ¿Por qué levantar excepciones y no actuar de alguna manera en caso de error? Por ejemplo, en el caso de la librería que conecta con el API de YouTube, si no podemos conectar correctamente con el API para acceder a los datos de un vídeo, de volver null
.
Eso podría ser una posibilidad pero es mejor avisar a quien nos usa de que ha ocurrido una situación excepcional. Si devolvemos null
simplemente estamos obligando al cliente a verificar siempre si lo que le enviamos de vuelta es un valor válido, de tipo objeto con los campos que esperaba. En cuanto si devolvemos una excepción puede verificarlo de una manera más sencilla con un simple bloque try / catch
.
Pero aparte de evitar que nuestros clientes deban comprobar constantemente valores de retorno (if (!$result) { ... }
), devolver excepciones ofrece otras ventajas:
- Permite a los clientes separar el flujo del manejo de errores al flujo ante situaciones normales.
- Proporcionar información detallada del problema que se ha producido (mensaje, código, traza de pila...).
- Permitir que el error sea manejable por niveles superiores de las aplicaciones, escalando en la pila de invocaciones, sin forzar a cada función a resolverlo.
Cuándo no se deben lanzar excepciones
Por supuesto, no todas las situaciones son excepcionales y por tanto, no siempre que ocurra algo que no forma parte de lo esperado, debe ser respondido con una excepción.
Por ejemplo, si estamos validando un formulario de login y no recibimos el nombre del usuario, entonces lo normal es mostrar un mensaje al usuario para que lo escriba. Si estamos esperando que nos den una descripción de menos de 300 caracteres, lo debemos advertir. Si estamos programando una función que dice si un email es válido y nos mandan un email incorrecto, entonces devolvemos un false
.
Todo lo anterior, como puedes entender, no son situaciones excepcionales sino cosas que debes cubrir en la lógica de tus aplicaciones.
Pero incluso, situaciones excepcionales como que no se puede conectar con la base de, en ocasiones debemos tratarla en la lógica. Por ejemplo, si estás desarrollando una web con código spaguetti, mezclando la presentación en HTML con la lógica en PHP, y la sentencia de conexión con la base de datos no funciona, entonces lo más normal es que hagas algo en vez de levantar una excepción, para que tus usuarios vean un mensaje claro que les informe que ahora el sitio web está fuera de servicio.
Esto último puede parecer contradictorio con lo que hemos dicho antes. ¿Cuando levantaré una excepción y cuándo trataré el error de conexión ante una base de datos caída? puedes depende un poco de la arquitectura de tu software. Si estás en una página básica, haciendo la conexión con tu base de datos al vuelo, mezclando código PHP con código HTML, es decir, enviando directamente salida al usuario, entonces lo normal sería tratar el error. Si tienes una buena arquitectura del software lo normal sería que escribas el código de conexión con la base de datos en un módulo aparte y levantes la excepción desde ese módulo, siendo tu controlador el encargado de mostrar una salida apropiada en caso que haya un error, o delegando la excepción para un nivel superior donde pueda ser tratada. Si no entiendes eso, quizás no sea el momento tampoco de preocuparte por ello, ya quedará patente para ti cuando vayas alcanzando mayor experiencia.
Ejemplo de excepción en PHP
Vamos a ver ahora un primer lanzamiento de error o excepción con PHP. En este ejemplo hacemos una función que intenta acceder a un dato de una URL y trata el caso en el que la URL no responda.
function fetchApiData(string $url): string
{
$context = stream_context_create([
'http' => [
'timeout' => 5, // segundos
]
]);
// La @ aquí sirve para que PHP no genere advertencias
$response = @file_get_contents($url, false, $context);
if ($response === false) {
throw new \RuntimeException("No se pudo acceder a la URL del API: $url");
}
return $response;
}
En el código anterior usamos throw
para levantar la excepción. La excepción se crea con un new
y luego el nombre de la clase de la excepción, existiendo varios tipos de excepciones que vienen incorporadas en el lenguaje.
La contrabarra antes del nombre de la clase de la excepción
\RuntimeException
es para indicar que esta clase está en el namespace raíz de PHP. Es decir, es una clase que forma parte del lenguaje de programación.
Ahora veamos un segundo ejemplo que ocurre cuando programo una función y me llaman con parámetros incorrectos.
function calcularPrecioConIVA(float $precioBase, float $iva): float
{
if ($precioBase < 0) {
throw new \InvalidArgumentException("El precio base no puede ser negativo.");
}
if ($iva < 0 || $iva > 100) {
throw new \InvalidArgumentException("El porcentaje de IVA debe estar entre 0 y 100.");
}
return $precioBase * (1 + $iva / 100);
}
En este caso, antes de calcular el precio con iva, se verifican casos que no quiero que se produzcan cuando me llamen. Puede que sean discutibles esos casos, pero tómalo como un ejemplo. En este caso quiero levantar la excepción de tipo InvalidArgumentException
, porque me llamaron mal.
Toma el anterior ejemplo como una posible implementación de excepción. En estos casos muchos prefieren programar con aserciones, por diseño por contrato, en vez de excepciones y pueden ser muy útiles en muchos casos.
Cómo hacer excepciones personalizadas
Aparte de las excepciones propias del lenguaje, nosotros también podemos crear excepciones personalizadas con clases definidas en el código de la aplicación. Estas excepciones pueden representar errores específicos del dominio de nuestra aplicación y ofrecer a quienes las tengan que tratar información más completa sobre el problema que se ha producido.
De este modo podemos especificar casos concretos de fallos y mejorar la mantenibilidad y la legibilidad del código. Incluso, los desarrolladores podrían capturar solamente una excepción o excepciones particulares, para los errores que les interesen tratar.
Para definir tu propia clase de excepción tienes que extender alguna excepción disponible en PHP, de la siguiente manera.
<?php
namespace App\Exceptions;
class UserNotFoundException extends \RuntimeException
{
// Puedes añadir lógica personalizada si lo necesitas
}
Luego puedes lanzar tu excepción cuando sea necesario en el código
use App\Exceptions\UserNotFoundException;
function getUserById(int $id): User
{
$user = UserRepository::find($id);
if (!$user) {
throw new UserNotFoundException("User with ID $id not found.");
}
return $user;
}
En tus aplicaciones puedes crear una jerarquía de excepciones con una excepción base para toda la aplicación y excepciones particulares que extiende de ella.
<?php
namespace App\Exceptions;
class AppException extends \Exception {}
class UserNotFoundException extends AppException {}
class InvalidCourseAssignmentException extends AppException {}
Está estructura jerárquica de excepciones permite que los clientes puedan tratar de manera global la excepción si lo necesitan.
Cómo capturar las excepciones
Hasta este punto hemos explicado cómo levantar excepciones pero también resultará extremadamente importante tratarlas cuando se produzcan. Para ello utilizamos una estructura muy tradicional de los lenguajes de programación, con las sentencias try
/ catch
.
Vamos a hacer un ejemplo básico para no dejarlo en el aire, pero lo abordaremos con más detalle en el próximo artículo.
try {
// alguna operación
} catch (App\Exceptions\AppException $e) {
// Manejar errores de la app, sin afectar excepciones del sistema
}
Dentro del bloque try
colocamos operaciones que puedan levantar excepciones. Posteriormente con el bloque catch
capturamos excepciones y escribimos el código que debe ejecutarse cuando se produzcan.
En el próximo artículo profundizaremos sobre este bloque try
/ catch
.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...