Validation

Validation

Niang\Core\Validation\Validator : 29 règles, vérifiées une à une contre Validator::applyRule() — pas contre une liste retapée de mémoire.

Valider depuis un contrôleur

Controller::validate() encapsule Validator::make(...)->validate() :

php
public function store(Request $request): Response
{
    $data = $this->validate($request, [
        'name' => 'required|string|min:2',
        'email' => 'required|email',
        'message' => 'required|string|min:10',
    ]);

    // $data ne contient que les champs déclarés dans les règles.
}

Les règles d'un champ s'écrivent en chaîne séparée par |, ou en tableau (['required', 'string', 'min:2']) — les deux formes sont équivalentes.

FormRequest : valider par injection

Une classe qui étend Niang\Core\Validation\FormRequest et déclare rules() se valide toute seule dès qu'elle est type-hintée dans un contrôleur — le Container la construit à partir de la requête courante, vérifie authorize() puis valide rules() avant même d'entrer dans la méthode (voir injection par réflexion) :

app/Requests/ContactRequest.php

namespace App\Requests;

use Niang\Core\Validation\FormRequest;

class ContactRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => 'required|string|min:2',
            'email' => 'required|email',
            'message' => 'required|string|min:10',
        ];
    }
}
php
public function store(ContactRequest $request): Response
{
    $data = $request->validated(); // déjà validé, garanti au moment où on arrive ici
}

authorize() retourne true par défaut ; la surcharger et retourner false lève une AuthorizationException (403) avant même la validation — utile pour une FormRequest qui doit vérifier une permission en plus de la forme des données.

Ce qui se passe quand ça échoue

Une règle qui échoue lève une ValidationException, interceptée une seule fois par Handler::render() (voir cycle de vie d'une requête) — jamais à gérer manuellement dans le contrôleur :

  • Si la requête attend du JSON (Accept: application/json) : réponse 422 avec {"message": "...", "errors": {...}}.
  • Sinon : redirection vers le Referer, avec les erreurs et la saisie précédente flashées en session — récupérables avec errors('champ') et old('champ') dans la vue.
php
<input type="email" name="email" value="<?= e(old('email')) ?>">
<?= component('components/field-errors', ['field' => 'email']) ?>

Une seule erreur est retenue par champ (la première règle qui échoue), pour rester lisible.

Toutes les règles

RègleVérifieExemple
requiredPrésent et non vide (chaîne non blanche, tableau non vide).'name' => 'required'
required_if:champ,valeurRequis seulement si champ vaut valeur.'phone' => 'required_if:contact_method,phone'
required_with:champRequis seulement si champ est rempli.'shipping_city' => 'required_with:shipping_address'
required_without:champRequis seulement si champ est vide.'email' => 'required_without:phone'
stringis_string().'name' => 'string'
numericis_numeric().'price' => 'numeric'
integerFILTER_VALIDATE_INT.'quantity' => 'integer'
booleanUn booléen, ou 0/1/'0'/'1'/'true'/'false'.'accepted' => 'boolean'
arrayis_array().'tags' => 'array'
emailFILTER_VALIDATE_EMAIL.'email' => 'required|email'
urlFILTER_VALIDATE_URL.'website' => 'url'
datestrtotime() réussit.'birthdate' => 'date'
date_format:formatRespecte exactement un format DateTime::createFromFormat().'day' => 'date_format:Y-m-d'
min:n≥ n (longueur de chaîne, valeur si numérique, taille en Ko pour un fichier).'password' => 'min:8'
max:n≤ n (mêmes unités que min).'bio' => 'max:500'
between:min,maxCompris entre les deux.'age' => 'between:18,99'
in:a,b,cUne des valeurs listées.'role' => 'in:admin,editor,viewer'
not_in:a,b,cAucune des valeurs listées.'username' => 'not_in:admin,root'
same:champIdentique à un autre champ.'password_confirmation' => 'same:password'
different:champDifférent d'un autre champ.'new_password' => 'different:current_password'
regex:motifCorrespond au motif PCRE (délimiteurs inclus).'code' => 'regex:/^[A-Z]{3}-\d{4}$/'
confirmedUn champ x_confirmation existe et lui est identique.'password' => 'required|confirmed'
unique:table,colonne[,id,idColonne]Aucune ligne existante avec cette valeur (le 3ᵉ paramètre ignore la ligne courante lors d'une modification).'email' => "unique:users,email,{$id},id"
exists:table,colonneUne ligne existe avec cette valeur (colonne par défaut id).'category_id' => 'exists:categories,id'
fileUn fichier envoyé, arrivé sans erreur (voir Upload de fichiers).'cv' => 'required|file'
imageLe contenu réel est une image JPEG, PNG, GIF, WebP ou AVIF (SVG exclu : il peut contenir du JavaScript).'avatar' => 'image|max:2048'
mimes:ext,...L'extension déduite du contenu (jamais du nom envoyé) est dans la liste.'cv' => 'mimes:pdf,docx'
mimetypes:type,...Le type MIME réel est dans la liste ; accepte un joker (image/*).'media' => 'mimetypes:image/*,video/mp4'
dimensions:...Largeur/hauteur d'une image : min_width, max_width, min_height, max_height, width, height.'banniere' => 'dimensions:min_width=1200,max_height=600'

nullable n'est pas dans ce tableau : ce n'est pas une règle mais un modificateur (géré par runRules(), avant la boucle) — placé avant les autres, il fait sauter toutes les règles suivantes si le champ est vide, plutôt que de les faire échouer.

Messages et attributs personnalisés

php
Validator::make($data, [
    'email' => 'required|email',
], [
    'email.required' => 'Une adresse email est nécessaire pour vous recontacter.',
], [
    'email' => 'Adresse email',
])->validate();

Le 3ᵉ argument (messages) accepte une clé 'champ.règle' (prioritaire) ou juste 'règle' (s'applique à tous les champs). Le 4ᵉ (attributes) remplace le nom du champ dans les messages par défaut, ex. « Le champ Adresse email est requis. » plutôt que « Le champ email est requis. ».

Les messages par défaut viennent de lang/<langue>/validation.php, livré en français et en anglais (APP_LOCALE) : modifiez-y un message pour tout le projet, ou donnez un libellé à un champ dans sa section attributes — voir Langues.

Valider un tableau (règles items.*)

Une clé contenant .* applique ses règles à chaque élément d'un tableau :

php
Validator::make($data, [
    'items' => 'required|array',
    'items.*.name' => 'required|string',
    'items.*.quantity' => 'required|integer|min:1',
])->validate();

Chaque erreur est indexée par position : items.0.name, items.1.quantity, etc. — récupérables individuellement avec errors('items.0.name').

⏱ 8.97 ms 🗄 0 requête(s) SQL 🧠 4.00 MB ↩ 200