@ianclemence Couple of things: I’d extract the week days to an enumeration:
enum DayOfWeek: int
{
case Monday = 1;
case Tuesday = 2;
// And so on...
}
I’m also not 100% sure on this one, but “class” may be a reserved keyword in PHP, i.e. you may not be able to use that as your relation name. I know I had issues with a relation called “match” in one of my applications when PHP 8.0 introduced the match expression.
In answer to the actual question, you need to check two conditions:
- Whether the time span is free
- If the time span is not free, whether the teacher, class, and stream are different.
I’d also pull the method for checking this out of your Lesson model and into a dedicated class:
class Availability
{
public function forLesson(Lesson $lesson): bool
{
$exists = Lesson::query()
->where(function (Builder $query) use ($lesson): void {
// Check if time span is completely free
$query->where('start_at', '>', $lesson->start_at);
$query->where('end_at', '<', $lesson->end_at);
})
->orWhere(function (Builder $query) use ($lesson): void {
// If time span is not completely free...
$query->where('start_at', '<=', $lesson->start_at);
$query->where('end_at', '>=', $lesson->end_at);
$query->where(function (Builder $query) use ($lesson): void {
// Check class, stream, and teacher are all free
$query->where('class_id', '=', $lesson->class_id);
$query->orWhere('stream_id', '=', $lesson->stream_id);
$query->orWhere('teacher_id', '=', $lesson->teacher_id);
});
})
->exists();
if ($exists) {
throw new UnavailableException();
}
}
}
(Disclaimer: code may be wrong. It was written off the cuff, but should illustrate indentation and usage.)
You’ll notice I just pass a Lesson model instance instead of individual parameters. The idea would be, you pass a Lesson model instance to see if the query is “satisfied”. You could do this in a transaction where you create your lessons:
DB::transaction(function () use ($request, $availability) {
$lesson = Lesson::query()->create($request->validated()); // For brevity
$availability->forLesson($lesson);
});
If there’s a conflict with availability, then an UnavailableException instance will be thrown, the transaction cancelled, and the model won’t be persisted to the database.
By passing an entire Lesson model instance, it also means you don‘t need to change the arguments if you want to change the conditions being checked.