In e-commerce projecten waarbij klanten producten met specifieke configuratie-opties bestellen loopt validatiecode al snel uit de hand. Het probleem is dat de code steeds ingewikkelder wordt en zich gaat herhalen over meerdere controllers. Een klant kan bijvoorbeeld alleen bepaalde kleuren kiezen als het product een specifieke maat heeft, en sommige combinaties zijn per seizoen uitgesloten. Laravel's standaard validatieregels zijn dan niet toereikend voor deze business logic, en dat is het moment om dieper te graven in custom validation rules.
Custom validation rules maken
Laravel biedt verschillende manieren om custom validation rules te maken, maar ik verkies de Rule klasse benadering voor complexere validatie. Door gebruik te maken van php artisan make:rule ValidateProductConfiguration creëer ik een dedicated klasse die al mijn business logic afhandelt. Dit houdt mijn form requests schoon en maakt de validatielogica herbruikbaar.
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use App\Models\Product;
use App\Models\ProductConfiguration;
class ValidateProductConfiguration implements Rule
{
protected $product;
protected $message;
public function __construct($productId)
{
$this->product = Product::find($productId);
}
public function passes($attribute, $value)
{
if (!$this->product) {
$this->message = 'Het geselecteerde product bestaat niet.';
return false;
}
$configuration = json_decode($value, true);
if (!$this->isValidColorSizeCombination($configuration)) {
$this->message = 'Deze kleur is niet beschikbaar in de geselecteerde maat.';
return false;
}
if (!$this->isSeasonallyAvailable($configuration)) {
$this->message = 'Deze configuratie is momenteel niet beschikbaar.';
return false;
}
return true;
}
public function message()
{
return $this->message;
}
private function isValidColorSizeCombination($configuration)
{
$availableConfigs = ProductConfiguration::where('product_id', $this->product->id)
->where('color', $configuration['color'])
->where('size', $configuration['size'])
->exists();
return $availableConfigs;
}
private function isSeasonallyAvailable($configuration)
{
$currentMonth = now()->month;
$summerColors = ['yellow', 'orange', 'bright-blue'];
if (in_array($currentMonth, [12, 1, 2]) && in_array($configuration['color'], $summerColors)) {
return false;
}
return true;
}
}
Deze aanpak geeft mij volledige controle over de validatielogica en houdt alle gerelateerde business rules bij elkaar. Door de foutmeldingen dynamisch te maken gebaseerd op de specifieke validatiefout, krijgen gebruikers altijd relevante feedback.
Integratie met form requests
Form requests worden veel krachtiger wanneer je ze combineert met custom validation rules. Voor hetzelfde e-commerce project maak ik een dedicated form request die al mijn productconfiguratie validatie afhandelt. Hierdoor blijft mijn controller method simpel en hebben alle validatieregels een duidelijke plek.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Rules\ValidateProductConfiguration;
use App\Rules\ValidateInventoryAvailability;
class StoreOrderRequest extends FormRequest
{
public function authorize()
{
return auth()->check();
}
public function rules()
{
return [
'product_id' => ['required', 'exists:products,id'],
'quantity' => ['required', 'integer', 'min:1', 'max:10'],
'configuration' => [
'required',
'json',
new ValidateProductConfiguration($this->product_id),
new ValidateInventoryAvailability($this->product_id, $this->quantity)
],
'delivery_date' => ['required', 'date', 'after:tomorrow'],
'customer_notes' => ['nullable', 'string', 'max:500']
];
}
public function messages()
{
return [
'delivery_date.after' => 'Leverdatum moet minimaal twee dagen na vandaag zijn.',
'quantity.max' => 'Je kunt maximaal 10 stuks per bestelling bestellen.',
];
}
protected function prepareForValidation()
{
if ($this->has('configuration') && is_array($this->configuration)) {
$this->merge([
'configuration' => json_encode($this->configuration)
]);
}
}
}
Door prepareForValidation() te gebruiken kan ik data transformeren voordat de validatie plaatsvindt. Dit is handig wanneer frontend frameworks zoals Vue.js arrays sturen, maar ik ze als JSON strings wil valideren en opslaan.
Conditional validation met custom rules
Een van de krachtigste aspecten van custom validation rules is de mogelijkheid om conditionele validatie te implementeren. In hetzelfde project had ik de uitdaging dat enterprise klanten andere validatieregels hebben dan gewone consumenten. Enterprise klanten kunnen bijvoorbeeld bulk orders plaatsen en hebben toegang tot speciale producten.
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use App\Models\User;
class ValidateEnterpriseOrder implements Rule
{
protected $user;
protected $message;
public function __construct()
{
$this->user = auth()->user();
}
public function passes($attribute, $value)
{
$orderData = json_decode($value, true);
if ($this->user->account_type !== 'enterprise') {
if ($orderData['quantity'] > 50) {
$this->message = 'Bulk orders zijn alleen beschikbaar voor enterprise accounts.';
return false;
}
if (isset($orderData['enterprise_discount_code'])) {
$this->message = 'Enterprise kortingscodes zijn niet geldig voor dit account type.';
return false;
}
}
if ($this->user->account_type === 'enterprise') {
if (!$this->isValidEnterpriseConfiguration($orderData)) {
$this->message = 'Deze enterprise configuratie is niet geldig voor jouw account.';
return false;
}
}
return true;
}
public function message()
{
return $this->message;
}
private function isValidEnterpriseConfiguration($orderData)
{
$allowedProducts = $this->user->enterprise_profile->allowed_products;
if (!in_array($orderData['product_id'], $allowedProducts)) {
return false;
}
return true;
}
}
Database queries binnen validation rules kunnen een performance impact hebben, dus ik cache vaak gebruikersgegevens of gebruik eager loading waar mogelijk. Voor het enterprise voorbeeld hierboven laad ik de enterprise profile data vooraf in de constructor of gebruik ik Laravel's cache mechanisme.
Testing van custom validation rules
Het testen van custom validation rules is cruciaal omdat ze vaak complexe business logic bevatten. Ik gebruik Laravel's ingebouwde testing tools om verschillende scenarios te controleren. Voor elke custom rule schrijf ik unit tests die alle mogelijke paden door de validatielogica controleren.
<?php
namespace Tests\Unit;
use Tests\TestCase;
use App\Rules\ValidateProductConfiguration;
use App\Models\Product;
use App\Models\ProductConfiguration;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ValidateProductConfigurationTest extends TestCase
{
use RefreshDatabase;
public function test_passes_with_valid_configuration()
{
$product = Product::factory()->create();
ProductConfiguration::factory()->create([
'product_id' => $product->id,
'color' => 'blue',
'size' => 'large'
]);
$rule = new ValidateProductConfiguration($product->id);
$configuration = json_encode(['color' => 'blue', 'size' => 'large']);
$this->assertTrue($rule->passes('configuration', $configuration));
}
public function test_fails_with_invalid_color_size_combination()
{
$product = Product::factory()->create();
$rule = new ValidateProductConfiguration($product->id);
$configuration = json_encode(['color' => 'red', 'size' => 'small']);
$this->assertFalse($rule->passes('configuration', $configuration));
$this->assertStringContainsString('Deze kleur is niet beschikbaar', $rule->message());
}
public function test_fails_with_seasonal_restrictions()
{
$this->travelTo(now()->month(1)); // January
$product = Product::factory()->create();
ProductConfiguration::factory()->create([
'product_id' => $product->id,
'color' => 'yellow',
'size' => 'medium'
]);
$rule = new ValidateProductConfiguration($product->id);
$configuration = json_encode(['color' => 'yellow', 'size' => 'medium']);
$this->assertFalse($rule->passes('configuration', $configuration));
$this->assertStringContainsString('momenteel niet beschikbaar', $rule->message());
}
}
Deze tests geven mij vertrouwen dat mijn validatielogica correct werkt onder verschillende omstandigheden. Vooral de seizoensgebonden logica test ik grondig omdat dit tijdgevoelige business logic is die gemakkelijk over het hoofd wordt gezien.
Custom validation rules hebben mijn Laravel applicaties veel robuuster gemaakt. Ze houden mijn controllers schoon, maken complexe business logic herbruikbaar en zijn eenvoudig te testen. Het grootste voordeel is dat alle gerelateerde validatielogica op één plek staat, wat onderhoud en debugging veel eenvoudiger maakt. Sindsdien gebruik ik ze in bijna elk project waar de standaard Laravel validatieregels te kort schieten.