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

# Custom validatieregels

> Hoe je de validatiefunctionaliteit van Laravel uitbreidt met rule-objecten en closures, tot en met de interne implementatie van het framework.

## Wat zijn custom validatieregels?

Laravel biedt een rijke set ingebouwde validatieregels, maar soms heb je applicatiespecifieke validatielogica nodig. Met custom validatieregels definieer je herbruikbare validatielogica als klasse of closure, die je op dezelfde manier gebruikt als de standaardregels.

Er zijn twee manieren om custom regels te definiëren:

* **Rule-objecten** — goed herbruikbaar en makkelijk te testen
* **Closures** — geschikt voor simpele regels die je maar één keer gebruikt

## Rule-objecten

### Een ruleklasse genereren

Genereer een nieuwe ruleklasse met het Artisan-commando `make:rule`. De gegenereerde klasse wordt in de map `app/Rules` geplaatst.

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

### De ValidationRule-interface implementeren

Implementeer de `validate`-methode in de gegenereerde klasse. Deze methode roept de `$fail`-closure aan wanneer de validatie faalt.

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

namespace App\Rules;

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

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

In de string die je aan de `$fail`-closure doorgeeft, kun je de placeholder `:attribute` gebruiken. Laravel vervangt die door de veldnaam.

### Een rule-object toepassen

Geef een instantie van het rule-object door in de validatie-array.

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

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

Dit werkt op dezelfde manier in de `rules()`-methode van een form request.

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

### Foutmeldingen met vertaalsleutels

In plaats van de foutmelding hard te coderen, kun je ook een vertaalsleutel gebruiken.

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

Voeg de melding toe aan het vertaalbestand `lang/nl/validation.php`.

```php theme={null}
return [
    'uppercase' => ':attribute moet in hoofdletters worden ingevoerd.',
    // ...
];
```

### Meerdere foutmeldingen toevoegen

Om meerdere fouten voor één veld te rapporteren, roep je `$fail` meerdere keren aan.

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

## Regels op basis van closures

Simpele regels die je maar op één plek in je applicatie gebruikt, kun je zonder klasse definiëren met een closure.

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

Closureregels zijn handig omdat je ze inline definieert, maar hergebruik en losstaand testen zijn lastig. Logica die je op meerdere plekken gebruikt, kun je daarom beter in een rule-object onderbrengen.

## Impliciete regels (ook uitvoeren bij lege waarden)

Standaard wordt een custom regel niet uitgevoerd wanneer het veld leeg is of ontbreekt. Wil je de regel ook bij lege waarden laten draaien, genereer de klasse dan met de optie `--implicit`.

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

De gegenereerde klasse implementeert de interface `ImplicitRule`. Deze interface heeft zelf geen extra methodes en werkt als signaal richting 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` geeft alleen aan Laravel door dat "het attribuut verplicht is". Of de validatie bij een lege waarde daadwerkelijk faalt, hangt af van de implementatie van je `validate`-methode.
</Warning>

## Toegang tot data

### DataAwareRule — toegang tot alle formulierdata

Wil je valideren op basis van de waarden van andere velden, dan implementeer je de interface `DataAwareRule`. De `setData`-methode wordt automatisch aangeroepen voordat de validatie begint.

```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
{
    /**
     * Alle te valideren data
     *
     * @var array<string, mixed>
     */
    protected array $data = [];

    public function __construct(
        protected string $table,
        protected string $column = 'value',
    ) {}

    /**
     * De validatiedata zetten
     *
     * @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.');
        }
    }
}
```

Voorbeeldgebruik:

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

### ValidatorAwareRule — toegang tot de validatorinstantie

Om toegang te krijgen tot alle informatie van de validator (gefaalde regels, custom meldingen, enzovoort) implementeer je de 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
    {
        // Overslaan als een ander veld de validatie al niet heeft gehaald
        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.');
        }
    }
}
```

## Praktische use cases

### Controleren op Japanse tekens

Een regel die het tekentype valideert.

```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
    {
        // Alleen hiragana, katakana, kanji en het verlengingsteken toegestaan
        if (! preg_match('/^[\p{Hiragana}\p{Katakana}\p{Han}ー\s]+$/u', $value)) {
            $fail(':attribute moet in het Japans (hiragana, katakana of kanji) worden ingevoerd.');
        }
    }
}
```

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

### Telefoonnummerformaat valideren

Een regel die het Japanse telefoonnummerformaat valideert.

```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
    {
        // Met en zonder koppelteken, ook het internationale formaat is toegestaan
        $normalized = preg_replace('/[\s\-\(\)]/', '', $value);

        $patterns = [
            '/^0\d{9,10}$/',        // Gangbare vaste en mobiele nummers
            '/^\+81\d{9,10}$/',     // Internationaal formaat
        ];

        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $normalized)) {
                return;
            }
        }

        $fail(':attribute moet een geldig telefoonnummerformaat hebben.');
    }
}
```

### Uniciteit binnen een tenant

Een uniciteitsbeperking binnen de scope van een tenant, gebruikelijk in multitenant-applicaties.

<Steps>
  <Step title="De ruleklasse aanmaken">
    ```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(':attribute is al in gebruik.');
            }
        }
    }
    ```
  </Step>

  <Step title="Gebruiken in een 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="Bij updates het ID uitsluiten">
    Werk je een bestaand record bij, dan sluit je het eigen ID uit bij de duplicaatcontrole.

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

## Regels registreren in een service provider

### Een regel toevoegen met Validator::extend()

Met `Validator::extend()` kun je een custom regel als string (`'rule_name'`) beschikbaar maken. Je registreert de regel in de `boot()`-methode van 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, ':attribute moet in het Japans worden ingevoerd.');
        });
    }
}
```

Regels die je registreert met `Validator::extend()` kun je als string opgeven.

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

<Warning>
  `Validator::extend()` is een oudere registratiemethode dan rule-objecten. Voor nieuwe ontwikkeling raden we rule-objecten aan die de interface `ValidationRule` implementeren.
</Warning>

### Als statische methode toevoegen aan de Rule-klasse

Door een macro toe te voegen aan de `Rule`-facade kun je een vloeiende API (fluent API) bieden zoals `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}
// Voorbeeldgebruik
$request->validate([
    'name' => ['required', Rule::tenantUnique('projects', 'name')],
]);
```

## Details van de interne implementatie

Laten we kijken hoe `Illuminate\Validation\Validator` custom regels aanroept.

Binnen de validator verwerkt `validateAttribute()` elk veld. Implementeert de regel de interface `ValidationRule`, dan wordt de methode `validateUsingCustomRule()` aangeroepen.

```php theme={null}
// Vereenvoudigde versie van Illuminate\Validation\Validator::validateUsingCustomRule()
protected function validateUsingCustomRule($attribute, $value, $rule)
{
    // Bij een DataAwareRule wordt alle data geïnjecteerd
    if ($rule instanceof DataAwareRule) {
        $rule->setData($this->getData());
    }

    // Bij een ValidatorAwareRule wordt de validator zelf geïnjecteerd
    if ($rule instanceof ValidatorAwareRule) {
        $rule->setValidator($this);
    }

    // validate() aanroepen
    $rule->validate($attribute, $value, function ($message, $translate = false) use ($attribute, $rule) {
        // De $fail-closure — voegt een foutmelding toe
        $this->errors()->add($attribute, $this->makeReplacements(
            $message,
            $attribute,
            get_class($rule),
            [], // Extra placeholder-vervangingen (bijv. ['min' => '8'])
        ));
    });
}
```

De afhandeling van `ImplicitRule` wordt bepaald door de methode `isImplicit()`, die de regel ook uitvoert wanneer de waarde leeg is.

```php theme={null}
// Bepaalt of de regel bij een lege waarde wordt uitgevoerd
protected function isImplicit($rule): bool
{
    return $rule instanceof ImplicitRule
        || in_array($rule, $this->implicitRules);
}
```

<Tip>
  Je kunt `DataAwareRule` en `ValidatorAwareRule` ook tegelijk implementeren. In een klasse met beide interfaces vinden beide injecties plaats.
</Tip>

## Gerelateerde pagina's

<Card title="Validatie (introductie)" icon="shield-check" href="/nl/validation">
  Bekijk de standaardmanieren van valideren in controllers en form requests.
</Card>


## Related topics

- [Upgraden van Laravel 9 naar 10](/nl/blog/upgrade-9-to-10.md)
- [Custom agents](/nl/packages/laravel-copilot-sdk/custom-agents.md)
- [Validatie](/nl/validation.md)
- [HTTP-tests](/nl/http-tests.md)
- [Custom providers](/nl/packages/laravel-copilot-sdk/custom-providers.md)
