FormRequest bez Form'y
FormRequest w Laravel służy głównie do walidiacji requestu z form - cóż za odkrywczość - powiecie. Niekiedy jednak nie posiadamy formy, a chcemy zwalidować sam request - jest na to prosta i sprawdzona metoda.
Sam przyznam o ile FormRequest'y są prostym i nawet zgrabnym sposobem do walidacji danych wejściowym (z form) to niestety do walidacji innych danych się słabo sprawdzają - zwłaszcza kiedy chcemy zwalidować czy dany zasób istnieje. Oczywiście można też pokusić się o walidację na samych route’ach, gdzie definiujemy endpointy naszej aplikacji (jednak za tym nie przepadam szczególnie - rozbija to logikę na różne miejsca i jest - kodowo - brzydkie).
Podstawową bolączką jest to, to co jest zwracane w razie nie poprawnej walidacji danych - wyjątek z błędnymi danymi (messageBag) z walidatora dotyczących poszczególnych pól. W momencie kiedy sprawdzamy jedynie czy dany zasób istnieje (po id, slug itd.) w razie braku takowego powinniśmy otrzymywać błąd 404 w reponse’ie [link do rfc].
Najlepszym sposobem obejścia tego jest utworzenie osobnego (abstrakcyjnego) FormRequest’a rozszerzającego tego dostarczanego z framework'iem i nadpisanie metody publicznej failedValidation
- np. w taki sposób:
<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\App;
use Symfony\Component\HttpFoundation\Response;
abstract class AbstractHttpErrorFormRequest extends FormRequest
{
protected int $httpErrorCode = Response::HTTP_NOT_FOUND;
protected string $httpErrorMessage = 'Resource Not found';
/** {@inheritDoc} */
public function failedValidation(Validator $validator): void
{
App::abort($this->httpErrorCode, $this->httpErrorMessage);
}
}
Potem już dane nasze FormRequest’y wymagającej takiej logiki wystarczy rozszerzać o przygotowaną przez nas klasę - i tylko tyle (lub aż tyle) :). Dodatkowo można pokusić się - jeżeli zajdzie oczywiście taka potrzeba - o przeciążenie pól na klasie $httpErrorCode
oraz $httpErrorMessage
i dostosowanie do danej potrzeby.
<?php
namespace App\Http\Requests\Article;
use App\Enums\Article\RequestParameterEnum;
use App\Http\Requests\AbstractHttpErrorFormRequest;
class CategoryRequest extends AbstractHttpErrorFormRequest
{
/** {@inheritDoc} */
public function authorize(): bool
{
return true;
}
/** {@inheritDoc} */
public function rules(): array
{
return [
RequestParameterEnum::categorySlug->value => [
'required',
'string',
'exists:categories,slug',
],
];
}
}
Oczywiście nie jest to idealne rozwiązanie - możemy napotkać bardziej skomplikowane walidacje, które będą wymagały od nas bardziej skomplikowanych działań (choć w tym miejscu zastanowiłbym się, czy nie robimy czegoś źle akurat - albo czy nie da się prościej podejść do problemu).
Konkluzja? Nie bójmy się nadpisywać, przeciążać, rozszerzać - na tym polega piękno sztuki programowania w końcu! :)