# Add validation to your controllers

One way to add validation to your controllers is to inject a FormRequest in your asController method just as you would do in a controller.

public function asController(MyFormRequest $request)
{
    // Authorization and validation defined in MyFormRequest was successful.
}

However, that means yet another class, that is tightly coupled to this action, has to be created somewhere else in your application — typically in app/Http/Requests.

This is why Laravel Actions provides a special request class called ActionRequest.

An ActionRequest is a special FormRequest class that allows you to define your authorization and validation directly within your action. It will look for specific methods within your action and delegate to them when it needs to.

use Lorisleiva\Actions\ActionRequest;

public function asController(ActionRequest $request)
{
    // Authorization and validation defined in this class was successful.
}

This page documents these special methods that you may implement to define your authorization and validation.

# Authorization

Just like in a FormRequest, you may implement the authorize method that returns true if and only if the use is authorized to see access this action.

public function authorize(ActionRequest $request): bool
{
    return $request->user()->role === 'author';
}

You may use the can method on your authenticated user or the Gate facade to check for specific abilities defined in Laravel.

use Illuminate\Support\Facades\Gate;

public function authorize(ActionRequest $request): bool
{
    // Using the `can` method.
    return $request->user()->can('update', $request->route('article'));
    
    // Using the `Gate` facade (this allows for nullable users).
    return Gate::check('update', $request->route('article'));
}

Instead of returning a boolean, you may also return gate responses to provide a more detailed response.

use Illuminate\Auth\Access\Response;

public function authorize(ActionRequest $request): Response
{
    if ($request->user()->role !== 'author') {
        return Response::deny('You must be an author to create a new article.');
    }

    return Response::allow();
}

Just like in a FormRequest, it will return an AuthorizationException if authorization fails. You may provide your own authorization failure logic by implementing the getAuthorizationFailure method.

public function getAuthorizationFailure(): void
{
    throw new MyCustomAuthorizationException();
}

# Adding validation rules

You may implement the rules method to provide the rules to validate against the request data.

public function rules(): array
{
    return [
        'title' => ['required', 'min:8'],
        'body' => ['required', IsValidMarkdown::class],
    ];
}

You may then use the validated method inside your asController method to access the request data that went through your validation rules.

public function asController(ActionRequest $request)
{
    $request->validated();
}

# Custom validation logic

In addition to your validation rules, you may provide the withValidator method to provide custom validation logic.

It works just like in a FormRequest and provides the validator as a first argument allowing you to add "after validation callbacks".

use Illuminate\Validation\Validator;

public function withValidator(Validator $validator, ActionRequest $request): void
{
    $validator->after(function (Validator $validator) use ($request) {
        if (! Hash::check($request->get('current_password'), $request->user()->password)) {
            $validator->errors()->add('current_password', 'Wrong password.');
        }
    });
}

Very often, when you use withValidator, you just want to add a after callback on the validator.

Laravel Actions conveniently allows you to implement the afterValidator method directly to avoid the nested callback.

use Illuminate\Validation\Validator;

public function afterValidator(Validator $validator, ActionRequest $request): void
{
    if (! Hash::check($request->get('current_password'), $request->user()->password)) {
        $validator->errors()->add('current_password', 'Wrong password.');
    }
}

Alternatively, if you want full control over the validator that will be generated, you may implement the getValidator method instead.

Implementing this method will ignore any other validation methods such as rules, withValidator and afterValidator.

use Illuminate\Validation\Factory;
use Illuminate\Validation\Validator;

public function getValidator(Factory $factory, ActionRequest $request): Validator
{
    return $factory->make($request->only('title', 'body'), [
        'title' => ['required', 'min:8'],
        'body' => ['required', IsValidMarkdown::class],
    ]);
}

# Prepare for validation

Just like in a FormRequest, you may provide the prepareForValidation method to insert some custom logic before both authorization and validation are triggered.

public function prepareForValidation(ActionRequest $request): void
{
    $request->merge(['some' => 'additional data']);
}

# Custom validation messages

You may also customise the messages of your validation rules and provide some human-friendly mapping to your request attributes by implementing the getValidationMessages and getValidationAttributes methods respectively.

public function getValidationMessages(): array
{
    return [
        'title.required' => 'Looks like you forgot the title.',
        'body.required' => 'Is that really all you have to say?',
    ];
}

public function getValidationAttributes(): array
{
    return [
        'title' => 'headline',
        'body' => 'content',
    ];
}

Note that providing the getValidator method will ignore both of these methods too.

# Custom validation failure

Just like in a FormRequest, it will return an ValidationException if validation fails. This exception will, by default, redirect to the previous page and use the default error bag on the validator. You may customise both of these behaviours by implementing the getValidationRedirect and getValidationErrorBag methods respectively.

use Illuminate\Routing\UrlGenerator;

public function getValidationRedirect(UrlGenerator $url): string
{
    return $url->to('/my-custom-redirect-url');
}

public function getValidationErrorBag(): string
{
    return 'my_custom_error_bag';
}

Alternatively, you may override the validation failure that is being thrown altogether by implementing the getValidationFailure method.

public function getValidationFailure(): void
{
    throw new MyCustomValidationException();
}

Okay enough about controllers, let's now see how we can dispatch our actions as asynchronous jobs.

Last Updated: 12/17/2023, 11:52:06 AM