Capítulo 2.7: Gestión de Errores
Tiempo de estudio: 40 minutos
1. ¿Por qué los errores estándar son malos?
Si ocurre un error en su aplicación Laravel (por ejemplo, no se encuentra un registro en la base de datos) y no lo maneja de ninguna manera, el usuario verá una enorme página HTML con información de depuración o un mensaje poco informativo de "Error del Servidor".
Para una API, esto es una catástrofe. Su aplicación frontend espera recibir JSON, no HTML. Nuestra tarea es interceptar cualquier error y convertirlo en una respuesta JSON estructurada.
2. El despachador central de errores: bootstrap/app.php
En las versiones antiguas de Laravel, había un archivo voluminoso App\Exceptions\Handler.php
. En Laravel 11/12, todo es mucho más simple y elegante. El centro de gestión de errores ahora se encuentra directamente en el archivo de configuración de su aplicación: bootstrap/app.php
.
Abra bootstrap/app.php
. Al final, verá el bloque .withExceptions(...)
. Este es nuestro "despachador central".
<?php
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// ...
})
->withExceptions(function (Exceptions $exceptions) {
// <-- AQUÍ ES DONDE TRABAJAREMOS
})->create();
3. Manejando el error más común: "No encontrado" (404)
El error más común en una API es cuando el usuario solicita un recurso que no existe (por ejemplo, GET /api/planets/999
). Laravel en este caso genera una excepción ModelNotFoundException
o NotFoundHttpException
. Vamos a interceptarlas.
Agregue el siguiente código dentro de .withExceptions(...)
:
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// Interceptamos la excepción cuando el modelo no se encuentra en la base de datos
$exceptions->render(function (ModelNotFoundException $e, Request $request) {
// Verificamos que la solicitud provenga específicamente de nuestra API
if ($request->is('api/*')) {
return response()->json([
'message' => 'El recurso solicitado no se encuentra en nuestra galaxia.'
], 404);
}
});
// Interceptamos la excepción cuando la ruta misma no se encuentra
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Esta ruta cósmica no existe.'
], 404);
}
});
})->create();
$exceptions->render(...)
— Registramos un "manejador". Dice: "Si ocurre una excepción de tipoModelNotFoundException
, ejecuta este código".if ($request->is('api/*'))
— Esta es una verificación importante. Garantiza que nuestra bonita respuesta JSON solo se enviará para solicitudes de API, sin afectar las páginas web normales.return response()->json(...)
— Creamos y devolvemos una respuesta JSON estandarizada con el código 404.
Ahora, si solicita un planeta inexistente, en lugar de una página HTML fea, recibirá un JSON ordenado.
4. Excepciones personalizadas: Creando nuestras propias "señales de alarma"
A veces, las excepciones estándar no son suficientes. Imaginemos que tenemos una regla de negocio: "no se puede eliminar el planeta 'Tierra'". Si alguien intenta hacerlo, debemos devolver un error significativo.
Paso 1: Creamos nuestra propia clase de excepción Ejecutamos en la terminal:
Paso 2: La usamos en el controlador
Abrimos PlanetController.php
y modificamos el método destroy
:
<?php
// app/Http/Controllers/PlanetController.php
use App\Exceptions\CannotDeleteEarthException; // <-- Importamos nuestra excepción
use App\Models\Planet;
public function destroy(Planet $planet)
{
// Nuestra nueva regla de negocio
if (strtolower($planet->name) === 'земля') {
throw new CannotDeleteEarthException('La eliminación del planeta Tierra está prohibida por el Código Galáctico.');
}
$planet->delete();
return response()->json(null, 204);
}
DELETE /api/planets/1
(donde 1 es el ID de la Tierra), nuestro código lanzará una excepción CannotDeleteEarthException
.
Paso 3: Enseñamos a Laravel a manejar bellamente nuestra "alarma"
Volvamos a bootstrap/app.php
y agreguemos un nuevo manejador para nuestra excepción.
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// Nuestro nuevo manejador
$exceptions->render(function (CannotDeleteEarthException $e, Request $request) {
return response()->json([
'message' => 'Operación prohibida.',
'details' => $e->getMessage() // Obtenemos el mensaje que pasamos en throw
], 403); // 403 Forbidden - "Acceso denegado"
});
// ... (otros manejadores para 404)
})->create();
5. Gestión de todos los demás fallos (500 Internal Server Error)
¿Qué hacer con todos los demás errores inesperados? Por ejemplo, si la base de datos se cae o hay un error de sintaxis en el código. Para esto, podemos registrar un manejador "universal" para el tipo de error más general: Throwable
.
Importante: Este manejador debe ser el último para no interceptar excepciones más específicas que definimos anteriormente.
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// ... (manejadores para CannotDeleteEarthException y 404)
// MANEJADOR UNIVERSAL (al final de todo)
$exceptions->render(function (Throwable $e, Request $request) {
if ($request->is('api/*')) {
// En modo de depuración, se puede mostrar el mensaje de error real
$message = config('app.debug')
? 'Ha ocurrido un error: ' . $e->getMessage()
: 'Ha ocurrido un error inesperado a bordo. Los ingenieros ya han sido llamados.';
return response()->json(['message' => $message], 500);
}
});
})->create();
Ahora, cualquier excepción "desconocida" será interceptada cuidadosamente y convertida en JSON con el código 500, sin romper su API ni mostrar información innecesaria al usuario.
6. Registro de errores: La caja negra de la nave espacial
Configuración de registro en config/logging.php
:
<?php
'channels' => [
'space_api' => [
'driver' => 'daily',
'path' => storage_path('logs/space_api.log'),
'level' => 'error',
'days' => 14,
],
],
Añadir un registro al log:
<?php
try {
// Código con riesgo de error
} catch (Exception $e) {
Log::channel('space_api')->error('Error de acceso a los planetas', [
'exception' => $e,
'request' => request()->all(),
'user_id' => auth()->id()
]);
throw $e;
}
Quiz de afianzamiento
🚀 Resumen del capítulo:
Has equipado tu API con un robusto sistema de rescate:
- 🛟 Intercepción global de errores estándar
- 🪐 Excepciones personalizadas con códigos comprensibles
- 📝 Formato JSON unificado para todos los errores
- 🔍 Registro con detalles del incidente
- 📡 Integración con sistemas de monitoreo
¡La nave espacial está lista para emergencias! En el capítulo final de la sección, probaremos todos los sistemas.
📌 Verificación:
- Crea la excepción
PlanetNotFoundException
- Agrega el manejo de errores 404 en
->withExceptions
- Prueba la solicitud a un planeta inexistente
⚠️ Si los errores no se interceptan:
- Asegúrate de que
is('api/*')
coincide con tus rutas- Verifica el orden de los manejadores en
register()
- Para excepciones personalizadas, usa
throw new