Installation:
composer require shayanys/lara-reserve
php artisan migrate
Run migrations to create the reserves table.
Basic Setup:
Book, Room):
use ShayanYS\LaraReserve\Interfaces\ReservableInterface;
use ShayanYS\LaraReserve\Traits\Reservable;
class Book extends Model implements ReservableInterface
{
use Reservable;
}
User):
use ShayanYS\LaraReserve\Interfaces\CustomerInterface;
use ShayanYS\LaraReserve\Traits\Customer;
class User extends Authenticatable implements CustomerInterface
{
use Customer;
}
First Use Case:
Reserve a Book for a User:
$book = Book::find(1);
$user = User::find(1);
$reservation = $book->reserve($user, now()->addDays(3));
Check reservations:
$book->reservations; // Collection of Reserve models
Reserving Items:
// Basic reservation
$reservation = $reservable->reserve($customer, $endAt);
// With custom data (e.g., notes, status)
$reservation = $reservable->reserve($customer, $endAt, [
'notes' => 'For research',
'status' => 'pending',
]);
Checking Availability:
// Check if a model is available for a date range
if ($reservable->isAvailable($startAt, $endAt)) {
// Proceed with reservation
}
Canceling Reservations:
$reservation->cancel(); // Soft-deletes the reserve record
Querying Reservations:
// Get all reservations for a model
$reservations = $reservable->reservations;
// Filter active reservations
$activeReservations = $reservable->reservations()->active()->get();
Customer-Specific Queries:
// Get all reservations made by a customer
$user->reservations;
// Get all reservables reserved by a customer
$user->reservables;
Validation:
Use Laravel's validation to ensure end_at is after start_at:
$validator = Validator::make($request->all(), [
'end_at' => 'required|after:start_at',
]);
Events:
Listen for reservation events (e.g., Reserved, Cancelled) to trigger notifications or updates:
// In EventServiceProvider
protected $listen = [
\ShayanYS\LaraReserve\Events\Reserved::class => [
\App\Listeners\SendReservationConfirmation::class,
],
];
Policies: Restrict reservation actions using Laravel's authorization:
// app/Policies/BookPolicy.php
public function reserve(User $user, Book $book)
{
return $user->canReserve($book);
}
API Resources: Transform reservation data for APIs:
// app/Http/Resources/ReservationResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'reservable_type' => $this->reservable_type,
'reservable_id' => $this->reservable_id,
'customer_id' => $this->customer_id,
'start_at' => $this->start_at,
'end_at' => $this->end_at,
'status' => $this->status,
];
}
Overlapping Reservations:
if (!$reservable->isAvailable($startAt, $endAt)) {
throw new \Exception('Item already reserved for this period.');
}
Soft Deletes:
cancel() method:
public function cancel()
{
$this->delete(); // Hard delete
}
Morph Map Conflicts:
reservable_type or customer_type clashes with Laravel's default morph maps, explicitly define them:
class Book extends Model implements ReservableInterface
{
protected $morphClass = Book::class;
}
Time Zone Issues:
start_at and end_at timestamps are in the correct time zone. Use Carbon for consistency:
$startAt = Carbon::parse($request->start_at)->setTimezone('UTC');
Query Logs: Enable Laravel's query logging to debug reservation queries:
DB::enableQueryLog();
$reservations = $reservable->reservations;
dd(DB::getQueryLog());
Model Events: Use model events to trace reservation lifecycle:
$reservable->reservations()->created(function ($reserve) {
Log::info('New reservation created:', $reserve->toArray());
});
Custom Reserve Model:
Extend the Reserve model to add fields:
php artisan make:model ReserveExtension -m
Then update the migration and model.
Custom Statuses:
Add custom statuses to the reserves table:
// Migration
$table->string('status')->default('active');
// Usage
$reservation = $reservable->reserve($customer, $endAt, [
'status' => 'pending_approval',
]);
Reservation Rules:
Override the isAvailable method to enforce custom rules (e.g., max reservations per customer):
public function isAvailable($startAt, $endAt)
{
$overlap = $this->reservations()
->where('customer_id', $this->customer->id)
->where(function ($query) use ($startAt, $endAt) {
$query->where(function ($q) use ($startAt, $endAt) {
$q->where('start_at', '<', $endAt)
->where('end_at', '>', $startAt);
});
})->exists();
return !$overlap && parent::isAvailable($startAt, $endAt);
}
API Endpoints: Create dedicated API routes for reservations:
Route::post('/reservables/{reservable}/reserve', [ReservationController::class, 'store']);
Route::delete('/reservations/{reservation}/cancel', [ReservationController::class, 'cancel']);
How can I help you explore Laravel packages today?