Conditional validation rules in Laravel
Laravel lets you conditionally add validation rules using the Validator::sometimes
instance method. That works well, but there are times when you don’t have access to the validator instance, and don’t want to manually construct one.
Let’s take a look at how you can conditionally apply validation rules in those situations.
The problem
Imagine we have a store
method, which creates a new customer order.
// use Illuminate\Http\Request;
public function store(Request $request)
{
// Validation goes here...
$order = Order::create($validated);
}
Before saving the order, we need to validate the delivery details. These are the rules we need to enforce:
- If the delivery_provider is “fedex”, the
delivery_service
must be “next_day” or “two_day”. - If the delivery_provider is “ups”, the
delivery_service
must be “express” or “standard”. - If the delivery_provider is “usps”, the
delivery_service
must be “two_day” or “someday”.
Define a map of validation rules
You may have noticed that the above rules constitute a map, where the key is the delivery provider, and the value is an array of services offered by that provider:
$rules = [
'fedex' => ['next_day', 'two_day'],
'ups' => ['express', 'standard'],
'usps' => ['two_day', 'someday'],
];
From here, it’s a short step to convert the values in our map to Laravel-friendly validation rules:
$rules = [
'fedex' => ['required', 'in:next_day,two_day'],
'ups' => ['required', 'in:express,standard'],
'usps' => ['required', 'in:two_day,someday'],
];
Determine which rules to apply
The next step is figuring out which set of rules to apply. That’s easy enough; we just need to retrieve the chosen delivery provider:
$provider = $request->get('delivery_provider');
$serviceRules = array_key_exists($provider, $rules)
? $rules[$provider]
: [];
A working solution
At this point, we have everything we need to validate the delivery service. Here’s our updated store
method:
public function store(Request $request)
{
$rules = [
'fedex' => ['required', 'in:next_day,two_day'],
'ups' => ['required', 'in:express,standard'],
'usps' => ['required', 'in:two_day,someday'],
];
$provider = $request->get('delivery_provider');
$serviceRules = array_key_exists($provider, $rules)
? $rules[$provider]
: [];
$validated = $request->validate([
'delivery_service' => $serviceRules,
]);
$order = Order::create($validated);
}
A neater solution
That all works, but it can be a little verbose. To keep things neat, I created Apposite. Here’s our store method, refactored to use Apposite’s ApplyMap
validation rule:
// use Monooso\Apposite\ApplyMap;
public function store(Request $request)
{
$serviceRules = new ApplyMap(
$request->get('delivery_provider'),
[
'fedex' => ['required', 'in:next_day,two_day'],
'ups' => ['required', 'in:express,standard'],
'usps' => ['required', 'in:two_day,someday'],
]
);
$validated = $request->validate([
'delivery_service' => [$serviceRules],
]);
$order = Order::create($validated);
}
Apposite also includes ApplyWhen
and ApplyUnless
, which apply validation rules according to a boolean conditional, and a few other useful enhancements.
Check out the GitHub repository for full installation and usage instructions.
Sign up for my newsletter
A monthly round-up of blog posts, projects, and internet oddments.