Crear excepciones personalizadas en PHP te permite capturar errores de forma precisa según el dominio de tu aplicación. En lugar de capturar RuntimeException genérica y adivinar el origen, puedes definir una jerarquía propia donde cada tipo de error tiene su clase, su información adicional y su lugar exacto en el árbol de excepciones.
La base: extender Exception o RuntimeException
<?php
// Excepción raíz de la aplicación
class AppException extends RuntimeException {}
// Subclases por dominio
class ValidationException extends AppException
{
public function __construct(
private readonly array $errores,
string $message = 'Error de validación',
int $code = 422,
?Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
public function getErrores(): array
{
return $this->errores;
}
}
class DatabaseException extends AppException
{
public function __construct(
string $consulta,
string $message = 'Error de base de datos',
?Throwable $previous = null,
) {
parent::__construct("$message consulta: $consulta", 500, $previous);
}
}
class NotFoundException extends AppException
{
public function __construct(string $recurso, int|string $id)
{
parent::__construct("$recurso con id '$id' no encontrado", 404);
}
}
?>
Usar la jerarquía en el código
<?php
class UsuarioServicio
{
private array $usuarios = [
1 => ['nombre' => 'Ana', 'email' => '[email protected]'],
];
public function crear(array $datos): array
{
$errores = [];
if (empty($datos['nombre'])) {
$errores['nombre'] = 'El nombre es obligatorio';
}
if (empty($datos['email']) || !filter_var($datos['email'], FILTER_VALIDATE_EMAIL)) {
$errores['email'] = 'Email inválido';
}
if ($errores) {
throw new ValidationException($errores);
}
$id = count($this->usuarios) + 1;
$this->usuarios[$id] = $datos;
return ['id' => $id] + $datos;
}
public function encontrar(int $id): array
{
if (!isset($this->usuarios[$id])) {
throw new NotFoundException('Usuario', $id);
}
return $this->usuarios[$id];
}
}
$servicio = new UsuarioServicio();
// Captura específica
try {
$user = $servicio->crear(['nombre' => '', 'email' => 'invalido']);
} catch (ValidationException $e) {
echo $e->getMessage() . "n";
print_r($e->getErrores());
}
// Error de validación
// Array ( [nombre] => El nombre es obligatorio [email] => Email inválido )
try {
$user = $servicio->encontrar(99);
} catch (NotFoundException $e) {
echo $e->getMessage(); // Usuario con id '99' no encontrado
}
?>
Capturar por clase padre: AppException
<?php
// En un controlador o middleware puedes capturar toda la jerarquía
function manejarPeticion(callable $controlador): array
{
try {
return $controlador();
} catch (ValidationException $e) {
return ['status' => 422, 'errores' => $e->getErrores()];
} catch (NotFoundException $e) {
return ['status' => 404, 'mensaje' => $e->getMessage()];
} catch (AppException $e) {
return ['status' => $e->getCode(), 'mensaje' => $e->getMessage()];
} catch (Throwable $e) {
// Errores inesperados del sistema
error_log($e->getMessage());
return ['status' => 500, 'mensaje' => 'Error interno del servidor'];
}
}
$servicio = new UsuarioServicio();
$respuesta = manejarPeticion(fn() => $servicio->encontrar(99));
// ['status' => 404, 'mensaje' => "Usuario con id '99' no encontrado"]
?>
Añadir contexto adicional a la excepción
<?php
class ApiException extends AppException
{
public function __construct(
private readonly int $statusHttp,
private readonly string $codigoError,
string $message,
?Throwable $previous = null,
) {
parent::__construct($message, $statusHttp, $previous);
}
public function getStatusHttp(): int { return $this->statusHttp; }
public function getCodigoError(): string { return $this->codigoError; }
public function toArray(): array
{
return [
'codigo' => $this->codigoError,
'mensaje' => $this->getMessage(),
'status' => $this->statusHttp,
];
}
}
throw new ApiException(429, 'RATE_LIMIT_EXCEEDED', 'Demasiadas peticiones');
?>
La documentación oficial sobre extender excepciones en PHP y el catálogo de excepciones SPL muestran las clases base disponibles y cuándo es apropiado usar cada una en lugar de crear una nueva.
