Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Form requests

When a form has ten fields, the validation rules take up half of the controller method. A request class — a Form Request — takes them over. The controller receives data that is already validated and minds its own business.

Creating

php artisan make:request StorePostRequest

The class appears in app/Http/Requests.

Rules

The rules() method returns the rules. They are conveniently written as arrays:

namespace App\Http\Requests;
 
use Illuminate\Foundation\Http\FormRequest;
 
class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }
 
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string'],
            'category_id' => ['required', 'exists:categories,id'],
            'tags' => ['array'],
            'tags.*' => ['integer', 'exists:tags,id'],
        ];
    }
}

The exists rule checks that such a record is in the table. The tags.* form sets the rules for every element of an array.

Using it in a controller

It is enough to name the class as the parameter type. Validation runs before the method is entered:

public function store(StorePostRequest $request)
{
    $post = Post::create($request->validated());
 
    return redirect()->route('posts.show', $post);
}

validated() returns only the fields that have rules. Extra data from the request never reaches the database.

Checking permissions

The authorize() method decides whether the request may run at all. If it returns false, the user gets a 403 error:

public function authorize(): bool
{
    return $this->user()->can('update', $this->route('post'));
}

Custom messages

public function messages(): array
{
    return [
        'title.required' => 'Please enter the post title.',
        'title.max' => 'The title must not exceed 255 characters.',
    ];
}
 
public function attributes(): array
{
    return [
        'category_id' => 'category',
    ];
}

attributes() replaces the field name in the standard messages with a readable one.

Preparing data

prepareForValidation() runs before validation — it is where data is brought into shape:

use Illuminate\Support\Str;
 
protected function prepareForValidation(): void
{
    $this->merge([
        'slug' => Str::slug($this->title),
        'is_published' => $this->boolean('is_published'),
    ]);
}

Rules that depend on the record

When editing, uniqueness has to be checked while ignoring the record itself:

use Illuminate\Validation\Rule;
 
public function rules(): array
{
    return [
        'slug' => ['required', Rule::unique('posts')->ignore($this->route('post'))],
        'status' => ['required', Rule::in(['draft', 'published'])],
    ];
}

Common mistake

The $request->all() method returns everything the user sent, including fields that have no rules. Passing it to create() lets someone slip in an extra field — is_admin, for example. Take data only from validated().

Thanks for reading!

Donate to the site — we work hard for you.