Laravel offre molte regole di validazione integrate, ma a volte serve una logica di validazione specifica dell’applicazione. Le regole personalizzate permettono di definire logica riutilizzabile come classe o closure, e usarla al pari delle regole standard.Ci sono due modi principali per definire una regola personalizzata.
Oggetti Rule — molto riutilizzabili e facili da testare
Closure — indicate per regole semplici usate una sola volta
Per riportare più errori sullo stesso campo, chiami $fail più volte.
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.'); }}
Le regole semplici usate una sola volta nell’applicazione possono essere definite come closure, senza creare una 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."); } }, ],]);
Le regole con closure sono comode perché inline, ma sono difficili da riusare e testare in isolamento. Per logica usata in più punti conviene estrarla in un oggetto Rule.
Regole implicite (eseguite anche con valore vuoto)
Di default, se il campo è vuoto o assente, le regole personalizzate non vengono eseguite. Se vuoi eseguirle anche sui valori vuoti, genera la classe con l’opzione --implicit.
php artisan make:rule Uppercase --implicit
La classe generata implementa l’interfaccia ImplicitRule. Questa interfaccia non aggiunge metodi: funziona come segnale per 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 segnala a Laravel che “l’attributo è obbligatorio”. Se far fallire davvero la validazione con valore vuoto o meno dipende dall’implementazione di validate.
Se vuoi validare in base al valore di altri campi, implementa l’interfaccia DataAwareRule. Il metodo setData viene chiamato automaticamente prima dell’esecuzione della regola.
<?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{ /** * Tutti i dati sotto validazione * * @var array<string, mixed> */ protected array $data = []; public function __construct( protected string $table, protected string $column = 'value', ) {} /** * Imposta i dati di validazione * * @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 — accedere all’istanza del Validator
Per accedere a tutte le informazioni del Validator (regole già fallite, messaggi personalizzati, ecc.) implementa l’interfaccia 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 { // salta se un altro campo ha già fallito la validazione 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.'); } }}
Regola che verifica il tipo di caratteri (mezza/piena larghezza).
php artisan make:rule JapaneseOnly
<?phpnamespace App\Rules;use Closure;use Illuminate\Contracts\Validation\ValidationRule;class JapaneseOnly implements ValidationRule{ public function validate(string $attribute, mixed $value, Closure $fail): void { // consente solo hiragana, katakana, kanji e prolungamento if (! preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー\s]+$/u', $value)) { $fail('Il campo :attribute deve contenere solo caratteri giapponesi (hiragana, katakana, kanji).'); } }}
$request->validate([ 'name_kana' => ['required', 'string', new JapaneseOnly],]);
Regola per verificare il formato dei numeri di telefono giapponesi.
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 { // accetta con o senza trattini, compreso il formato internazionale $normalized = preg_replace('/[\s\-\(\)]/', '', $value); $patterns = [ '/^0\d{9,10}$/', // numeri fissi o cellulari generici '/^\+81\d{9,10}$/', // formato internazionale ]; foreach ($patterns as $pattern) { if (preg_match($pattern, $normalized)) { return; } } $fail('Il campo :attribute deve essere un numero di telefono valido.'); }}
Vediamo come Illuminate\Validation\Validator invoca le regole personalizzate.Nel Validator, validateAttribute() elabora ogni campo. Se la regola implementa ValidationRule, viene invocato il metodo validateUsingCustomRule().
// versione semplificata di Illuminate\Validation\Validator::validateUsingCustomRule()protected function validateUsingCustomRule($attribute, $value, $rule){ // per DataAwareRule inietta tutti i dati if ($rule instanceof DataAwareRule) { $rule->setData($this->getData()); } // per ValidatorAwareRule inietta il Validator stesso if ($rule instanceof ValidatorAwareRule) { $rule->setValidator($this); } // chiama validate() $rule->validate($attribute, $value, function ($message, $translate = false) use ($attribute, $rule) { // closure $fail — aggiunge un messaggio d'errore $this->errors()->add($attribute, $this->makeReplacements( $message, $attribute, get_class($rule), [], // sostituzioni di placeholder aggiuntivi (es: ['min' => '8']) )); });}
La gestione di ImplicitRule avviene tramite il metodo isImplicit(): se il valore è vuoto, la regola viene comunque eseguita.
// decide se eseguire la regola quando il valore è vuotoprotected function isImplicit($rule): bool{ return $rule instanceof ImplicitRule || in_array($rule, $this->implicitRules);}
Puoi implementare contemporaneamente DataAwareRule e ValidatorAwareRule. In una classe che implementa entrambe le interfacce vengono eseguite entrambe le iniezioni.