Qu’est-ce qu’une règle de validation personnalisée
Laravel dispose d’un large éventail de règles de validation intégrées, mais parfois vous avez besoin d’une logique de validation spécifique à votre application. Les règles personnalisées vous permettent de définir une logique de validation réutilisable sous forme de classes ou de closures, utilisables comme les règles standards.Il existe principalement deux façons de définir des règles personnalisées :
Objet règle — très réutilisable et facile à tester
Closure — adapté aux règles simples utilisées une seule fois
Pour reporter plusieurs erreurs sur un même champ, appelez $fail plusieurs fois.
public function validate(string $attribute, mixed $value, Closure $fail): void{ if (! is_string($value)) { $fail('The :attribute must be a string.'); return; } if (strlen($value) < 8) { $fail('The :attribute must be at least 8 characters.'); } if (! preg_match('/[A-Z]/', $value)) { $fail('The :attribute must contain at least one uppercase letter.'); }}
Les règles simples utilisées une seule fois dans l’application peuvent être définies via une closure, sans créer de classe.
use Illuminate\Support\Facades\Validator;use Closure;$validator = Validator::make($request->all(), [ 'title' => [ 'required', 'max:255', function (string $attribute, mixed $value, Closure $fail) { if ($value === 'foo') { $fail("The {$attribute} is invalid."); } }, ],]);
Les règles closure sont pratiques car définies inline, mais elles sont difficiles à réutiliser et à tester de façon isolée. Pour une logique utilisée à plusieurs endroits, préférez extraire dans un objet règle.
Règles implicites (exécutées même si la valeur est vide)
Par défaut, si un champ est vide ou absent, les règles personnalisées ne sont pas exécutées. Pour exécuter la règle même sur les valeurs vides, générez la classe avec l’option --implicit.
php artisan make:rule Uppercase --implicit
La classe générée implémente l’interface ImplicitRule. Cette interface elle-même n’ajoute pas de méthode ; elle sert de signal à Laravel.
<?phpnamespace App\Rules;use Closure;use Illuminate\Contracts\Validation\ImplicitRule;use Illuminate\Contracts\Validation\ValidationRule;class RequiredIfJapanese implements ValidationRule, ImplicitRule{ public function validate(string $attribute, mixed $value, Closure $fail): void { if (empty($value)) { $fail('The :attribute is required.'); return; } if (! preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー]+$/u', $value)) { $fail('The :attribute must contain only Japanese characters.'); } }}
ImplicitRule indique simplement à Laravel que « l’attribut est requis ». C’est à l’implémentation de validate de faire réellement échouer la validation lorsque la valeur est vide.
DataAwareRule — accéder à toutes les données du formulaire
Pour valider en fonction des valeurs d’autres champs, implémentez l’interface DataAwareRule. La méthode setData est appelée automatiquement avant le démarrage de la validation.
<?phpnamespace App\Rules;use Closure;use Illuminate\Contracts\Validation\DataAwareRule;use Illuminate\Contracts\Validation\ValidationRule;use Illuminate\Support\Facades\DB;class UniqueForTenant implements DataAwareRule, ValidationRule{ /** * Toutes les données à valider * * @var array<string, mixed> */ protected array $data = []; public function __construct( protected string $table, protected string $column = 'value', ) {} /** * Définit les données de validation * * @param array<string, mixed> $data */ public function setData(array $data): static { $this->data = $data; return $this; } public function validate(string $attribute, mixed $value, Closure $fail): void { $tenantId = $this->data['tenant_id'] ?? null; $exists = DB::table($this->table) ->where('tenant_id', $tenantId) ->where($this->column, $value) ->exists(); if ($exists) { $fail('The :attribute has already been taken for this tenant.'); } }}
ValidatorAwareRule — accéder à l’instance du validateur
Pour accéder à toutes les informations du validateur (règles échouées, messages personnalisés, etc.), implémentez l’interface ValidatorAwareRule.
<?phpnamespace App\Rules;use Closure;use Illuminate\Contracts\Validation\ValidationRule;use Illuminate\Contracts\Validation\ValidatorAwareRule;use Illuminate\Validation\Validator;class ConditionalFormat implements ValidationRule, ValidatorAwareRule{ protected Validator $validator; public function setValidator(Validator $validator): static { $this->validator = $validator; return $this; } public function validate(string $attribute, mixed $value, Closure $fail): void { // Sauter si un autre champ a déjà échoué à la validation if ($this->validator->errors()->has('type')) { return; } $type = $this->validator->getData()['type'] ?? null; if ($type === 'phone' && ! preg_match('/^\+?[0-9\-\s]+$/', $value)) { $fail('The :attribute must be a valid phone number.'); } if ($type === 'email' && ! filter_var($value, FILTER_VALIDATE_EMAIL)) { $fail('The :attribute must be a valid email address.'); } }}
Une règle validant le format d’un numéro de téléphone japonais.
php artisan make:rule JapanesePhone
<?phpnamespace App\Rules;use Closure;use Illuminate\Contracts\Validation\ValidationRule;class JapanesePhone implements ValidationRule{ public function validate(string $attribute, mixed $value, Closure $fail): void { // Prend en charge avec/sans tirets, et le format international $normalized = preg_replace('/[\s\-\(\)]/', '', $value); $patterns = [ '/^0\d{9,10}$/', // Numéros fixes et mobiles courants '/^\+81\d{9,10}$/', // Format international ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $normalized)) { return; } } $fail('Le champ :attribute doit être au format d\'un numéro de téléphone valide.'); }}
Validator::extend() permet d’utiliser une règle personnalisée sous forme de chaîne ('rule_name'). Enregistrez-la dans la méthode boot() de AppServiceProvider.
<?phpnamespace App\Providers;use Illuminate\Support\Facades\Validator;use Illuminate\Support\ServiceProvider;use Illuminate\Validation\Validator as ValidatorInstance;class AppServiceProvider extends ServiceProvider{ public function boot(): void { Validator::extend('japanese_only', function (string $attribute, mixed $value, array $parameters, ValidatorInstance $validator): bool { return preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー\s]+$/u', $value) === 1; }); Validator::replacer('japanese_only', function (string $message, string $attribute): string { return str_replace(':attribute', $attribute, 'Le champ :attribute doit être en japonais.'); }); }}
Les règles enregistrées avec Validator::extend() peuvent être spécifiées comme chaîne.
Validator::extend() est une méthode d’enregistrement plus ancienne que les objets règle. Pour les nouveaux développements, il est recommandé d’utiliser des objets règle implémentant l’interface ValidationRule.
Voyons comment Illuminate\Validation\Validator invoque les règles personnalisées.Au sein du validateur, validateAttribute() traite chaque champ. Si la règle implémente l’interface ValidationRule, la méthode validateUsingCustomRule() est appelée.
// Version simplifiée de Illuminate\Validation\Validator::validateUsingCustomRule()protected function validateUsingCustomRule($attribute, $value, $rule){ // Si DataAwareRule, injecter toutes les données if ($rule instanceof DataAwareRule) { $rule->setData($this->getData()); } // Si ValidatorAwareRule, injecter le validateur lui-même if ($rule instanceof ValidatorAwareRule) { $rule->setValidator($this); } // Appeler validate() $rule->validate($attribute, $value, function ($message, $translate = false) use ($attribute, $rule) { // Closure $fail — ajoute le message d'erreur $this->errors()->add($attribute, $this->makeReplacements( $message, $attribute, get_class($rule), [], // Remplacements de placeholders supplémentaires (ex : ['min' => '8']) )); });}
Le traitement de ImplicitRule est déterminé par la méthode isImplicit(), qui exécute la vérification même sur les valeurs vides.
// Détermine si la règle doit être exécutée sur les valeurs videsprotected function isImplicit($rule): bool{ return $rule instanceof ImplicitRule || in_array($rule, $this->implicitRules);}
Vous pouvez implémenter DataAwareRule et ValidatorAwareRule simultanément. Dans une classe qui implémente les deux interfaces, les deux injections sont effectuées.