Excepciones Personalizadas
Las excepciones genéricas funcionan, pero crear tus propias excepciones te permite representar errores específicos de tu dominio, añadir información extra y capturarlas de forma más precisa.
Crear una excepción básica
Para crear una excepción personalizada,
simplemente extiende de
Exception o cualquiera de sus
subclases:
<?php
declare(strict_types=1);
// Excepción simple sin modificaciones
class UsuarioNoEncontradoException extends Exception
{
}
// Uso
function buscarUsuario(int $id): array
{
$usuarios = [
1 => ['nombre' => 'Ana', 'email' => 'ana@example.com'],
2 => ['nombre' => 'Luis', 'email' => 'luis@example.com'],
];
if (!isset($usuarios[$id])) {
throw new UsuarioNoEncontradoException("Usuario con ID $id no encontrado");
}
return $usuarios[$id];
}
try {
$usuario = buscarUsuario(999);
} catch (UsuarioNoEncontradoException $e) {
// Captura específica para este tipo de error
echo 'No se encontró el usuario: ' . $e->getMessage();
}
Añadir datos adicionales
Las excepciones personalizadas pueden incluir información extra relevante para el error:
<?php
declare(strict_types=1);
class ValidacionException extends Exception
{
private array $errores;
public function __construct(array $errores, string $mensaje = 'Error de validación')
{
parent::__construct($mensaje);
$this->errores = $errores;
}
public function getErrores(): array
{
return $this->errores;
}
}
// Uso
function validarFormulario(array $datos): array
{
$errores = [];
if (empty($datos['email'])) {
$errores['email'] = 'El email es requerido';
} elseif (!filter_var($datos['email'], FILTER_VALIDATE_EMAIL)) {
$errores['email'] = 'El email no es válido';
}
if (empty($datos['nombre'])) {
$errores['nombre'] = 'El nombre es requerido';
}
if (!empty($errores)) {
throw new ValidacionException($errores);
}
return $datos;
}
try {
$datos = validarFormulario(['email' => 'invalido', 'nombre' => '']);
} catch (ValidacionException $e) {
echo $e->getMessage() . "\n";
foreach ($e->getErrores() as $campo => $error) {
echo "- $campo: $error\n";
}
// Error de validación
// - email: El email no es válido
// - nombre: El nombre es requerido
}
Jerarquía de excepciones
Para aplicaciones más grandes, es útil crear una jerarquía de excepciones que represente los diferentes tipos de errores:
<?php
declare(strict_types=1);
// Excepción base de la aplicación
class AppException extends Exception
{
}
// Excepciones de dominio
class UsuarioException extends AppException
{
}
class UsuarioNoEncontradoException extends UsuarioException
{
public function __construct(int $id)
{
parent::__construct("Usuario con ID $id no encontrado", 404);
}
}
class UsuarioDuplicadoException extends UsuarioException
{
public function __construct(string $email)
{
parent::__construct("Ya existe un usuario con email: $email", 409);
}
}
// Excepciones de autenticación
class AuthException extends AppException
{
}
class CredencialesInvalidasException extends AuthException
{
public function __construct()
{
parent::__construct('Email o contraseña incorrectos', 401);
}
}
class SesionExpiradaException extends AuthException
{
public function __construct()
{
parent::__construct('Tu sesión ha expirado', 401);
}
}
// Uso con captura por jerarquía
try {
autenticarUsuario($email, $password);
} catch (CredencialesInvalidasException $e) {
// Error específico de credenciales
echo 'Credenciales incorrectas';
} catch (AuthException $e) {
// Cualquier otro error de autenticación
echo 'Error de autenticación: ' . $e->getMessage();
} catch (AppException $e) {
// Cualquier error de la aplicación
echo 'Error: ' . $e->getMessage();
}
Excepción con contexto HTTP
Para APIs y aplicaciones web, es útil que las excepciones incluyan información sobre el código HTTP correspondiente:
<?php
declare(strict_types=1);
class HttpException extends Exception
{
private int $statusCode;
private array $headers;
public function __construct(
string $message,
int $statusCode = 500,
array $headers = [],
?Throwable $previous = null
) {
parent::__construct($message, $statusCode, $previous);
$this->statusCode = $statusCode;
$this->headers = $headers;
}
public function getStatusCode(): int
{
return $this->statusCode;
}
public function getHeaders(): array
{
return $this->headers;
}
}
class NotFoundException extends HttpException
{
public function __construct(string $recurso = 'Recurso')
{
parent::__construct("$recurso no encontrado", 404);
}
}
class UnauthorizedException extends HttpException
{
public function __construct(string $message = 'No autorizado')
{
parent::__construct($message, 401, [
'WWW-Authenticate' => 'Bearer'
]);
}
}
class ForbiddenException extends HttpException
{
public function __construct(string $message = 'Acceso denegado')
{
parent::__construct($message, 403);
}
}
// Manejador de errores para API
function manejarExcepcion(Throwable $e): void
{
$statusCode = 500;
$body = ['error' => 'Error interno del servidor'];
if ($e instanceof HttpException) {
$statusCode = $e->getStatusCode();
$body = ['error' => $e->getMessage()];
foreach ($e->getHeaders() as $nombre => $valor) {
header("$nombre: $valor");
}
}
http_response_code($statusCode);
header('Content-Type: application/json');
echo json_encode($body);
}
// Uso
try {
$usuario = buscarUsuario(999);
} catch (Throwable $e) {
manejarExcepcion($e);
}
Ejemplo práctico: Sistema de pagos
<?php
declare(strict_types=1);
// Excepciones del dominio de pagos
class PagoException extends Exception
{
protected string $transaccionId;
public function __construct(string $mensaje, string $transaccionId = '')
{
parent::__construct($mensaje);
$this->transaccionId = $transaccionId;
}
public function getTransaccionId(): string
{
return $this->transaccionId;
}
}
class SaldoInsuficienteException extends PagoException
{
private float $saldoActual;
private float $montoRequerido;
public function __construct(float $saldoActual, float $montoRequerido)
{
$faltante = $montoRequerido - $saldoActual;
parent::__construct("Saldo insuficiente. Faltan \$$faltante");
$this->saldoActual = $saldoActual;
$this->montoRequerido = $montoRequerido;
}
public function getSaldoActual(): float
{
return $this->saldoActual;
}
public function getMontoFaltante(): float
{
return $this->montoRequerido - $this->saldoActual;
}
}
class TarjetaRechazadaException extends PagoException
{
private string $codigoRechazo;
public function __construct(string $codigoRechazo, string $transaccionId)
{
$mensajes = [
'INSUFFICIENT_FUNDS' => 'Fondos insuficientes en la tarjeta',
'EXPIRED_CARD' => 'La tarjeta ha expirado',
'INVALID_CVV' => 'CVV incorrecto',
'BLOCKED_CARD' => 'La tarjeta está bloqueada',
];
$mensaje = $mensajes[$codigoRechazo] ?? 'Tarjeta rechazada';
parent::__construct($mensaje, $transaccionId);
$this->codigoRechazo = $codigoRechazo;
}
public function getCodigoRechazo(): string
{
return $this->codigoRechazo;
}
}
// Servicio de pagos
class ServicioPago
{
private float $saldo = 100.00;
public function realizarPago(float $monto, string $metodoPago): string
{
if ($monto <= 0) {
throw new InvalidArgumentException('El monto debe ser positivo');
}
$transaccionId = uniqid('TXN_');
if ($metodoPago === 'saldo') {
if ($this->saldo < $monto) {
throw new SaldoInsuficienteException($this->saldo, $monto);
}
$this->saldo -= $monto;
} elseif ($metodoPago === 'tarjeta') {
// Simular rechazo aleatorio
if (rand(0, 1) === 0) {
$codigos = ['INSUFFICIENT_FUNDS', 'EXPIRED_CARD', 'INVALID_CVV'];
throw new TarjetaRechazadaException(
$codigos[array_rand($codigos)],
$transaccionId
);
}
}
return $transaccionId;
}
}
// Uso
$servicio = new ServicioPago();
try {
$txnId = $servicio->realizarPago(150.00, 'saldo');
echo "Pago exitoso: $txnId";
} catch (SaldoInsuficienteException $e) {
echo $e->getMessage();
echo "\nTe faltan: $" . $e->getMontoFaltante();
} catch (TarjetaRechazadaException $e) {
echo $e->getMessage();
echo "\nCódigo: " . $e->getCodigoRechazo();
echo "\nTransacción: " . $e->getTransaccionId();
} catch (PagoException $e) {
echo "Error en el pago: " . $e->getMessage();
}
Cuándo crear excepciones personalizadas
Crea una excepción personalizada cuando necesites: capturarla de forma específica, añadir datos adicionales, o representar un error de tu dominio. Si solo necesitas un mensaje diferente, usa las excepciones SPL existentes.
<?php
declare(strict_types=1);
// NO necesitas excepción personalizada para esto:
throw new InvalidArgumentException('El email no es válido');
// SÍ necesitas cuando quieres:
// 1. Capturar de forma específica
try {
// ...
} catch (EmailInvalidoException $e) {
// Manejar específicamente emails inválidos
}
// 2. Añadir datos del dominio
class ProductoAgotadoException extends Exception
{
public function __construct(
private string $sku,
private int $stockActual
) {
parent::__construct("Producto $sku agotado");
}
public function getSku(): string { return $this->sku; }
public function getStockActual(): int { return $this->stockActual; }
}
// 3. Agrupar errores relacionados
try {
procesarPedido($datos);
} catch (PedidoException $e) {
// Captura todas las excepciones relacionadas con pedidos
}
Ejercicios
Ejercicio 1: Excepción de carrito
Crea una excepción
CarritoVacioException que
se lance cuando se intente procesar un
carrito sin productos. Incluye un método
para obtener el ID del carrito.
Ver solución
<?php
declare(strict_types=1);
class CarritoVacioException extends Exception
{
private string $carritoId;
public function __construct(string $carritoId)
{
parent::__construct("El carrito $carritoId está vacío");
$this->carritoId = $carritoId;
}
public function getCarritoId(): string
{
return $this->carritoId;
}
}
class Carrito
{
private string $id;
private array $productos = [];
public function __construct(string $id)
{
$this->id = $id;
}
public function procesar(): void
{
if (empty($this->productos)) {
throw new CarritoVacioException($this->id);
}
// Procesar carrito...
}
}
// Uso
try {
$carrito = new Carrito('CART-123');
$carrito->procesar();
} catch (CarritoVacioException $e) {
echo $e->getMessage() . "\n";
echo "ID del carrito: " . $e->getCarritoId();
}
Ejercicio 2: Jerarquía de archivos
Crea una jerarquía de excepciones para
operaciones con archivos:
ArchivoException (base),
ArchivoNoEncontradoException,
PermisosDenegadosException,
ArchivoCorruptoException.
Cada una debe tener la información
relevante (ruta, permisos, etc.).
Ver solución
<?php
declare(strict_types=1);
class ArchivoException extends Exception
{
protected string $ruta;
public function __construct(string $mensaje, string $ruta)
{
parent::__construct($mensaje);
$this->ruta = $ruta;
}
public function getRuta(): string
{
return $this->ruta;
}
}
class ArchivoNoEncontradoException extends ArchivoException
{
public function __construct(string $ruta)
{
parent::__construct("Archivo no encontrado: $ruta", $ruta);
}
}
class PermisosDenegadosException extends ArchivoException
{
private string $permisosRequeridos;
public function __construct(string $ruta, string $permisos)
{
parent::__construct("Permisos insuficientes para: $ruta", $ruta);
$this->permisosRequeridos = $permisos;
}
public function getPermisosRequeridos(): string
{
return $this->permisosRequeridos;
}
}
class ArchivoCorruptoException extends ArchivoException
{
private string $razon;
public function __construct(string $ruta, string $razon)
{
parent::__construct("Archivo corrupto: $ruta", $ruta);
$this->razon = $razon;
}
public function getRazon(): string
{
return $this->razon;
}
}
// Uso
try {
throw new PermisosDenegadosException('/etc/passwd', 'lectura');
} catch (ArchivoException $e) {
echo $e->getMessage() . "\n";
echo "Ruta: " . $e->getRuta();
}
Ejercicio 3: API con excepciones HTTP
Crea un manejador de excepciones para
una API que convierta diferentes
excepciones en respuestas JSON con el
código HTTP apropiado. Por ejemplo,
NotFoundException → 404,
ValidacionException → 422.
Ver solución
<?php
declare(strict_types=1);
class NotFoundException extends Exception
{
public function __construct(string $recurso)
{
parent::__construct("$recurso no encontrado", 404);
}
}
class ValidacionException extends Exception
{
private array $errores;
public function __construct(array $errores)
{
parent::__construct('Error de validación', 422);
$this->errores = $errores;
}
public function getErrores(): array
{
return $this->errores;
}
}
function manejarExcepcionApi(Throwable $e): void
{
$codigo = match (true) {
$e instanceof NotFoundException => 404,
$e instanceof ValidacionException => 422,
$e instanceof InvalidArgumentException => 400,
default => 500
};
$respuesta = ['error' => true, 'message' => $e->getMessage()];
if ($e instanceof ValidacionException) {
$respuesta['errores'] = $e->getErrores();
}
http_response_code($codigo);
header('Content-Type: application/json');
echo json_encode($respuesta, JSON_PRETTY_PRINT);
}
// Uso
try {
throw new ValidacionException([
'email' => 'Email inválido',
'edad' => 'Debe ser mayor de 18'
]);
} catch (Throwable $e) {
manejarExcepcionApi($e);
}
¿Has encontrado un error o tienes sugerencias para esta lección?
Enviar feedback¿Te está gustando el curso?
Tenemos cursos premium con proyectos reales y soporte.
Descubrir cursos premium