Laravel Intermédiaire

Comment gérer les FormRequest avec Laravel

Form Requests + Request typée (enum, date, int, bool, string) = moins d’implicite, erreurs claires, code plus sûr et lisible. Sans strings magiques.

8 min de lecture

Des formulaires Laravel typés

Quand vous traitez un formulaire dans Laravel, vous faites souvent : $request->input('age'), $request->input('start_date'), etc. En pratique, vous récupérez… une string, parfois un array, et beaucoup d’implicite.

Depuis Laravel 9+, l’API de Request propose une approche plus typée et plus expressive : boolean(), integer(), date(), enum(), string()… combinée avec des Form Requests bien définies, cela donne un code plus sûr, plus lisible, et plus simple à maintenir.

Form requests : la base d’un formulaire robuste

Imaginons un formulaire de création d’événement sportif : le user saisit un titre, un type d’événement (enum), une date de début, un nombre maximum de participants et un champ “public ?”.

Une Form Request typée et claire

<?php

declare(strict_types=1);

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
use App\Enums\EventType;

final class StoreEventRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()?->can('create', Event::class) ?? false;
    }

    public function rules(): array
    {
        return [
            'title'          => ['required', 'string', 'max:255'],
            'type'           => ['required', new Enum(EventType::class)],
            'starts_at'      => ['required', 'date', 'after:now'],
            'max_attendees'  => ['required', 'integer', 'min:1', 'max:500'],
            'is_public'      => ['required', 'boolean'],
        ];
    }

    public function attributes(): array
    {
        return [
            'title'         => __('events.fields.title'),
            'type'          => __('events.fields.type'),
            'starts_at'     => __('events.fields.starts_at'),
            'max_attendees' => __('events.fields.max_attendees'),
            'is_public'     => __('events.fields.is_public'),
        ];
    }
}

Les règles protègent l’intégrité des données. La méthode attributes() permet d’avoir des messages d’erreur lisibles, traduits, et cohérents avec votre interface.

Récupérer des valeurs typées depuis la request

Une fois la validation passée, vous pouvez exploiter l’API typée de Request au lieu de manipuler des strings partout.

Enums : fini les strings magiques

Supposons un enum PHP 8.1 pour le type d’événement :

<?php

declare(strict_types=1);

namespace App\Enums;

enum EventType: string
{
    case RUN       = 'run';
    case CYCLING   = 'cycling';
    case SWIMMING  = 'swimming';
}

Dans votre contrôleur, utilisez enum() pour récupérer directement une instance d’EventType :

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Requests\StoreEventRequest;
use App\Enums\EventType;
use App\Models\Event;

final class EventController
{
    public function store(StoreEventRequest $request): Event
    {
        $type = $request->enum(
            key: 'type',
            enum: EventType::class
        );

        // $type est de type ?EventType
        // La validation garantit qu'il n'est pas null ici.

        return Event::query()
            ->create([
                'title'         => $request->string('title')->toString(),
                'type'          => $type->value,
                'starts_at'     => $request->date('starts_at'),
                'max_attendees' => $request->integer('max_attendees'),
                'is_public'     => $request->boolean('is_public'),
            ]);
    }
}

Vous gagnez :

  • Une auto-complétion sur les valeurs possibles

  • Moins de bugs liés aux typos (pas de 'runing' au lieu de 'run')

  • Une meilleure expressivité métier : EventType::RUN est plus clair qu'une simple string

Dates typées : plus besoin de parser à la main

La méthode date() retourne directement un objet Carbon (ou CarbonImmutable selon votre config).

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Requests\StoreEventRequest;
use Carbon\CarbonInterface;

final class EventScheduleController
{
    public function preview(StoreEventRequest $request): array
    {
        $startsAt = $request->date('starts_at'); // CarbonInterface

        return [
            'day'   => $startsAt->translatedFormat('l'),
            'date'  => $startsAt->toDateString(),
            'time'  => $startsAt->format('H:i'),
        ];
    }
}

Plus besoin de faire Carbon::parse($request->input('starts_at')), ni de gérer les erreurs de format ici : la validation s’en charge.

Entiers, booléens, chaînes : API fluide et sûre

Pour les entiers :

<?php

declare(strict_types=1);

$maxAttendees = $request->integer('max_attendees'); // int

Pour les booléens :

<?php

declare(strict_types=1);

$isPublic = $request->boolean('is_public'); // bool

Pour les chaînes, Laravel renvoie un Stringable, pratique pour les transformations :

<?php

declare(strict_types=1);

$slug = $request
    ->string('title')
    ->trim()
    ->lower()
    ->slug()
    ->toString();

Vous évitez les erreurs du style $request->input('max_attendees') + 1 sur une string ou un null.

Validation + Request typée : un duo puissant

Un exemple plus complet : profil d’utilisateur

Imaginons un formulaire de profil avec :

  • Un pays (enum)

  • Une newsletter (booléen)

  • Une taille en cm (int)

  • Un pseudo (string avec normalisation)

<?php

declare(strict_types=1);

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
use App\Enums\Country;

final class UpdateProfileRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'nickname'    => ['required', 'string', 'max:50'],
            'country'     => ['required', new Enum(Country::class)],
            'height_cm'   => ['nullable', 'integer', 'min:50', 'max:260'],
            'newsletter'  => ['required', 'boolean'],
        ];
    }

    public function attributes(): array
    {
        return [
            'nickname'   => __('profile.fields.nickname'),
            'country'    => __('profile.fields.country'),
            'height_cm'  => __('profile.fields.height_cm'),
            'newsletter' => __('profile.fields.newsletter'),
        ];
    }
}

Et dans le contrôleur :

<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Requests\UpdateProfileRequest;
use App\Enums\Country;
use Illuminate\Http\RedirectResponse;

final class ProfileController
{
    public function update(UpdateProfileRequest $request): RedirectResponse
    {
        $user = $request->user();

        $country = $request->enum(
            key: 'country',
            enum: Country::class
        ); // ?Country

        $user->update([
            'nickname'   => $request
                ->string('nickname')
                ->trim()
                ->toString(),
            'country'    => $country?->value,
            'height_cm'  => $request->integer('height_cm'),
            'newsletter' => $request->boolean('newsletter'),
        ]);

        return redirect()
            ->route('profile.show')
            ->with('status', __('profile.updated'));
    }
}

La cohérence entre rules(), attributes() et la récupération typée fait que :

  • Vos contrôleurs restent fins et lisibles

  • Vos messages d’erreur sont clairs pour l’utilisateur

  • Votre IDE peut déduire les types de la plupart des champs

Personnaliser encore plus avec les règles et attributes

Les règles complexes restent compatibles avec cette approche typée. Par exemple, pour une réservation d’hôtel :

  • check_in avant check_out

  • nombre de nuits calculé depuis deux dates typées

<?php

declare(strict_types=1);

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

final class BookRoomRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'check_in'   => ['required', 'date', 'after_or_equal:today'],
            'check_out'  => ['required', 'date', 'after:check_in'],
            'guests'     => ['required', 'integer', 'min:1', 'max:4'],
        ];
    }

    public function attributes(): array
    {
        return [
            'check_in'  => __('booking.fields.check_in'),
            'check_out' => __('booking.fields.check_out'),
            'guests'    => __('booking.fields.guests'),
        ];
    }
}

Et dans le contrôleur :

<?php

declare(strict_types=1);

use App\Http\Requests\BookRoomRequest;

final class BookingController
{
    public function store(BookRoomRequest $request): array
    {
        $checkIn  = $request->date('check_in');
        $checkOut = $request->date('check_out');

        $nights = $checkIn->diffInDays($checkOut);

        return [
            'nights' => $nights,
            'guests' => $request->integer('guests'),
        ];
    }
}

Vous profitez des dates typées pour calculer des durées sans logique de parsing répétée.

À retenir

  • Utilisez les Form Requests pour centraliser validation et noms d’attributs.

  • Typez vos champs côté contrôleur avec enum(), date(), integer(), boolean(), string().

  • Enum + règles de validation remplacent avantageusement les strings “magiques”.

  • attributes() rend vos messages d’erreur plus clairs et plus faciles à traduire.

  • Moins de conversions manuelles, moins d’if défensifs, un code plus expressif et sécurisé.

En combinant validation avancée et récupération typée des champs de formulaire, vous exploitez réellement la puissance de Laravel et de PHP 8.2+ dans vos applications.

Contact

Travaillons ensemble

Vous avez un projet en tête ? Remplissez le formulaire et je vous répondrai dans les plus brefs délais.