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

# Enregistrement de règles de validation sous forme de chaîne pour les packages

> Analysez Factory::extend de Laravel 13 pour comprendre le contrat d'appel des règles sous forme de chaîne, les messages d'erreur et leur traduction, la différence entre règles Implicit et Dependent, ainsi que la durée de vie des Validator déjà créés et les tests.

Pour fournir depuis un package distribué une règle sous forme de chaîne comme `acme_tracking:AC`, vous enregistrez la logique de validation dans la `Factory` de validation. Elle peut alors être utilisée depuis les form requests de l'application consommatrice avec la même syntaxe que les règles intégrées.

Cette page a été vérifiée sur l'implémentation officielle de Laravel Framework `v13.40.0`. Elle complète les exemples de base de [Règles de validation personnalisées](https://kawax.biz/fr/advanced/custom-validation-rules.md) et traite de l'enregistrement, de la traduction et de la durée de vie de l'état lorsque vous maintenez ces règles comme API publique d'un package.

## Choisir entre ceci et les objets de règle

Les objets `ValidationRule` présentés dans la documentation officielle peuvent aussi être utilisés dans un package. Si la possibilité de spécifier la règle sous forme de chaîne n'est pas en soi une exigence, envisagez un objet capable de recevoir des arguments typés via son constructeur.

| Approche | Cas d'usage adapté | Signalement de l'échec |
| - | - | - |
| Objet de règle comme `new CustomRule(...)` | Distribuer sous forme de classe une configuration typée ou une validation complexe | Appeler le `$fail` passé à `validate()` |
| Règle enregistrée sous forme de chaîne comme `acme_tracking:AC` | Fournir une API utilisable aussi dans des définitions de règles basées sur des chaînes | Le callback enregistré retourne `false` |

Même si la méthode porte le même nom `validate()`, le contrat d'appel est différent. Si vous passez directement une classe implémentant `ValidationRule` comme chaîne de classe à `Factory::extend()`, le type du troisième argument et la valeur de retour ne correspondent pas.

## Séparer enregistrement, création et validation

La méthode `extend()` de la façade `Validator` est un appel vers `Illuminate\Validation\Factory`. Il s'agit d'un objet distinct de l'instance `Illuminate\Validation\Validator` qui valide les entrées.

```mermaid theme={null}
flowchart TD
    A["boot du package"] --> B["Enregistrer la règle et le replacer<br>dans la Factory"]
    B --> C["Créer un nouveau Validator<br>avec Factory::make"]
    C --> D["Copier les extensions enregistrées<br>et les messages fallback"]
    D --> E["Analyser les règles de chaque attribut<br>et vérifier les conditions d'exécution"]
    E --> F["Valider avec la classe ou la closure"]
    F --> G{"La valeur de retour est true ?"}
    G -->|Oui| H["Cette règle réussit"]
    G -->|Non| I["Sélectionner le message et<br>remplacer les placeholders"]
```

Le diagramme montre le chemin suivi lorsqu'une règle sous forme de chaîne est exécutée. Les cas où elle est ignorée, par exemple en l'absence de saisie ou avec `nullable`, sont décrits plus loin.

La Factory est enregistrée comme singleton sous la clé `validator`, mais le Validator retourné par l'implémentation standard de `make()` est créé à neuf à chaque appel. **Un enregistrement ultérieur dans la Factory ne met pas à jour le tableau d'extensions des Validator déjà créés.**

## Enregistrer une règle propre au package

À titre d'exemple, nous validons un code de suivi fictif composé d'un préfixe en majuscules, d'un tiret et de 8 chiffres. Il s'agit d'un format d'exemple comme `AC-00000001`, et non de la spécification de numéros d'un transporteur réel.

Nous supposons que l'espace de noms du package `Acme\Courier\` correspond à `src/` via le PSR-4 de Composer, et qu'un Service Provider classique non différé est chargé. Pour le chargement des providers, consultez [Découverte automatique des packages](https://kawax.biz/fr/advanced/package-discovery.md).

### Séparer la logique de validation dans une classe

Préparez dans `src/Validation/TrackingCode.php` un handler pour la règle sous forme de chaîne. Cette classe n'implémente pas l'interface `ValidationRule`.

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

namespace Acme\Courier\Validation;

use Illuminate\Validation\Validator;
use InvalidArgumentException;

final class TrackingCode
{
    public function validate(
        string $attribute,
        mixed $value,
        array $parameters,
        Validator $validator,
    ): bool {
        if (count($parameters) !== 1
            || ! is_string($parameters[0])
            || preg_match('/\A[A-Z]{2,8}\z/', $parameters[0]) !== 1
        ) {
            throw new InvalidArgumentException(
                'acme_tracking expects one uppercase ASCII prefix of 2-8 characters.',
            );
        }

        return is_string($value)
            && preg_match(
                '/\A'.preg_quote($parameters[0], '/').'-[0-9]{8}\z/',
                $value,
            ) === 1;
    }
}
```

Le callback reçoit quatre arguments : le nom de l'attribut, la valeur saisie, le tableau de paramètres et le Validator en cours de validation. Dans le cas de `acme_tracking:AC`, le tableau de paramètres est `['AC']`. La règle vérifie elle-même le type de la valeur, afin de ne pas provoquer d'erreur de type même lorsqu'elle est utilisée seule.

Un préfixe invalide ou un nombre de paramètres incorrect lève une exception, car il s'agit non pas d'une erreur de saisie de l'utilisateur mais d'une **erreur de configuration dans la définition de la règle**. Lorsque la règle est ignorée pour une valeur vide, cette vérification de configuration n'est pas exécutée non plus ; il faut donc aussi des tests avec une valeur non vide valide.

### Enregistrer après la résolution de la Factory

Depuis la méthode `boot()` de `src/CourierServiceProvider.php`, enregistrez la règle et le traitement de remplacement du message.

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

namespace Acme\Courier;

use Acme\Courier\Validation\TrackingCode;
use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory;

final class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->callAfterResolving('validator', function (Factory $factory): void {
            $factory->extend(
                'acme_tracking',
                TrackingCode::class.'@validate',
                'The :attribute must be :prefix- followed by eight digits.',
            );

            $factory->replacer(
                'acme_tracking',
                static function (
                    string $message,
                    string $attribute,
                    string $rule,
                    array $parameters,
                ): string {
                    return str_replace(':prefix', $parameters[0] ?? '', $message);
                },
            );
        });
    }
}
```

`callAfterResolving()` attend la création de la Factory si elle n'est pas encore résolue, et enregistre immédiatement si elle l'est déjà. Le hook lui-même est détaillé dans [Hooks après résolution des services de package](https://kawax.biz/fr/advanced/package-service-resolution.md).

La chaîne de classe `TrackingCode::class.'@validate'` résout la classe via le conteneur au moment de la validation, puis appelle la méthode. Même avec seulement le nom de la classe sans `@validate`, le nom de méthode par défaut est `validate`. Si le constructeur a des dépendances, liez-les de façon à ce que le conteneur puisse les résoudre.

Cet exemple n'enregistre pas le handler comme singleton et ne comporte aucune propriété stockant les entrées. Les données de validation sont manipulées via les arguments, et la conception évite de capturer un état propre à la requête dans les closures conservées par la Factory.

### Utiliser la règle depuis l'application consommatrice

```php theme={null}
$request->validate([
    'tracking_code' => ['bail', 'required', 'string', 'acme_tracking:AC'],
]);
```

`bail` arrête les règles suivantes dès qu'une règle précédente de cet attribut échoue. Les rôles sont séparés : `required` pour le caractère obligatoire, `acme_tracking` pour le format.

Uniformisez le nom public avec un snake case unique comme `acme_tracking`. Un enregistrement portant le même nom pour le même type d'extension est écrasé par le dernier. Par ailleurs, lorsqu'une méthode intégrée comme `validateRequired()` existe, c'est elle qui est appelée directement : une extension portant le même nom ne peut donc pas remplacer une validation intégrée.

## Préserver les messages d'erreur de l'utilisateur

Le message d'une règle sous forme de chaîne classique est sélectionné globalement selon l'ordre de priorité suivant. Les branches particulières comme les règles de taille ne sont pas traitées ici.

| Priorité | Source du message |
| - | - |
| 1 | Troisième argument de `Validator::make()` ou `messages()` de la form request |
| 2 | Traduction `validation.custom.nom_attribut.nom_règle` de l'application consommatrice |
| 3 | Traduction du nom de la règle, comme `validation.acme_tracking` |
| 4 | Chaîne fallback enregistrée comme troisième argument de `Factory::extend()` |

Le replacer personnalisé est appelé après que Laravel a remplacé `:attribute` et les autres placeholders. Le replacer reçoit cinq arguments : le message sélectionné, le nom de l'attribut, le nom de la règle, le tableau de paramètres et le Validator. L'exemple précédent omet le cinquième argument, qu'il n'utilise pas.

> [!WARNING]
>
> Si le replacer remplace systématiquement tout le message par un texte fixe, il ignore les `messages()` et les surcharges de traduction de l'utilisateur. L'exemple précédent ne remplace que `:prefix` dans le `$message` reçu.

Pour définir un message par défaut en japonais côté application, ajoutez `acme_tracking` au tableau retourné par le fichier existant `lang/ja/validation.php`. L'exemple ci-dessous montre l'entrée à ajouter ; il ne supprime pas les autres messages intégrés.

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

return [
    'acme_tracking' => ':attributeは:prefix-に続く8桁の数字で入力してください。',
];
```

Pour la préparation des fichiers de traduction et la configuration de la locale, consultez [Localisation](https://kawax.biz/fr/localization.md). Si vous exécutez `__('...')` au moment de l'enregistrement et stockez le résultat comme fallback, la chaîne de la locale alors active reste dans la Factory. Dans une application qui change de locale, utilisez la traduction au moment de la génération de l'erreur.

### Utiliser les traductions avec espace de noms du package

En appelant `loadTranslationsFrom(__DIR__.'/../lang', 'acme-courier')` dans `boot()`, vous pouvez référencer le fichier `lang/ja/validation.php` du package, entre autres, via `acme-courier::validation.tracking_code`.

Cependant, **passer simplement une clé de traduction comme troisième argument de `extend()` ne la traduit pas automatiquement.** La Factory la conserve comme une simple chaîne fallback. En remplaçant les appels `extend()` et `replacer()` du hook précédent par l'enregistrement suivant, vous pouvez récupérer la traduction du package uniquement lorsque le fallback est sélectionné.

```php theme={null}
$factory->extend(
    'acme_tracking',
    TrackingCode::class.'@validate',
    'acme-courier::validation.tracking_code',
);

$factory->replacer(
    'acme_tracking',
    static function (
        string $message,
        string $attribute,
        string $rule,
        array $parameters,
        \Illuminate\Validation\Validator $validator,
    ): string {
        if ($message === 'acme-courier::validation.tracking_code') {
            return $validator->getTranslator()->get($message, [
                'attribute' => $validator->getDisplayableAttribute($attribute),
                'prefix' => $parameters[0] ?? '',
            ]);
        }

        return str_replace(':prefix', $parameters[0] ?? '', $message);
    },
);
```

Préparez une entrée `tracking_code` dans le `validation.php` de chaque locale du package. Pour le japonais, vous pouvez utiliser le même texte que l'entrée `acme_tracking` précédente.

Comme le replacer récupère ici une nouvelle chaîne de traduction, `:attribute` est aussi passé explicitement à la traduction. Le remplacement standard de Laravel n'est pas réappliqué à cette valeur de retour. Si un message personnalisé de l'utilisateur est sélectionné, son texte est conservé. Pour la surcharge des traductions distribuées, consultez [Surcharge et mise à jour des traductions de package](https://kawax.biz/fr/advanced/package-translations.md).

## Comprendre les conditions d'exécution avec Implicit et nullable

`extendImplicit()` enregistre une règle qui devient candidate à la validation même pour un attribut absent ou une chaîne vide. Cela ne signifie ni « faire échouer systématiquement » ni « ignorer toutes les autres conditions de saut ».

| Entrée / spécification | `extend()` classique | `extendImplicit()` |
| - | - | - |
| Attribut absent | Non exécutée | Candidate à l'exécution |
| Chaîne vide ou composée uniquement d'espaces | Non exécutée | Candidate à l'exécution |
| `null` sur un attribut présent, sans `nullable` | Candidate à l'exécution | Candidate à l'exécution |
| `null` sur un attribut présent, avec `nullable` | Non exécutée | Candidate à l'exécution |
| Attribut absent, avec `sometimes` | Non exécutée | Non exécutée |

Le tableau compare les conditions d'exécution de chaque règle. Les cas où le traitement s'arrête plus tôt à cause de `bail`, de l'exclusion d'attributs ou de `stopOnFirstFailure()` sont à part. Pour les entrées HTTP, une chaîne vide peut être convertie en `null` par un middleware ; testez donc séparément la chaîne vide et `null`.

Pour la règle de format présentée ici, la combinaison d'un `extend()` classique et de `required` suffit. Passer une règle déjà publiée en Implicit peut faire échouer des entrées qui n'étaient pas validées auparavant ; traitez donc ce changement comme une modification de compatibilité.

## Résoudre les références dans les tableaux avec les règles Dependent

Pour une règle qui prend en paramètre le nom d'un autre champ, vous pouvez utiliser `extendDependent()`. Lors de la validation de `items.*.confirmed_tracking`, le paramètre `items.*.tracking` est converti vers la même ligne, par exemple `items.0.tracking`. Ce remplacement n'est pas effectué avec un `extend()` classique.

Voici une règle de correspondance destinée à illustrer ce mécanisme. Pour une simple vérification d'égalité, une application réelle peut utiliser la règle intégrée `same`. Considérez-la comme un exemple à adapter pour des validations inter-champs propres à votre package.

Ajoutez-la à la Factory reçue depuis le hook.

```php theme={null}
$factory->extendDependent(
    'acme_matches_field',
    static function (
        string $attribute,
        mixed $value,
        array $parameters,
        \Illuminate\Validation\Validator $validator,
    ): bool {
        if (count($parameters) !== 1
            || ! is_string($parameters[0])
            || $parameters[0] === ''
        ) {
            throw new \InvalidArgumentException('acme_matches_field expects one field name.');
        }

        $data = $validator->getData();

        return \Illuminate\Support\Arr::has($data, $parameters[0])
            && $value === \Illuminate\Support\Arr::get($data, $parameters[0]);
    },
    'The :attribute does not match the related field.',
);
```

```php theme={null}
$request->validate([
    'items' => ['required', 'array'],
    'items.*' => ['array:tracking,confirmed_tracking'],
    'items.*.tracking' => ['required', 'string', 'acme_tracking:AC'],
    'items.*.confirmed_tracking' => [
        'required',
        'string',
        'acme_matches_field:items.*.tracking',
    ],
]);
```

L'enregistrement en Dependent ne garantit ni l'existence du champ référencé ni son état validé. Le `getData()` du quatrième argument correspond aux **données soumises à la validation**, et pas uniquement aux données validées. Définissez les règles nécessaires à la fois sur le champ référençant et sur le champ référencé.

De plus, une règle Dependent ne devient pas Implicit pour autant. Spécifiez séparément le caractère obligatoire pour les valeurs absentes ou vides. L'implémentation applique aux paramètres un échappement des points et le remplacement de `*` ; évitez donc une API qui mélange des chaînes arbitraires autres que des noms de champs.

## Tester de l'enregistrement à la validation dans une application

Dans une application Laravel 13 qui charge le package et son Service Provider, créez `tests/Feature/TrackingRuleTest.php`. En plus des tests unitaires de la classe, vérifiez l'enregistrement et le traitement des messages via la Factory réelle.

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

namespace Tests\Feature;

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Factory;
use Tests\TestCase;

final class TrackingRuleTest extends TestCase
{
    public function test_format_presence_and_null_are_separate(): void
    {
        $rules = ['code' => ['acme_tracking:AC']];

        foreach ([
            [['code' => 'AC-00000001'], true],
            [['code' => 'ZX-00000001'], false],
            [['code' => ['AC-00000001']], false],
            [['code' => ''], true],
            [['code' => null], false],
            [[], true],
        ] as [$data, $expected]) {
            $this->assertSame($expected, Validator::make($data, $rules)->passes());
        }

        $this->assertTrue(Validator::make(
            ['code' => null],
            ['code' => ['nullable', 'acme_tracking:AC']],
        )->passes());

        $this->assertFalse(Validator::make(
            [],
            ['code' => ['required', 'acme_tracking:AC']],
        )->passes());
    }

    public function test_application_message_and_display_name_are_kept(): void
    {
        $validator = Validator::make(
            ['code' => 'invalid'],
            ['code' => ['acme_tracking:AC']],
            ['code.acme_tracking' => ':attributeには:prefixのコードを入力してください。'],
            ['code' => '追跡番号'],
        );

        $this->assertSame(
            '追跡番号にはACのコードを入力してください。',
            $validator->errors()->first('code'),
        );
    }

    public function test_existing_validator_keeps_its_extensions(): void
    {
        $factory = new Factory($this->app['translator'], $this->app);
        $factory->extend('acme_probe', static fn (): bool => true);
        $before = $factory->make(['code' => 'x'], ['code' => ['acme_probe']]);

        $factory->extend('acme_probe', static fn (): bool => false, 'Rejected.');
        $after = $factory->make(['code' => 'x'], ['code' => ['acme_probe']]);

        $this->assertTrue($before->passes());
        $this->assertFalse($after->passes());
    }
}
```

```bash theme={null}
php artisan test --compact tests/Feature/TrackingRuleTest.php
```

Le dernier test ne modifie pas la Factory partagée : il crée une nouvelle Factory et vérifie la copie des éléments enregistrés. Si vous fournissez aussi les exemples de traductions avec espace de noms ou de règles Dependent, préparez des tests supplémentaires dans un environnement incluant les enregistrements correspondants.

## Points à vérifier pour la maintenance à long terme

* Documenter comme API publique le nom de la règle, la signification et le nombre de paramètres, le caractère obligatoire et les types de valeurs acceptés.
* Tester les paramètres invalides, les valeurs absentes, les chaînes vides et `null`, ainsi que les combinaisons avec `nullable`, `sometimes` et `bail`.
* Vérifier les `messages()` de l'utilisateur, les noms d'affichage des attributs, le changement de locale et la surcharge des traductions du package.
* Pour les règles Dependent, vérifier avec plusieurs lignes et des tableaux imbriqués qu'aucune valeur d'une autre ligne n'est référencée.
* Vérifier l'enregistrement sur une Factory non résolue et déjà résolue, sans s'attendre à ce qu'il soit appliqué rétroactivement aux Validator déjà créés.
* Éviter les collisions de noms avec d'autres packages et ne pas mélanger un même nom entre enregistrements classique, Implicit et Dependent.
* Pour appliquer des changements aux workers de longue durée, redémarrer les processus, et ne pas laisser d'entrées ni d'informations utilisateur dans des handlers partagés.

Consultez également [Tests de packages](https://kawax.biz/fr/advanced/package-testing.md) pour l'environnement de validation d'un package seul, et [Gestion de la compatibilité des versions de package](https://kawax.biz/fr/advanced/package-versioning.md) pour la gestion de la compatibilité.

## Sources primaires consultées

* [Documentation officielle de Laravel : règles personnalisées et règles Implicit](https://github.com/laravel/docs/blob/10add0d2dade04cacfab436dcc3a8df7ca004e74/validation.md#custom-validation-rules)
* [Documentation officielle de Laravel : Service Provider et traductions des packages](https://github.com/laravel/docs/blob/10add0d2dade04cacfab436dcc3a8df7ca004e74/packages.md)
* [Factory : make, extend, extendImplicit, extendDependent, replacer](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Factory.php)
* [Validator : conditions d'exécution, enregistrement des extensions, résolution des classes](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Validator.php)
* [ValidationRuleParser : analyse des règles sous forme de chaîne](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/ValidationRuleParser.php)
* [FormatsMessages : priorité et remplacement des messages](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Concerns/FormatsMessages.php)
* [ValidationServiceProvider : enregistrement de validator en singleton](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/ValidationServiceProvider.php)
* [ServiceProvider : callAfterResolving](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Support/ServiceProvider.php#L310-L317)
* [Tests officiels : transfert des extensions et du fallback depuis la Factory](https://github.com/laravel/framework/blob/v13.40.0/tests/Validation/ValidationFactoryTest.php)
* [Tests officiels : règles en chaîne, classe, Implicit et Dependent](https://github.com/laravel/framework/blob/v13.40.0/tests/Validation/ValidationValidatorTest.php#L7372-L7471)


## Related topics

- [Enregistrement de relations dynamiques pour les packages](https://kawax.biz/fr/advanced/package-dynamic-relations.md)
- [Règles de validation personnalisées](https://kawax.biz/fr/advanced/custom-validation-rules.md)
- [Hooks de résolution des services d'un package](https://kawax.biz/fr/advanced/package-service-resolution.md)
- [Extension des connexions de base de données pour les packages](https://kawax.biz/fr/advanced/package-database-connections.md)
- [Ajouter des informations de diagnostic du package à about](https://kawax.biz/fr/advanced/package-about.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.