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

# Règles de validation personnalisées

> Étendez la validation Laravel avec des objets règle ou des closures, jusqu'au niveau de l'implémentation interne du framework.

## 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

## Objet règle

### Générer une classe de règle

La commande Artisan `make:rule` génère une nouvelle classe de règle. Elle est placée dans le répertoire `app/Rules`.

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

### Implémenter l'interface ValidationRule

Implémentez la méthode `validate` dans la classe générée. Cette méthode appelle la closure `$fail` en cas d'échec de la validation.

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

namespace App\Rules;

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

class Uppercase implements ValidationRule
{
    /**
     * Exécute la règle de validation
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}
```

La chaîne passée à la closure `$fail` peut contenir le placeholder `:attribute`, que Laravel remplace par le nom du champ.

### Appliquer l'objet règle

Passez une instance de l'objet règle dans le tableau de validation.

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

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

La méthode `rules()` d'un form request s'utilise de la même façon.

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

### Messages d'erreur via des clés de traduction

Plutôt que de coder en dur le message d'erreur, vous pouvez utiliser une clé de traduction.

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

Ajoutez le message dans le fichier de traduction `lang/fr/validation.php`.

```php theme={null}
return [
    'uppercase' => 'Le champ :attribute doit être en majuscules.',
    // ...
];
```

### Ajouter plusieurs messages d'erreur

Pour reporter plusieurs erreurs sur un même champ, appelez `$fail` plusieurs fois.

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

## Règles via closure

Les règles simples utilisées une seule fois dans l'application peuvent être définies via une closure, sans créer de 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.");
            }
        },
    ],
]);
```

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

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

```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` 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.
</Warning>

## Accès aux données

### 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.

```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
{
    /**
     * 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.');
        }
    }
}
```

Exemple d'utilisation :

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

### 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`.

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

## Cas d'usage pratiques

### Vérification de caractères spécifiques

Une règle qui valide un jeu de caractères précis.

```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
    {
        // Autorise uniquement hiragana, katakana, kanji et le tiret long
        if (! preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー\s]+$/u', $value)) {
            $fail('Le champ :attribute doit contenir uniquement des caractères japonais (hiragana, katakana, kanji).');
        }
    }
}
```

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

### Vérification d'un format de numéro de téléphone

Une règle validant le format d'un numéro de téléphone japonais.

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

### Contrainte d'unicité avec tenant ID

Une contrainte d'unicité au sein du scope d'un tenant, courante dans les applications multi-tenant.

<Steps>
  <Step title="Créer la classe de règle">
    ```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('Le champ :attribute est déjà utilisé.');
            }
        }
    }
    ```
  </Step>

  <Step title="Utiliser dans une 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="Exclure l'ID lors de la mise à jour">
    Lors de la mise à jour d'un enregistrement existant, excluez son propre ID de la vérification de doublon.

    ```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>

## Enregistrement de règles dans un service provider

### Ajouter une règle avec Validator::extend()

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

```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, 'Le champ :attribute doit être en japonais.');
        });
    }
}
```

Les règles enregistrées avec `Validator::extend()` peuvent être spécifiées comme chaîne.

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

<Warning>
  `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`.
</Warning>

### Ajouter comme méthode statique de la classe Rule

En ajoutant une macro à la façade `Rule`, vous pouvez fournir une API fluide comme `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}
// Exemple d'utilisation
$request->validate([
    'name' => ['required', Rule::tenantUnique('projects', 'name')],
]);
```

## Détails de l'implémentation interne

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.

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

```php theme={null}
// Détermine si la règle doit être exécutée sur les valeurs vides
protected function isImplicit($rule): bool
{
    return $rule instanceof ImplicitRule
        || in_array($rule, $this->implicitRules);
}
```

<Tip>
  Vous pouvez implémenter `DataAwareRule` et `ValidatorAwareRule` simultanément. Dans une classe qui implémente les deux interfaces, les deux injections sont effectuées.
</Tip>

## Pages associées

<Card title="Validation (introduction)" icon="shield-check" href="/fr/validation">
  Consultez la méthode standard de validation dans les contrôleurs et les form requests.
</Card>


## Related topics

- [Mise à niveau de Laravel 9 vers 10](/fr/blog/upgrade-9-to-10.md)
- [Validation](/fr/validation.md)
- [GeneratorCommand — implémenter des commandes make: personnalisées](/fr/advanced/generator-command.md)
- [Precognition](/fr/precognition.md)
- [Créer un agent personnalisé pour Boost](/fr/advanced/boost-custom-agent.md)
