Before validating, a form request runs prepareForValidation(), which may rewrite the input, and then authorize(), which returns a bool or a gate Response carrying a message for the 403:
public function authorize(): Response
{
return $this->user()->is_admin
? Response::allow()
: Response::deny('Only staff can change the catalog.');
}
protected function prepareForValidation(): void
{
$this->merge([
'sku' => Str::upper((string) $this->sku),
'price' => ltrim((string) $this->price, '$'),
]);
}Output
$ U='-u reader@example.com:secret123'; N='name=Refactoring&category_id=1&format=hardcover'
$ curl -s $U -H "$H" -H "$J" -o r.json -w '%{http_code} ' -d "$N&sku=BK-1005&price=47.5" \
$B/products; jq -r .message r.json
403 Only staff can change the catalog.
$ curl -s $A -H "$H" -o /dev/null -w '%{redirect_url}\n' -d "$N&sku= bk-1005 &price=\$47.50" \
$B/products
http://127.0.0.1:8308/products/3The reader was refused before any rule, or its unique query, ran. The admin's bk-1005 and $47.50 were stored as BK-1005 and 47.50 (the global TrimStrings middleware had removed the spaces). Beyond a flag, call a policy: $this->user()->can('create', Product::class) (Authorization). after() returns closures that see the whole validator, for cross-field checks.