Laravel手术剧场时段防重复预约验证实现问询
Hey there! As a fellow Laravel developer, let's walk through how to add that duplicate booking validation you need. Since you're new to the framework, I'll break this down into simple, actionable steps so you can follow along easily.
1. Add a Database-Level Unique Constraint (Recommended)
First, let's add a safety net at the database level to prevent duplicates even if our code logic has gaps. This ensures no conflicting bookings can slip through, no matter what.
If you already have the booking_forms table, create a new migration to add a composite unique index:
php artisan make:migration add_unique_constraint_to_booking_forms
Open the new migration file and update the up method:
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up() { Schema::table('booking_forms', function (Blueprint $table) { // Adjust fields based on your actual slot definition // If using fixed slots (e.g., morning/afternoon), add the slot field here // For time ranges, we'll handle overlap logic in code, but this baseline prevents exact duplicates $table->unique(['date', 'TheatreRoomID']); }); } public function down() { Schema::table('booking_forms', function (Blueprint $table) { $table->dropUnique(['date', 'TheatreRoomID']); }); } };
Run the migration to apply the constraint:
php artisan migrate
2. Add Validation Logic (With Custom Rule for Time Overlaps)
If your bookings use custom time ranges (not fixed slots), we'll need a custom validation rule to check for overlapping bookings. If you use fixed slots (like "Morning" or "Afternoon"), skip to the simplified rule below.
Create a Custom Validation Rule
Run this command to generate a new rule:
php artisan make:rule UniqueTheatreSlot
Open app/Rules/UniqueTheatreSlot.php and update it with this logic:
namespace App\Rules; use Illuminate\Contracts\Validation\Rule; use App\Models\BookingForm; class UniqueTheatreSlot implements Rule { protected $theatreId; protected $date; protected $startTime; protected $endTime; protected $bookingId; // For edit scenarios to exclude the current booking public function __construct($theatreId, $date, $startTime, $endTime, $bookingId = null) { $this->theatreId = $theatreId; $this->date = $date; $this->startTime = $startTime; $this->endTime = $endTime; $this->bookingId = $bookingId; } public function passes($attribute, $value) { $query = BookingForm::where('TheatreRoomID', $this->theatreId) ->where('date', $this->date) // Check for overlapping time ranges: // Current booking starts before an existing booking ends, // and current booking ends after an existing booking starts ->where(function ($query) { $query->where('start_time', '<', $this->endTime) ->where('end_time', '>', $this->startTime); }); // Exclude the current booking if we're editing if ($this->bookingId) { $query->where('id', '!=', $this->bookingId); } // Return true if no overlapping bookings exist return !$query->exists(); } public function message() { return 'This theatre slot is already booked for the selected date and time range.'; } }
Use the Rule in Your Controller
In your BookingFormController's store method (and update method if you have one), add the validation:
For Creating Bookings:
public function store(Request $request) { $validated = $request->validate([ 'date' => 'required|date', 'TheatreRoomID' => 'required|exists:theatre_rooms,id', // Ensure the theatre exists in your database 'start_time' => 'required|date_format:H:i', 'end_time' => [ 'required', 'date_format:H:i', 'after:start_time', // Ensure end time is after start time new \App\Rules\UniqueTheatreSlot( $request->TheatreRoomID, $request->date, $request->start_time, $request->end_time ) ] // Add other fields from your form here ]); BookingForm::create($validated); return redirect()->route('bookings.index')->with('success', 'Booking created successfully!'); }
For Editing Bookings:
public function update(Request $request, BookingForm $booking) { $validated = $request->validate([ 'date' => 'required|date', 'TheatreRoomID' => 'required|exists:theatre_rooms,id', 'start_time' => 'required|date_format:H:i', 'end_time' => [ 'required', 'date_format:H:i', 'after:start_time', new \App\Rules\UniqueTheatreSlot( $request->TheatreRoomID, $request->date, $request->start_time, $request->end_time, $booking->id // Pass the current booking ID to exclude it from the check ) ] // Add other fields here ]); $booking->update($validated); return redirect()->route('bookings.index')->with('success', 'Booking updated successfully!'); }
Simplified Rule for Fixed Slots
If you use fixed slots (e.g., a slot field with values like "morning", "afternoon"), you don't need a custom rule. Use Laravel's built-in unique validation with additional conditions:
$validated = $request->validate([ 'date' => 'required|date', 'TheatreRoomID' => 'required|exists:theatre_rooms,id', 'slot' => [ 'required', 'unique:booking_forms,slot,NULL,id,TheatreRoomID,'.$request->TheatreRoomID.',date,'.$request->date ] ]);
3. Show Error Messages in Your View
Make sure your Create.blade.php view displays validation errors. Add this somewhere near the top of your form:
@if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif
And for each input, add error styling if applicable:
<div class="form-group"> <label for="date">Date</label> <input type="date" name="date" id="date" class="form-control @error('date') is-invalid @enderror" value="{{ old('date') }}"> @error('date') <div class="invalid-feedback">{{ $message }}</div> @enderror </div>
Final Tips
- Test Edge Cases: Make sure to test scenarios like back-to-back bookings (e.g., one ends at 10:00, another starts at 10:00) and adjust the overlap logic in the custom rule if needed based on your business requirements.
- Handle Database Exceptions: Even with the unique constraint, add a try/catch block around the
create/updatemethods to catch any unexpected errors and display a user-friendly message.
内容的提问来源于stack exchange,提问作者Andrew Glenny

