Laravel está muy enfocado a mostrar páginas mediante templates, pero en mi caso lo estoy utilizando para implementar un API. En este caso, la gestión de excepciones que viene de base en Laravel (aquí la documentación oficial) se queda un poco coja porque aunque nos permita devolver un código de error en cualquier momento:
abort(404);
No permite modelar la respuesta de una excepción.
Por tanto, voy a unir todos los puntos que vengo mostrando ya en un par de artículos, que es poder utilizar DTOs que validen su propio modelo; y para esto necesito gestionar las excepciones de una manera similar a como se hace en Spring Boot en Java (y que es muy simple y muy potente).
Para ello vamos a:
- Definir una excepción que devuelva un JSON.
- Utilizar la excepción en el DTO
Tenemos la suerte que Laravel 8 ya viene con un Handler por defecto y que en la instalación por defecto podréis encontrar en:
App\Exceptions\Handler
Definir una excepción que devuelva un JSON
Lo primero es definir una excepción que podamos lanzar, que a la que le voy a definir 2 atributos: el código HTTP que tiene que devolver y el mensaje.
<?php
namespace App\Exceptions;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Mockery\Exception;
class CustomException extends Exception
{
protected $code;
protected $message;
public function __construct($code, $message)
{
$this->code = $code;
$this->message = $message;
}
public function render($request){
$response['message'] = $this->message;
return response()->json($response, $this->code);
}
}
Resalto el hecho de que la respuesta que devuelve la excepción mediante el método «render» es un JSON.
Utilizar la excepción en el DTO
Puede parecer magia, y casi lo es, pero ahora ya podemos utilizar la excepción en cualquier lugar y como quiero validar el modelo del DTO en el propio DTO, hay que modificar:
class BookStoreRequestBody extends DataTransferObject
{
public string $name;
public string $author;
public static function fromRequest(Request $request): self
{
return BookStoreRequestBody::fromArray($request->all());
}
public static function fromArray($request): self
{
$validator = Validator::make($request, [
'name' => 'required',
'author' => 'required',
]);
if($validator->fails()){
return response()->json($validator->errors(), 400);
}
return new self([
'name' => $request['name'],
'author' => $request['author'],
]);
}
}
La modificamos a:
class BookStoreRequestBody extends DataTransferObject
{
public string $name;
public string $author;
public static function fromRequest(Request $request): self
{
return BookStoreRequestBody::fromArray($request->all());
}
public static function fromArray($request): self
{
$validator = Validator::make($request, [
'name' => 'required',
'author' => 'required',
]);
if($validator->fails()){
throw new CustomException(400,'Bad request');
}
return new self([
'name' => $request['name'],
'author' => $request['author'],
]);
}
}
Y con esto ya estaríamos devolviendo la excepción directamente, ya que esa excepción sería capturada por el Handler y devolvería en la API la respuesta del método «render».
Prueba de la API
Voy a probar la API ahora con Postman (que viene a ser la mejor aplicación para probar una API de largo, si alguien conoce alguna mejor está invitado a comentarla) y ver que efectivamente recibo la respuesta:

Como podéis ver, he pasado de tener que validar en el controlador a tener un DTO que modela la petición y que además incluye la validación de su propio modelo.
Además, ya tenemos una excepción que podemos lanzar desde cualquier punto de código y nos va a dar una respuesta formateada tal y como queremos, en este caso con el código HTTP y el body JSON con la información deseada.
El código del ejemplo, lo podéis ver en github.
Deja una respuesta