> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Regole di validazione personalizzate

> Come estendere la validazione di Laravel con oggetti Rule e closure, con un'analisi anche dell'implementazione interna del framework.

## Cosa sono le regole di validazione personalizzate

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

## Oggetti Rule

### Generare la classe della regola

Con il comando Artisan `make:rule` generi una nuova classe di regola. La classe viene creata in `app/Rules`.

```bash theme={null}
php artisan make:rule Uppercase
```

### Implementare l'interfaccia ValidationRule

Nella classe generata implementi il metodo `validate`. Il metodo, in caso di fallimento, invoca la closure `$fail`.

```php theme={null}
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class Uppercase implements ValidationRule
{
    /**
     * Esegue la regola di validazione
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}
```

Nel messaggio passato a `$fail` puoi usare il placeholder `:attribute`. Laravel lo sostituisce con il nome del campo.

### Applicare l'oggetto Rule

Passi un'istanza dell'oggetto Rule nell'array di validazione.

```php theme={null}
use App\Rules\Uppercase;

$request->validate([
    'name' => ['required', 'string', new Uppercase],
]);
```

Puoi usarla allo stesso modo nel metodo `rules()` di una form request.

```php theme={null}
public function rules(): array
{
    return [
        'name' => ['required', 'string', new Uppercase],
    ];
}
```

### Messaggi di errore tramite chiavi di traduzione

Al posto di hardcodare il messaggio, puoi usare una chiave di traduzione.

```php theme={null}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
    if (strtoupper($value) !== $value) {
        $fail('validation.uppercase')->translate();
    }
}
```

Aggiungi il messaggio al file di traduzione `lang/it/validation.php`.

```php theme={null}
return [
    'uppercase' => 'Il campo :attribute deve essere in maiuscolo.',
    // ...
];
```

### Aggiungere più messaggi di errore

Per riportare più errori sullo stesso campo, chiami `$fail` più volte.

```php theme={null}
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.');
    }
}
```

## Regole basate su closure

Le regole semplici usate una sola volta nell'applicazione possono essere definite come closure, senza creare una classe.

```php theme={null}
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`.

```bash theme={null}
php artisan make:rule Uppercase --implicit
```

La classe generata implementa l'interfaccia `ImplicitRule`. Questa interfaccia non aggiunge metodi: funziona come segnale per Laravel.

```php theme={null}
<?php

namespace 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.');
        }
    }
}
```

<Warning>
  `ImplicitRule` segnala a Laravel che "l'attributo è obbligatorio". Se far fallire davvero la validazione con valore vuoto o meno dipende dall'implementazione di `validate`.
</Warning>

## Accesso ai dati

### DataAwareRule — accedere a tutti i dati del form

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.

```php theme={null}
<?php

namespace 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.');
        }
    }
}
```

Esempio d'uso:

```php theme={null}
$request->validate([
    'tenant_id' => 'required|integer',
    'email' => ['required', 'email', new UniqueForTenant('users', 'email')],
]);
```

### ValidatorAwareRule — accedere all'istanza del Validator

Per accedere a tutte le informazioni del Validator (regole già fallite, messaggi personalizzati, ecc.) implementa l'interfaccia `ValidatorAwareRule`.

```php theme={null}
<?php

namespace 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.');
        }
    }
}
```

## Casi d'uso pratici

### Verifica di caratteri giapponesi

Regola che verifica il tipo di caratteri (mezza/piena larghezza).

```bash theme={null}
php artisan make:rule JapaneseOnly
```

```php theme={null}
<?php

namespace 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).');
        }
    }
}
```

```php theme={null}
$request->validate([
    'name_kana' => ['required', 'string', new JapaneseOnly],
]);
```

### Validazione del formato di un numero di telefono

Regola per verificare il formato dei numeri di telefono giapponesi.

```bash theme={null}
php artisan make:rule JapanesePhone
```

```php theme={null}
<?php

namespace 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.');
    }
}
```

### Vincolo di unicità per tenant

Vincolo di unicità nell'ambito del tenant, tipico delle applicazioni multi-tenant.

<Steps>
  <Step title="Creare la classe della regola">
    ```bash theme={null}
    php artisan make:rule TenantUnique
    ```

    ```php theme={null}
    <?php

    namespace App\Rules;

    use Closure;
    use Illuminate\Contracts\Validation\DataAwareRule;
    use Illuminate\Contracts\Validation\ValidationRule;
    use Illuminate\Support\Facades\DB;

    class TenantUnique implements DataAwareRule, ValidationRule
    {
        protected array $data = [];

        public function __construct(
            protected string $table,
            protected string $column,
            protected ?int $ignoreId = null,
        ) {}

        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;

            $query = DB::table($this->table)
                ->where('tenant_id', $tenantId)
                ->where($this->column, $value);

            if ($this->ignoreId !== null) {
                $query->where('id', '!=', $this->ignoreId);
            }

            if ($query->exists()) {
                $fail('Il campo :attribute è già in uso.');
            }
        }
    }
    ```
  </Step>

  <Step title="Usarla in una form request">
    ```php theme={null}
    <?php

    namespace App\Http\Requests;

    use App\Rules\TenantUnique;
    use Illuminate\Foundation\Http\FormRequest;

    class CreateProjectRequest extends FormRequest
    {
        public function rules(): array
        {
            return [
                'tenant_id' => 'required|integer|exists:tenants,id',
                'name' => [
                    'required',
                    'string',
                    'max:100',
                    new TenantUnique('projects', 'name'),
                ],
            ];
        }

        public function authorize(): bool
        {
            return true;
        }
    }
    ```
  </Step>

  <Step title="In update: escludere l'ID">
    Per aggiornare un record esistente, escludi il proprio ID dal controllo di duplicazione.

    ```php theme={null}
    class UpdateProjectRequest extends FormRequest
    {
        public function rules(): array
        {
            $projectId = $this->route('project');

            return [
                'tenant_id' => 'required|integer|exists:tenants,id',
                'name' => [
                    'required',
                    'string',
                    'max:100',
                    new TenantUnique('projects', 'name', ignoreId: $projectId),
                ],
            ];
        }
    }
    ```
  </Step>
</Steps>

## Registrazione delle regole nel service provider

### Aggiungere regole con Validator::extend()

Con `Validator::extend()` puoi usare la regola personalizzata in forma di stringa (`'rule_name'`). La registri nel metodo `boot()` di `AppServiceProvider`.

```php theme={null}
<?php

namespace 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, 'Il campo :attribute deve essere in giapponese.');
        });
    }
}
```

Le regole registrate con `Validator::extend()` si indicano come stringa.

```php theme={null}
$request->validate([
    'name_kana' => 'required|japanese_only',
]);
```

<Warning>
  `Validator::extend()` è la modalità di registrazione più vecchia. Per nuovi sviluppi consigliamo l'uso di oggetti Rule che implementano `ValidationRule`.
</Warning>

### Aggiungere come metodo statico sulla classe Rule

Aggiungendo una macro alla facade `Rule` puoi offrire un'API fluent come `Rule::myRule()`.

```php theme={null}
use Illuminate\Validation\Rule;

Rule::macro('tenantUnique', function (string $table, string $column, ?int $ignoreId = null) {
    return new \App\Rules\TenantUnique($table, $column, $ignoreId);
});
```

```php theme={null}
// esempio d'uso
$request->validate([
    'name' => ['required', Rule::tenantUnique('projects', 'name')],
]);
```

## Dettagli dell'implementazione interna

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()`.

```php theme={null}
// 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.

```php theme={null}
// decide se eseguire la regola quando il valore è vuoto
protected function isImplicit($rule): bool
{
    return $rule instanceof ImplicitRule
        || in_array($rule, $this->implicitRules);
}
```

<Tip>
  Puoi implementare contemporaneamente `DataAwareRule` e `ValidatorAwareRule`. In una classe che implementa entrambe le interfacce vengono eseguite entrambe le iniezioni.
</Tip>

## Pagine correlate

<Card title="Validazione (introduzione)" icon="shield-check" href="/it/validation">
  Rivedi le modalità di validazione standard in controller e form request.
</Card>


## Related topics

- [GeneratorCommand — implementare comandi `make:` personalizzati](/it/advanced/generator-command.md)
- [Aggiornamento da Laravel 9 a 10](/it/blog/upgrade-9-to-10.md)
- [Validazione](/it/validation.md)
- [Precognition](/it/precognition.md)
- [Laravel Prompts](/it/prompts.md)
