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:

// app/Http/Controllers/SalaryController.php:
public function store(StoreSalaryRequest $request)
{
Salary::create($request->validated());
 
return redirect()->route('salaries.index');
}
 
// app/Http/Requests/StoreSalaryRequest.php:
class StoreSalaryRequest extends FormRequest
{
public function rules()
{
return [
// ... BIG QUESTION: WHAT DO WE PUT HERE?
];
}
}

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:

php 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.

use App\Models\Salary;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\InvokableRule;
 
class UniqueSalaryRule implements DataAwareRule, InvokableRule
{
protected $data = [];
 
public function __invoke($attribute, $value, $fail)
{
if (Salary::where('user_id', $this->data['user_id'])
->where('date_from', '<=', $value)
->Where('date_to', '>=', $this->data['date_to'])
->exists()) {
$fail('Salary for this period already exists');
}
}
 
public function setData($data)
{
$this->data = $data;
 
return $this;
}
}

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:

class StoreSalaryRequest extends FormRequest
{
public function rules()
{
return [
'user_id' => 'required',
'date_from' => ['required', new UniqueSalaryRule()],
'date_to' => 'required',
'amount' => 'required',
];
}
}

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!

avatar

if i want pass some other value to the custom rule

👍 2
avatar

You can use the construct function. Example:

class UniqueSalaryRule  implements ValidationRule
		{
				private $foo;
				
				public function __construct($foo)
				{
				$this->foo = $foo;
				}
				
				// ...
		}


class StoreSalaryRequest extends FormRequest
		{
				public function rules()
				{
				$foo = 'random value';
						return [
								'user_id' => 'required',
								'date_from' => ['required', new UniqueSalaryRule($foo)],
								'date_to' => 'required',
								'amount' => 'required',
						];
				}
		}

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1057 lessons, total 42 h 44 min)
  • 79 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials