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

# String-Validierungsregeln in Paketen registrieren

> Analysiert Factory::extend in Laravel 13 und erläutert den Aufrufvertrag von String-Regeln, Fehlermeldungen und Übersetzungen, den Unterschied zwischen Implicit und Dependent, die Lebensdauer bereits erzeugter Validatoren sowie Tests.

Um aus einem verteilten Paket eine String-Regel wie `acme_tracking:AC` bereitzustellen, registrieren Sie die Prüflogik in der `Factory` der Validierung. In den Form Requests der nutzenden Anwendung lässt sie sich dann mit derselben Schreibweise wie die eingebauten Regeln verwenden.

Diese Seite basiert auf der offiziellen Implementierung von Laravel Framework `v13.40.0`. Sie ergänzt die Grundbeispiele aus [Benutzerdefinierte Validierungsregeln](https://kawax.biz/de/advanced/custom-validation-rules.md) und behandelt Registrierung, Übersetzung und die Lebensdauer des Zustands, wenn Sie die Regel als öffentliche API eines Pakets pflegen.

## Abgrenzung zu Regelobjekten

Die in der offiziellen Dokumentation vorgestellten `ValidationRule`-Objekte können auch in Paketen verwendet werden. Sofern die Angabe als String nicht selbst eine Anforderung ist, sollten Sie ein Objekt in Betracht ziehen, das typisierte Argumente über den Konstruktor entgegennimmt.

| Ansatz | Geeigneter Einsatzzweck | Meldung eines Fehlschlags |
| - | - | - |
| Regelobjekt wie `new CustomRule(...)` | Typisierte Konfiguration oder komplexe Prüfungen als Klasse verteilen | Das an `validate()` übergebene `$fail` aufrufen |
| Registrierte String-Regel wie `acme_tracking:AC` | Eine API bereitstellen, die sich auch in stringbasierte Regeldefinitionen einfügt | Der registrierte Callback gibt `false` zurück |

Trotz des gleichen Namens `validate()` unterscheiden sich die Aufrufverträge. Wenn Sie eine Klasse, die `ValidationRule` implementiert, unverändert als Klassen-String an `Factory::extend()` übergeben, passen weder der Typ des dritten Arguments noch der Rückgabewert.

## Registrierung, Erzeugung und Validierung trennen

`extend()` der `Validator`-Fassade ist ein Aufruf an `Illuminate\Validation\Factory`. Dies ist ein anderes Objekt als die `Illuminate\Validation\Validator`-Instanz, die die Eingaben prüft.

```mermaid theme={null}
flowchart TD
    A["boot des Pakets"] --> B["Regel und Replacer<br>in der Factory registrieren"]
    B --> C["Mit Factory::make einen<br>neuen Validator erzeugen"]
    C --> D["Registrierte Erweiterungen und<br>Fallback-Meldungen kopieren"]
    D --> E["Regeln jedes Attributs parsen<br>und Ausführungsbedingungen prüfen"]
    E --> F["Mit Klasse oder Closure validieren"]
    F --> G{"Rückgabewert true?"}
    G -->|Ja| H["Diese Regel ist erfolgreich"]
    G -->|Nein| I["Meldung auswählen und<br>Platzhalter ersetzen"]
```

Das Diagramm zeigt den Ablauf, wenn eine String-Regel ausgeführt wird. Das Überspringen bei fehlender Eingabe oder durch `nullable` wird weiter unten behandelt.

Die Factory wird als Singleton unter dem Schlüssel `validator` registriert, doch der von `make()` der Standardimplementierung zurückgegebene Validator wird jedes Mal neu erzeugt. **Nachträgliche Registrierungen in der Factory aktualisieren nicht das Erweiterungs-Array bereits erzeugter Validatoren.**

## Paketspezifische Regeln registrieren

Als Beispiel validieren wir einen fiktiven Sendungscode aus einem Präfix in ASCII-Großbuchstaben, einem Bindestrich und acht Ziffern. Ein Format wie `AC-00000001` dient nur als Beispiel und entspricht nicht der Nummernspezifikation eines realen Versanddienstleisters.

Angenommen wird ein Aufbau, bei dem der Paket-Namespace `Acme\Courier\` per Composer PSR-4 auf `src/` abgebildet wird und ein regulärer, nicht verzögerter Service Provider geladen wird. Zum Laden des Providers siehe [Automatische Paketerkennung](https://kawax.biz/de/advanced/package-discovery.md).

### Die Prüflogik in eine Klasse auslagern

Legen Sie in `src/Validation/TrackingCode.php` einen Handler für die String-Regel an. Diese Klasse implementiert das Interface `ValidationRule` nicht.

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

Der Callback erhält vier Argumente: den Attributnamen, den Eingabewert, das Parameter-Array und den gerade validierenden Validator. Bei `acme_tracking:AC` lautet das Parameter-Array `['AC']`. Auch die Regel selbst prüft den Typ des Werts, damit sie bei alleiniger Verwendung keinen Typfehler auslöst.

Ein ungültiges Präfix oder eine falsche Parameteranzahl wird nicht als Eingabefehler des Benutzers, sondern als **Konfigurationsfehler in der Regeldefinition** mit einer Exception behandelt. Wird die Regel bei leeren Werten übersprungen, läuft auch diese Konfigurationsprüfung nicht. Daher brauchen Sie zusätzlich Tests mit gültigen, nicht leeren Werten.

### Nach der Auflösung der Factory registrieren

Registrieren Sie die Regel und die Meldungsersetzung in `boot()` von `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()` wartet auf die Erzeugung, falls die Factory noch nicht aufgelöst ist, und registriert bei einer bereits aufgelösten Factory sofort. Details zum Hook selbst finden Sie unter [Hooks nach der Service-Auflösung in Paketen](https://kawax.biz/de/advanced/package-service-resolution.md).

Der Klassen-String `TrackingCode::class.'@validate'` löst die Klasse zur Validierungszeit über den Container auf und ruft die Methode auf. Auch ohne `@validate`, also nur mit dem Klassennamen, ist der Standard-Methodenname `validate`. Hat die Klasse Konstruktorabhängigkeiten, binden Sie diese so, dass der Container sie auflösen kann.

Dieses Beispiel registriert den Handler weder als Singleton noch besitzt es Eigenschaften, die Eingaben speichern. Daten zur Validierungszeit werden über die Argumente verarbeitet, und die von der Factory gehaltenen Closures sollten keinen anfragespezifischen Zustand einfangen.

### In der nutzenden Anwendung angeben

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

`bail` bricht die nachfolgenden Regeln ab, sobald eine vorhergehende Regel dieses Attributs fehlschlägt. Die Rollen werden getrennt: `required` für die Pflichtangabe, `acme_tracking` für das Format.

Verwenden Sie für öffentliche Namen einheitlich eindeutiges snake\_case wie `acme_tracking`. Wird derselbe Name für dieselbe Art von Erweiterung erneut registriert, überschreibt die spätere Registrierung die frühere. Existiert außerdem eine eingebaute Methode wie `validateRequired()`, wird diese direkt aufgerufen, sodass sich eine eingebaute Prüfung nicht durch eine gleichnamige Erweiterung ersetzen lässt.

## Fehlermeldungen der Nutzer erhalten

Die Meldung einer normalen String-Regel wird ungefähr in der folgenden Rangfolge ausgewählt. Sonderfälle wie Größenregeln werden hier nicht behandelt.

| Rang | Quelle der Meldung |
| - | - |
| 1 | Drittes Argument von `Validator::make()` oder `messages()` eines Form Requests |
| 2 | Übersetzung `validation.custom.Attributname.Regelname` der nutzenden Anwendung |
| 3 | Übersetzung zum Regelnamen wie `validation.acme_tracking` |
| 4 | Als drittes Argument von `Factory::extend()` registrierter Fallback-String |

Nachdem Laravel `:attribute` usw. ersetzt hat, wird der benutzerdefinierte Replacer aufgerufen. Der Replacer erhält fünf Argumente: die ausgewählte Meldung, den Attributnamen, den Regelnamen, das Parameter-Array und den Validator. Im obigen Beispiel wird das nicht verwendete fünfte Argument weggelassen.

> [!WARNING]
>
> Wenn ein Replacer die gesamte Meldung stets durch einen festen Text ersetzt, ignoriert er die Überschreibungen der Nutzer in `messages()` und in Übersetzungen. Das obige Beispiel ersetzt nur `:prefix` in der übergebenen `$message`.

Um in der Anwendung einen japanischen Standardtext festzulegen, fügen Sie dem zurückgegebenen Array der bestehenden `lang/ja/validation.php` den Eintrag `acme_tracking` hinzu. Das folgende Beispiel zeigt nur den hinzuzufügenden Eintrag und entfernt keine anderen eingebauten Meldungen.

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

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

Zur Vorbereitung von Übersetzungsdateien und zur Locale-Konfiguration siehe [Lokalisierung](https://kawax.biz/de/localization.md). Wenn Sie bei der Registrierung `__('...')` ausführen und das Ergebnis als Fallback speichern, verbleibt der String in der zu diesem Zeitpunkt aktiven Locale in der Factory. In Anwendungen, die die Locale wechseln, verwenden Sie die Übersetzung zum Zeitpunkt der Fehlererzeugung.

### Namespace-Übersetzungen des Pakets verwenden

Wenn Sie in `boot()` `loadTranslationsFrom(__DIR__.'/../lang', 'acme-courier')` aufrufen, können Sie z. B. auf `lang/ja/validation.php` des Pakets über `acme-courier::validation.tracking_code` zugreifen.

**Allein die Übergabe eines Übersetzungsschlüssels als drittes Argument von `extend()` führt jedoch nicht zu einer automatischen Übersetzung.** Die Factory speichert ihn lediglich als Fallback-String. Ersetzen Sie `extend()` und `replacer()` im obigen Hook durch die folgende Registrierung, wird die Übersetzung des Pakets nur dann abgerufen, wenn der Fallback ausgewählt wurde.

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

Legen Sie in der `validation.php` jeder Locale des Pakets einen Eintrag `tracking_code` an. Für den japanischen Text können Sie denselben Wortlaut wie beim obigen Eintrag `acme_tracking` verwenden.

Da hier im Replacer ein neuer Übersetzungsstring abgerufen wird, wird auch `:attribute` explizit an die Übersetzung übergeben. Laravels Standardersetzung wird auf diesen Rückgabewert nicht erneut angewendet. Wurde eine eigene Meldung des Nutzers ausgewählt, bleibt deren Wortlaut erhalten. Zum Überschreiben mitgelieferter Übersetzungen siehe [Paketübersetzungen überschreiben und aktualisieren](https://kawax.biz/de/advanced/package-translations.md).

## Ausführungsbedingungen von Implicit und nullable verstehen

`extendImplicit()` registriert eine Regel, die auch bei fehlenden Attributen oder leeren Strings als Kandidat für die Validierung gilt. Das bedeutet weder „immer fehlschlagen lassen“ noch „alle anderen Überspringbedingungen ignorieren“.

| Eingabe / Angabe | Normales `extend()` | `extendImplicit()` |
| - | - | - |
| Attribut fehlt | Wird nicht ausgeführt | Wird ausgeführt |
| Leerer String oder nur Leerzeichen | Wird nicht ausgeführt | Wird ausgeführt |
| `null` bei vorhandenem Attribut, ohne `nullable` | Wird ausgeführt | Wird ausgeführt |
| `null` bei vorhandenem Attribut, mit `nullable` | Wird nicht ausgeführt | Wird ausgeführt |
| Attribut fehlt, mit `sometimes` | Wird nicht ausgeführt | Wird nicht ausgeführt |

Die Tabelle vergleicht die Ausführungsbedingungen der einzelnen Regeln. Fälle, in denen die Verarbeitung vorher durch `bail`, den Ausschluss von Attributen, `stopOnFirstFailure()` usw. abbricht, sind gesondert zu betrachten. Bei HTTP-Eingaben können leere Strings durch Middleware in `null` umgewandelt werden, testen Sie leere Strings und `null` daher getrennt.

Für die hier gezeigte Formatregel genügt die Kombination aus normalem `extend()` und `required`. Wird eine bereits veröffentlichte Regel auf Implicit umgestellt, können Eingaben fehlschlagen, die vorher nicht geprüft wurden. Behandeln Sie dies daher als Kompatibilitätsänderung.

## Referenzen in Arrays mit Dependent-Regeln auflösen

Für Regeln, die den Namen eines anderen Felds als Parameter erhalten, steht `extendDependent()` zur Verfügung. Bei der Validierung von `items.*.confirmed_tracking` wird der Parameter `items.*.tracking` in das Feld derselben Zeile umgewandelt, etwa `items.0.tracking`. Mit normalem `extend()` findet diese Ersetzung nicht statt.

Das Folgende ist eine Übereinstimmungsregel, um diesen Mechanismus zu veranschaulichen. Für eine einfache Gleichheitsprüfung können Sie in echten Anwendungen die eingebaute Regel `same` verwenden. Verstehen Sie es als Beispiel für die Anwendung auf paketspezifische feldübergreifende Prüfungen.

Fügen Sie dies der Factory hinzu, die Sie im Hook erhalten.

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

Die Registrierung als Dependent garantiert weder die Existenz des referenzierten Felds noch dessen bereits validierten Zustand. `getData()` des vierten Arguments liefert die **zu validierenden Daten**, nicht nur die bereits validierten. Definieren Sie die nötigen Regeln sowohl für das referenzierende als auch für das referenzierte Feld.

Außerdem wird eine Regel durch Dependent nicht zu Implicit. Die Pflichtangabe bei fehlenden oder leeren Werten muss separat angegeben werden. Da die Implementierung Punkte in den Parametern maskiert und `*` ersetzt, sollten Sie APIs vermeiden, die beliebige Strings außer Feldnamen in die Parameter mischen.

## Von der Registrierung bis zur Validierung in der Anwendung testen

Erstellen Sie in einer Laravel-13-Anwendung, in der das Paket und der Service Provider geladen werden, die Datei `tests/Feature/TrackingRuleTest.php`. Zusätzlich zu Unit-Tests der Klasse prüfen Sie hier Registrierung und Meldungsverarbeitung über die tatsächliche 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
```

Der letzte Test verändert die gemeinsam genutzte Factory nicht, sondern erstellt eine neue Factory und prüft das Kopieren der registrierten Inhalte. Wenn Sie auch die Beispiele mit Namespace-Übersetzungen oder Dependent-Regeln bereitstellen, ergänzen Sie zusätzliche Tests in einer Umgebung, die die entsprechenden Registrierungen enthält.

## Prüfpunkte für die langfristige Wartung

* Regelname, Bedeutung und Anzahl der Parameter, Pflichtangabe und akzeptierte Werttypen als öffentliche API dokumentieren.
* Ungültige Parameter, fehlende Werte, leere Strings und `null` sowie Kombinationen mit `nullable`, `sometimes` und `bail` prüfen.
* `messages()` der Nutzer, Anzeigenamen von Attributen, Locale-Wechsel und das Überschreiben von Paketübersetzungen prüfen.
* Bei Dependent-Regeln mit mehreren Zeilen und verschachtelten Arrays sicherstellen, dass keine Werte anderer Zeilen referenziert werden.
* Die Registrierung sowohl bei noch nicht aufgelöster als auch bei bereits aufgelöster Factory prüfen und nicht erwarten, dass sie rückwirkend auf bereits erzeugte Validatoren angewendet wird.
* Namenskonflikte mit anderen Paketen vermeiden und denselben Namen nicht gemischt für normale, Implicit- und Dependent-Registrierungen verwenden.
* Beim Ausrollen von Änderungen auf langlebige Worker die Prozesse neu starten und keine Eingaben oder Benutzerinformationen in gemeinsam genutzten Handlern zurücklassen.

Zur Testumgebung für ein Paket allein siehe [Pakete testen](https://kawax.biz/de/advanced/package-testing.md), zum Umgang mit Kompatibilität siehe auch [Versionskompatibilität von Paketen verwalten](https://kawax.biz/de/advanced/package-versioning.md).

## Herangezogene Primärquellen

* [Offizielle Laravel-Dokumentation: Benutzerdefinierte Regeln und Implicit-Regeln](https://github.com/laravel/docs/blob/10add0d2dade04cacfab436dcc3a8df7ca004e74/validation.md#custom-validation-rules)
* [Offizielle Laravel-Dokumentation: Service Provider und Übersetzungen von Paketen](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: Ausführungsbedingungen, Registrierung von Erweiterungen, Auflösung von Klassen](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Validator.php)
* [ValidationRuleParser: Parsen von String-Regeln](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/ValidationRuleParser.php)
* [FormatsMessages: Rangfolge und Ersetzung von Meldungen](https://github.com/laravel/framework/blob/v13.40.0/src/Illuminate/Validation/Concerns/FormatsMessages.php)
* [ValidationServiceProvider: Singleton-Registrierung von 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)
* [Offizielle Tests: Weitergabe von Erweiterungen und Fallbacks aus der Factory](https://github.com/laravel/framework/blob/v13.40.0/tests/Validation/ValidationFactoryTest.php)
* [Offizielle Tests: String-, Klassen-, Implicit- und Dependent-Regeln](https://github.com/laravel/framework/blob/v13.40.0/tests/Validation/ValidationValidatorTest.php#L7372-L7471)


## Related topics

- [Eigene Validierungsregeln](https://kawax.biz/de/advanced/custom-validation-rules.md)
- [Mail-Transports in Paketen registrieren und testen](https://kawax.biz/de/advanced/package-mail-transports.md)
- [Hooks nach der Service-Auflösung in Paketen](https://kawax.biz/de/advanced/package-service-resolution.md)
- [Blade-Erweiterungen und Kompilierungscache in Paketen](https://kawax.biz/de/advanced/package-blade-extensions.md)
- [Dynamische Relationen in Paketen registrieren](https://kawax.biz/de/advanced/package-dynamic-relations.md)


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