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

# Stringvalidatieregels registreren vanuit packages

> Ontleed Factory::extend van Laravel 13 en leer het aanroepcontract van stringregels, foutmeldingen en vertalingen, het verschil tussen Implicit en Dependent, de levensduur van reeds aangemaakte Validators en testen.

Om vanuit een gedistribueerd package een stringregel zoals `acme_tracking:AC` aan te bieden, registreer je de validatielogica bij de validatie-`Factory`. Vanuit de form requests van de gebruikende applicatie kan de regel dan met dezelfde notatie als de ingebouwde regels worden gebruikt.

Deze pagina is gecontroleerd aan de hand van de officiële implementatie van Laravel Framework `v13.40.0`. Ze vult de basisvoorbeelden van [Aangepaste validatieregels](https://kawax.biz/nl/advanced/custom-validation-rules.md) aan en behandelt registratie, vertaling en de levensduur van state wanneer je de regel onderhoudt als publieke API van een package.

## Kiezen tussen dit en regelobjecten

De `ValidationRule`-objecten die in de officiële documentatie worden geïntroduceerd, kun je ook in packages gebruiken. Als het niet expliciet een vereiste is dat de regel als string kan worden opgegeven, overweeg dan een object dat getypeerde argumenten via de constructor ontvangt.

| Aanpak | Geschikt voor | Melden van een fout |
| - | - | - |
| Een regelobject zoals `new CustomRule(...)` | Getypeerde configuratie of complexe validatie als klasse distribueren | De aan `validate()` doorgegeven `$fail` aanroepen |
| Een geregistreerde stringregel zoals `acme_tracking:AC` | Een API aanbieden die ook in stringgebaseerde regeldefinities past | De geregistreerde callback retourneert `false` |

Ook al heten ze allebei `validate()`, het aanroepcontract verschilt. Als je een klasse die `ValidationRule` implementeert direct als klassestring aan `Factory::extend()` doorgeeft, komen het type van het derde argument en de retourwaarde niet overeen.

## Registratie, aanmaak en validatie scheiden

`extend()` op de `Validator`-facade is een aanroep naar `Illuminate\Validation\Factory`. Dat is een ander object dan de `Illuminate\Validation\Validator`-instantie die de invoer valideert.

```mermaid theme={null}
flowchart TD
    A["boot van het package"] --> B["Regel en replacer<br>bij de Factory registreren"]
    B --> C["Nieuwe Validator aanmaken<br>met Factory::make"]
    C --> D["Geregistreerde extensies en<br>fallbackmeldingen kopiëren"]
    D --> E["Regels per attribuut parsen<br>en uitvoeringsvoorwaarden controleren"]
    E --> F["Valideren met klasse of closure"]
    F --> G{"Retourwaarde is true?"}
    G -->|Yes| H["Deze regel slaagt"]
    G -->|No| I["Melding kiezen en<br>placeholders vervangen"]
```

Het diagram toont het pad wanneer een stringregel wordt uitgevoerd. Het overslaan door ontbrekende invoer of `nullable` wordt verderop behandeld.

De Factory wordt geregistreerd als singleton onder de sleutel `validator`, maar de Validator die de standaardimplementatie van `make()` retourneert, wordt elke keer opnieuw aangemaakt. **Latere registraties bij de Factory werken de extensie-arrays van reeds aangemaakte Validators niet bij.**

## Package-specifieke regels registreren

Als voorbeeld valideren we een fictieve trackingcode die bestaat uit een prefix van hoofdletters, een koppelteken en acht cijfers. Het gaat om een voorbeeldformaat zoals `AC-00000001`, niet om de nummerspecificatie van een bestaande vervoerder.

We gaan uit van een opzet waarin de package-namespace `Acme\Courier\` via Composer PSR-4 naar `src/` verwijst en een gewone, niet-uitgestelde service provider wordt geladen. Zie [Package discovery](https://kawax.biz/nl/advanced/package-discovery.md) voor het laden van providers.

### De validatielogica in een klasse onderbrengen

Maak in `src/Validation/TrackingCode.php` een handler voor de stringregel. Deze klasse implementeert de `ValidationRule`-interface niet.

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

De callback ontvangt vier argumenten: de attribuutnaam, de invoerwaarde, de parameterarray en de Validator die de validatie uitvoert. Bij `acme_tracking:AC` is de parameterarray `['AC']`. Ook de regel zelf controleert het type van de waarde, zodat er geen typefout optreedt wanneer de regel op zichzelf wordt gebruikt.

Een ongeldig prefix of een onjuist aantal parameters wordt niet als invoerfout van de gebruiker behandeld, maar als **configuratiefout in de regeldefinitie** en leidt tot een exception. Wanneer de regel bij een lege waarde wordt overgeslagen, wordt ook deze configuratiecontrole niet uitgevoerd, dus je hebt ook tests nodig met een geldige, niet-lege waarde.

### Registreren nadat de Factory is geresolved

Registreer de regel en de verwerking voor het vervangen van meldingen vanuit `boot()` in `src/CourierServiceProvider.php`.

```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()` wacht op de aanmaak als de Factory nog niet is geresolved, en registreert direct bij een Factory die al wel is geresolved. De hook zelf wordt in detail uitgelegd in [Hooks na het resolven van services in packages](https://kawax.biz/nl/advanced/package-service-resolution.md).

Bij de klassestring `TrackingCode::class.'@validate'` wordt de klasse tijdens de validatie via de container geresolved en wordt de methode aangeroepen. Ook als je `@validate` weglaat en alleen de klassenaam opgeeft, is de standaardmethodenaam `validate`. Als de constructor afhankelijkheden heeft, bind ze dan zodat de container ze kan resolven.

Dit voorbeeld registreert de handler niet als singleton en heeft geen properties die invoer opslaan. Verwerk de gegevens tijdens de validatie via de argumenten, en ontwerp het zo dat er geen requestspecifieke state wordt vastgelegd in closures die de Factory bewaart.

### Opgeven vanuit de gebruikende applicatie

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

`bail` stopt de volgende regels zodra een eerdere regel voor dat attribuut faalt. Verdeel de rollen: `required` voor de verplichting en `acme_tracking` voor het formaat.

Gebruik voor de publieke naam consequent een unieke snake case zoals `acme_tracking`. Bij registratie onder dezelfde naam voor hetzelfde soort extensie overschrijft de latere registratie de eerdere. Bovendien wordt, als er een ingebouwde methode zoals `validateRequired()` bestaat, die methode direct aangeroepen, dus je kunt ingebouwde validatie niet vervangen met een extensie met dezelfde naam.

## De foutmeldingen van de gebruiker behouden

De melding voor een gewone stringregel wordt grofweg in de volgende volgorde van prioriteit gekozen. Speciale vertakkingen zoals bij grootteregels vallen hier buiten.

| Prioriteit | Bron van de melding |
| - | - |
| 1 | Het derde argument van `Validator::make()` of `messages()` van een form request |
| 2 | De vertaling `validation.custom.attribuutnaam.regelnaam` van de gebruikende applicatie |
| 3 | De vertaling voor de regelnaam, zoals `validation.acme_tracking` |
| 4 | De fallbackstring die als derde argument van `Factory::extend()` is geregistreerd |

Nadat Laravel onder andere `:attribute` heeft vervangen, wordt de aangepaste replacer aangeroepen. De replacer ontvangt vijf argumenten: de gekozen melding, de attribuutnaam, de regelnaam, de parameterarray en de Validator. In het eerdere voorbeeld is het ongebruikte vijfde argument weggelaten.

> [!WARNING]
>
> Als de replacer de volledige melding altijd door een vaste tekst vervangt, worden de `messages()` en vertaaloverschrijvingen van de gebruiker genegeerd. Het eerdere voorbeeld vervangt alleen `:prefix` in de doorgegeven `$message`.

Om in de applicatie een standaardtekst in het Japans op te geven, voeg je `acme_tracking` toe aan de geretourneerde array in de bestaande `lang/ja/validation.php`. Het onderstaande voorbeeld toont de toe te voegen entry en verwijdert geen andere ingebouwde meldingen.

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

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

Zie [Lokalisatie](https://kawax.biz/nl/localization.md) voor het voorbereiden van vertaalbestanden en het instellen van de locale. Als je bij de registratie `__('...')` uitvoert en het resultaat als fallback opslaat, blijft de string van de toen actieve locale in de Factory achter. Gebruik in applicaties die van locale wisselen de vertaling op het moment dat de fout wordt gegenereerd.

### Bij gebruik van vertalingen met package-namespace

Als je in `boot()` `loadTranslationsFrom(__DIR__.'/../lang', 'acme-courier')` aanroept, kun je onder andere de `lang/ja/validation.php` van het package benaderen met `acme-courier::validation.tracking_code`.

**Alleen een vertaalsleutel als derde argument aan `extend()` doorgeven, leidt echter niet tot automatische vertaling.** De Factory bewaart die als gewone fallbackstring. Als je `extend()` en `replacer()` in de eerdere hook vervangt door de volgende registratie, wordt de vertaling van het package alleen opgehaald wanneer de fallback wordt gekozen.

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

Voorzie de `validation.php` van elke locale in het package van een `tracking_code`-entry. Voor de Japanse tekst kun je dezelfde tekst gebruiken als in de eerdere `acme_tracking`-entry.

Omdat hier binnen de replacer een nieuwe vertaalstring wordt opgehaald, wordt ook `:attribute` expliciet aan de vertaling doorgegeven. Laravels standaardvervanging wordt niet opnieuw op deze retourwaarde toegepast. Wanneer een eigen melding van de gebruiker wordt gekozen, blijft die tekst behouden. Zie [Packagevertalingen overschrijven en bijwerken](https://kawax.biz/nl/advanced/package-translations.md) voor het overschrijven van gedistribueerde vertalingen.

## De uitvoeringsvoorwaarden van Implicit en nullable begrijpen

`extendImplicit()` registreert een regel die ook bij ontbrekende attributen of lege strings in aanmerking komt voor validatie. Het betekent niet "altijd laten falen" of "alle andere voorwaarden voor overslaan negeren".

| Invoer/opgave | Gewone `extend()` | `extendImplicit()` |
| - | - | - |
| Attribuut ontbreekt | Niet uitgevoerd | Komt in aanmerking |
| Lege string of string met alleen witruimte | Niet uitgevoerd | Komt in aanmerking |
| `null` bij bestaand attribuut, zonder `nullable` | Komt in aanmerking | Komt in aanmerking |
| `null` bij bestaand attribuut, met `nullable` | Niet uitgevoerd | Komt in aanmerking |
| Attribuut ontbreekt, met `sometimes` | Niet uitgevoerd | Niet uitgevoerd |

De tabel vergelijkt de uitvoeringsvoorwaarden per regel. Gevallen waarin de verwerking eerder stopt door `bail`, het uitsluiten van attributen, `stopOnFirstFailure()` en dergelijke staan hier los van. Bij HTTP-invoer kunnen lege strings door middleware naar `null` worden omgezet, dus test lege strings en `null` afzonderlijk.

Voor de formaatregel in dit voorbeeld volstaat de combinatie van de gewone `extend()` en `required`. Als je een gepubliceerde regel naar Implicit wijzigt, kan invoer die voorheen niet werd gevalideerd gaan falen, dus behandel dat als een compatibiliteitswijziging.

## Verwijzingen binnen arrays resolven met Dependent-regels

Voor regels die de naam van een ander veld als parameter nemen, kun je `extendDependent()` gebruiken. Bij het valideren van `items.*.confirmed_tracking` wordt de parameter `items.*.tracking` omgezet naar bijvoorbeeld `items.0.tracking` in dezelfde rij. Met de gewone `extend()` vindt deze vervanging niet plaats.

Hieronder volgt een overeenkomstregel om dit mechanisme te verkennen. Voor een eenvoudige controle op overeenkomst kun je in een echte applicatie de ingebouwde regel `same` gebruiken. Beschouw dit als voorbeeld voor het toepassen op package-specifieke validatie tussen velden.

Voeg de regel toe aan de Factory die je vanuit de hook ontvangt.

```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',
    ],
]);
```

Registratie als Dependent garandeert niet dat het doelveld bestaat of al gevalideerd is. `getData()` op het vierde argument geeft **de te valideren gegevens**, niet alleen de gevalideerde gegevens. Definieer de benodigde regels voor zowel het verwijzende veld als het doelveld.

Een Dependent-regel wordt bovendien niet Implicit. Geef de verplichting bij ontbrekende of lege waarden apart op. De implementatie past op de parameters escaping van punten en vervanging van `*` toe, dus vermijd API's die willekeurige strings anders dan veldnamen in de parameters mengen.

## Van registratie tot validatie testen in een applicatie

Maak `tests/Feature/TrackingRuleTest.php` aan in een Laravel 13-applicatie waarin het package en de service provider worden geladen. Naast tests van de klasse op zich valideer je registratie en meldingsverwerking via de echte Factory.

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

De laatste test wijzigt de gedeelde Factory niet, maar maakt een nieuwe Factory aan om het kopiëren van de registraties te controleren. Als je ook de voorbeelden met vertalingen met namespace of Dependent aanbiedt, voorzie dan in extra tests in een omgeving die de bijbehorende registraties bevat.

## Aandachtspunten voor langdurig onderhoud

* Documenteer de regelnaam, de betekenis en het aantal parameters, de verplichting en de geaccepteerde waardetypen als publieke API.
* Valideer ongeldige parameters, ontbrekende waarden, lege strings en `null`, en combinaties van `nullable`, `sometimes` en `bail`.
* Controleer `messages()` van de gebruiker, weergavenamen van attributen, het wisselen van locale en het overschrijven van packagevertalingen.
* Controleer bij Dependent-regels met meerdere rijen en geneste arrays dat er niet naar waarden in andere rijen wordt verwezen.
* Controleer registratie bij zowel niet-geresolvede als geresolvede Factories, en verwacht niet dat wijzigingen met terugwerkende kracht in reeds aangemaakte Validators doorwerken.
* Voorkom naamconflicten met andere packages en meng dezelfde naam niet over gewone, Implicit- en Dependent-registraties.
* Herstart het proces om wijzigingen in langlevende workers door te voeren, en laat geen invoer of gebruikersgegevens achter in gedeelde handlers.

Zie ook [Packages testen](https://kawax.biz/nl/advanced/package-testing.md) voor een testomgeving voor het package op zich, en [Versiecompatibiliteit van packages beheren](https://kawax.biz/nl/advanced/package-versioning.md) voor de omgang met compatibiliteit.

## Geraadpleegde primaire bronnen

* [Officiële Laravel-documentatie: aangepaste regels en Implicit-regels](https://github.com/laravel/docs/blob/10add0d2dade04cacfab436dcc3a8df7ca004e74/validation.md#custom-validation-rules)
* [Officiële Laravel-documentatie: service providers en vertalingen van packages](https://github.com/laravel/docs/blob/10add0d2dade04cacfab436dcc3a8df7ca004e74/packages.md)
* [Factory: make, extend, extendImplicit, extendDependent en replacer](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Factory.php)
* [Validator: uitvoeringsvoorwaarden, registratie van extensies en het resolven van klassen](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Validator.php)
* [ValidationRuleParser: het parsen van stringregels](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/ValidationRuleParser.php)
* [FormatsMessages: prioriteit en vervanging van meldingen](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Concerns/FormatsMessages.php)
* [ValidationServiceProvider: singletonregistratie van validator](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)
* [Officiële tests: doorgeven van extensies en fallbacks vanuit de Factory](https://github.com/laravel/framework/blob/v13.40.0/tests/Validation/ValidationFactoryTest.php)
* [Officiële tests: string-, klasse-, Implicit- en Dependent-regels](https://github.com/laravel/framework/blob/v13.40.0/tests/Validation/ValidationValidatorTest.php#L7372-L7471)


## Related topics

- [Dynamische relaties registreren vanuit packages](https://kawax.biz/nl/advanced/package-dynamic-relations.md)
- [Databaseverbindingen uitbreiden vanuit packages](https://kawax.biz/nl/advanced/package-database-connections.md)
- [Artisan-commands van een package registreren en afhankelijkheden resolven](https://kawax.biz/nl/advanced/package-commands.md)
- [Mailtransports voor packages registreren en testen](https://kawax.biz/nl/advanced/package-mail-transports.md)
- [Geplande taken van een package registreren en onderhouden](https://kawax.biz/nl/advanced/package-scheduling.md)


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