You can absolutely create a custom validation rule for this scenario using Laravel’s custom Rule objects. The key is that when you need to validate based on multiple fields, you should pass those fields into your Rule, or use the Validator::extend approach for even more flexibility.
Approach: Custom Rule Object
- Create the Rule:
Run:
php artisan make:rule UniqueBooking
- Implement the Rule:
In app/Rules/UniqueBooking.php:
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\DB;
class UniqueBooking implements Rule
{
protected $startDate;
protected $length;
protected $userId;
protected $ignoreId;
public function __construct($startDate, $length, $userId, $ignoreId = null)
{
$this->startDate = $startDate;
$this->length = $length;
$this->userId = $userId;
$this->ignoreId = $ignoreId;
}
public function passes($attribute, $value)
{
$start = $this->startDate;
$end = date('Y-m-d H:i:s', strtotime($start . ' + ' . $this->length . ' minutes'));
$query = DB::table('pivot_table')
->where('user_id', $this->userId)
->where(function($q) use ($start, $end) {
$q->whereBetween('date_and_time', [$start, $end]);
});
if ($this->ignoreId) {
$query->where('id', '!=', $this->ignoreId);
}
return !$query->exists();
}
public function message()
{
return 'This booking conflicts with an existing record.';
}
}
- Use the Rule in Your Form Request or Validator:
use App\Rules\UniqueBooking;
public function rules()
{
return [
'type' => ['required'],
'start_date' => ['required', 'date'],
'length' => ['required', 'integer'],
'user_id' => ['required', 'exists:users,id'],
'id' => ['sometimes'], // for updates
// Apply the custom rule to any field, or use 'start_date' as the trigger
'start_date' => [
'required',
'date',
new UniqueBooking(
$this->input('start_date'),
$this->input('length'),
$this->input('user_id'),
$this->input('id') // pass null if creating
),
],
];
}
Notes:
- Adjust
"pivot_table"to your actual table name. - Adjust the date calculation as needed for your use case.
- You can trigger the rule on any field, but
start_dateis a common choice.
Summary:
This is best done as a custom Rule, and you can pass all necessary fields into the Rule’s constructor for multi-field validation. This keeps your validation logic clean and reusable.