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!
if i want pass some other value to the custom rule
You can use the construct function. Example: