Laravel Custom Validation Rule with Date Period and Multiple Fields

I've recently read a Laracasts forum post with a question about validating a date period. Decided to write this article with a possible solution.

The situation, as described by the author:

I want to add validation to dates. I am generating a salary for workers but If the salary is already created and the admin tries to create the salary between already created dates then I want to show him the message "Please select different dates" or something else.

Suppose the salary between 1-10-2022 to 1-15-2022 is already created then I don't want to create another salary between these dates.

In other words, how to validate a pair of fields with checking their certain rule in the database, something like select * from salaries where user_id = ? and date_from < ? and date_to > ?.

Let's say that we have a form, or an API endpoint that accepts these parameters in request:

  • user_id
  • date_from
  • date_to
  • amount

If we transform that into Laravel's language, we have this:

1// app/Http/Controllers/SalaryController.php:
2public function store(StoreSalaryRequest $request)
3{
4 Salary::create($request->validated());
5 
6 return redirect()->route('salaries.index');
7}
8 
9// app/Http/Requests/StoreSalaryRequest.php:
10class StoreSalaryRequest extends FormRequest
11{
12 public function rules()
13 {
14 return [
15 // ... BIG QUESTION: WHAT DO WE PUT HERE?
16 ];
17 }
18}

The problem is that we don't have such validation rule out of the box in the list of default Laravel validation rules. So we need to create a custom rule here.

I will give you an example with the new invokable syntax of custom validation rules. For older way (which still works), please visit the Laravel 8 docs here.

First, we create our rule:

1php artisan make:rule UniqueSalaryRule --invokable

Then we fill in the __invoke method with the database call, to check if the salary for the user_id and time period exists.

1use App\Models\Salary;
2use Illuminate\Contracts\Validation\DataAwareRule;
3use Illuminate\Contracts\Validation\InvokableRule;
4 
5class UniqueSalaryRule implements DataAwareRule, InvokableRule
6{
7 protected $data = [];
8 
9 public function __invoke($attribute, $value, $fail)
10 {
11 if (Salary::where('user_id', $this->data['user_id'])
12 ->where('date_from', '<=', $value)
13 ->Where('date_to', '>=', $this->data['date_to'])
14 ->exists()) {
15 $fail('Salary for this period already exists');
16 }
17 }
18 
19 public function setData($data)
20 {
21 $this->data = $data;
22 
23 return $this;
24 }
25}

To access the other attributes of the $request, we need to implement DataAwareRule interface and have setData method, read the docs section called Accessing Additional Data in the official docs.

Finally, we activate that validation rule in our FormRequest class:

1class StoreSalaryRequest extends FormRequest
2{
3 public function rules()
4 {
5 return [
6 'user_id' => 'required',
7 'date_from' => ['required', new UniqueSalaryRule()],
8 'date_to' => 'required',
9 'amount' => 'required',
10 ];
11 }
12}

And that's it: if the date_from and date_to combination is invalid, it will return the validation error on the date_from field.

Again, this is just one way of doing it: you can create a custom validation rule with another syntax, or even validate the data directly in the Controller instead of Form Request and validation rules. The choice is yours!

No comments or questions yet...

Like our articles?

Become a Premium Member for $129/year or $29/month

Recent Premium Tutorials