masterix21/laravel-bookings

Add bookings ability to any Eloquent model

Fund package maintenance!
lucalongo

Installs: 6

Dependents: 0

Suggesters: 0

Security: 0

Stars: 2

Watchers: 1

Forks: 1

Open Issues: 0

pkg:composer/masterix21/laravel-bookings

1.2.2 2025-10-22 14:39 UTC

This package is auto-updated.

Last update: 2025-10-22 14:41:45 UTC


README

Latest Version on Packagist GitHub Actions Workflow Status Total Downloads

A comprehensive Laravel package that adds powerful booking functionality to any Eloquent model. Transform your models into bookable resources with advanced features like time-based reservations, capacity management, planning constraints, overlap detection, and event-driven architecture.

Features

  • ๐Ÿš€ Make any Eloquent model bookable with simple traits
  • ๐Ÿ“… Advanced time period management using Spatie Period library
  • ๐Ÿข Resource capacity control with configurable limits
  • ๐Ÿ“‹ Planning constraints with weekday and time restrictions
  • ๐Ÿ” Overlap detection and conflict prevention
  • ๐ŸŽฏ Event-driven architecture for audit trails and integrations
  • ๐Ÿ—‚๏ธ Polymorphic relationships for flexible booker and resource types
  • ๐Ÿ”— Related bookings with parent-child relationships (v1.2.0+)
  • ๐Ÿงช Well tested with comprehensive test suite
  • โšก Performance optimized with efficient database queries
  • ๐Ÿ›ก๏ธ Transaction safety with automatic rollback on failures
  • ๐Ÿ”„ Automatic synchronization with model events
  • ๐Ÿ”— Planning source pattern for business logic separation

Installation

Install the package via Composer:

composer require masterix21/laravel-bookings

Publish and run the migrations:

php artisan vendor:publish --tag="bookings-migrations"
php artisan migrate

Optionally, publish the config file:

php artisan vendor:publish --tag="bookings-config"

Quick Start

1. Make a Model Bookable

Add the IsBookable trait to any model you want to make bookable:

use Masterix21\Bookings\Models\Concerns\Bookable;
use Masterix21\Bookings\Models\Concerns\IsBookable;

class Room extends Model implements Bookable
{
    use IsBookable;

    protected $fillable = ['name', 'capacity'];
}

2. Create a Bookable Resource

use Masterix21\Bookings\Models\BookableResource;

// Create a bookable resource for your room
$room = Room::create(['name' => 'Deluxe Suite', 'capacity' => 4]);

$bookableResource = BookableResource::create([
    'resource_type' => Room::class,
    'resource_id' => $room->id,
    'max' => 1, // Maximum concurrent bookings
    'size' => 4, // Resource capacity
    'is_bookable' => true,
    'is_visible' => true,
]);

3. Make a Booking

use Masterix21\Bookings\Actions\BookResource;
use Spatie\Period\Period;
use Spatie\Period\PeriodCollection;

// Create booking periods
$periods = PeriodCollection::make([
    Period::make('2024-12-25', '2024-12-27'), // 2 nights
]);

// Book the resource
$booking = (new BookResource())->run(
    periods: $periods,
    bookableResource: $bookableResource,
    booker: auth()->user(), // The user making the booking
    label: 'Christmas Holiday',
    note: 'Special dietary requirements',
    meta: ['guests' => 2, 'payment_method' => 'credit_card']
);

4. Hook into the Booking Lifecycle (Optional)

Add custom logic before or after booking is saved:

use Masterix21\Bookings\Actions\BookResource;
use Masterix21\Bookings\Models\Booking;

$booking = (new BookResource())
    ->onBookingSaving(function (Booking $booking) {
        // Executed before $booking->save()
        $booking->tenant_id = auth()->user()->tenant_id;
    })
    ->onBookingSaved(function (Booking $booking) {
        // Executed after $booking->save()
        Log::info("Booking {$booking->code} created");
    })
    ->run(
        periods: $periods,
        bookableResource: $bookableResource,
        booker: auth()->user(),
    );

For complete documentation, see docs/actions.md

Core Concepts

BookableResource

The central entity that represents a bookable item. It's linked to your actual model (Room, Car, etc.) via polymorphic relationships.

Booking

Represents a reservation with metadata, booker information, and associated time periods.

BookedPeriod

Individual time slots within a booking, supporting complex multi-period reservations.

BookablePlanning

Defines availability rules, working hours, and constraints for resources.

Advanced Features

Custom Resource Synchronization

Automatically sync data from your models to bookable resources:

use Masterix21\Bookings\Models\BookableResource;
use Masterix21\Bookings\Models\Concerns\Bookable;
use Masterix21\Bookings\Models\Concerns\IsBookable;
use Masterix21\Bookings\Models\Concerns\SyncBookableResource;

class Room extends Model implements Bookable
{
    use IsBookable;
    use SyncBookableResource;

    /**
     * Called automatically when the room is saved
     */
    public function syncBookableResource(BookableResource $resource): void
    {
        $resource->update([
            'is_visible' => $this->is_published,
            'is_bookable' => $this->is_available && $this->is_clean,
            'max' => $this->max_concurrent_bookings,
            'size' => $this->capacity,
        ]);
    }
}

Key Features:

  • Opt-in with SyncBookableResource trait
  • Automatically called on model save
  • Handles both single resource (bookableResource) and multiple resources (bookableResources)
  • N+1 query optimized

For complete documentation, see docs/synchronization.md

Related Bookings (v1.2.0+)

Link bookings together using parent-child relationships:

use Masterix21\Bookings\Actions\BookResource;

// Create a parent booking
$roomBooking = (new BookResource())->run(
    periods: PeriodCollection::make([Period::make('2024-12-25', '2024-12-27')]),
    bookableResource: $room,
    booker: $user,
    label: 'Hotel Room'
);

// Create related child bookings
$parkingBooking = (new BookResource())->run(
    periods: PeriodCollection::make([Period::make('2024-12-25', '2024-12-27')]),
    bookableResource: $parkingSpot,
    booker: $user,
    parent: $roomBooking,
    label: 'Parking Spot'
);

$spaBooking = (new BookResource())->run(
    periods: PeriodCollection::make([Period::make('2024-12-26 14:00', '2024-12-26 15:30')]),
    bookableResource: $spaRoom,
    booker: $user,
    parent: $roomBooking,
    label: 'Spa Treatment'
);

// Access relationships
$children = $roomBooking->childBookings; // Collection of related bookings
$parent = $parkingBooking->parentBooking; // Parent booking instance

Benefits:

  • Link related bookings together (room + parking, appointment + follow-up, etc.)
  • Maintain independent booking lifecycle for each resource
  • Children survive parent deletion (nullOnDelete() behavior)
  • Query and filter by relationships

Migration Required: This is an opt-in feature requiring an optional migration:

php artisan vendor:publish --tag="bookings-migrations"
# Then run: update_bookings_add_parent_booking_id.php
php artisan migrate

For complete documentation, see docs/related-bookings.md

Planning Source Pattern

Link business models (rates, special offers) directly to planning:

use Masterix21\Bookings\Models\Concerns\BookablePlanningSource;
use Masterix21\Bookings\Models\Concerns\IsBookablePlanningSource;
use Masterix21\Bookings\Models\Concerns\SyncBookablePlanning;

class Rate extends Model implements BookablePlanningSource
{
    use IsBookablePlanningSource;
    use SyncBookablePlanning;

    /**
     * Called automatically when the rate is saved
     */
    public function syncBookablePlanning(): void
    {
        $this->planning()->updateOrCreate(
            ['bookable_resource_id' => $this->room->bookableResource->id],
            [
                'starts_at' => $this->valid_from,
                'ends_at' => $this->valid_to,
                'monday' => true,
                'tuesday' => true,
                'wednesday' => true,
                'thursday' => true,
                'friday' => true,
                'saturday' => $this->includes_weekend,
                'sunday' => $this->includes_weekend,
            ]
        );
    }
}

Benefits:

  • Opt-in with SyncBookablePlanning trait
  • Single source of truth: your business model controls availability
  • Automatic synchronization on save
  • Bidirectional navigation: $rate->planning and $planning->source
  • Planning auto-deleted when source is deleted

Migration Required: If you're upgrading from an older version, run this additional migration:

php artisan vendor:publish --tag="bookings-migrations"
# Then manually run: update_bookable_plannings_add_source_columns.php
php artisan migrate

For complete documentation, see docs/synchronization.md

Planning Constraints

Define when resources are available:

use Masterix21\Bookings\Models\BookablePlanning;

BookablePlanning::create([
    'bookable_resource_id' => $bookableResource->id,
    'monday' => true,
    'tuesday' => true,
    'wednesday' => true,
    'thursday' => true,
    'friday' => true,
    'saturday' => false, // Closed on weekends
    'sunday' => false,
    'starts_at' => '2024-01-01 09:00:00', // Available from 9 AM
    'ends_at' => '2024-12-31 18:00:00',   // Until 6 PM
]);

Event System

Listen to booking lifecycle events:

use Masterix21\Bookings\Events\BookingCompleted;
use Masterix21\Bookings\Events\BookingFailed;

// In your EventServiceProvider
protected $listen = [
    BookingCompleted::class => [
        SendBookingConfirmationEmail::class,
        UpdateInventory::class,
    ],
    BookingFailed::class => [
        LogBookingFailure::class,
        NotifyAdministrators::class,
    ],
];

Checking Availability

// Check if a resource is booked at a specific time
$isBooked = $room->isBookedAt(now());

// Get booked periods for a specific date
$bookedPeriods = $room->bookedPeriodsOfDate(today());

// Get all bookings for a resource
$bookings = $room->bookings;

Overlap Detection

The package automatically prevents overlapping bookings:

use Masterix21\Bookings\Exceptions\BookingResourceOverlappingException;

try {
    $booking = (new BookResource())->run(/* ... */);
} catch (BookingResourceOverlappingException $e) {
    // Handle booking conflict
    return response()->json(['error' => 'Time slot already booked'], 409);
}

Complete Examples

For comprehensive implementation examples, see:

Documentation

For detailed documentation, see:

Key Topics

  • ๐Ÿ›๏ธ Architecture - Package design and structure
  • ๐Ÿš€ Getting Started - Quick start guide
  • ๐Ÿ“Š Models - Model relationships and usage
  • โšก Actions - Core booking operations
  • ๐Ÿ”„ Synchronization - Resource and planning synchronization
  • ๐ŸŽฏ Events - Event system and listeners
  • ๐Ÿงช Testing - Testing strategies and examples
  • ๐Ÿ”ง Extending - Customization and extensions
  • ๐Ÿšจ Troubleshooting - Common issues and solutions

Legacy Quick Reference

For complete API documentation, see docs/api-reference.md

The package provides the following core traits and actions:

  • IsBookable trait - Makes any model bookable
  • HasBookings trait - For entities that can make bookings
  • BookResource action - Creates and updates bookings
  • CheckBookingOverlaps action - Validates booking conflicts

Events are automatically fired during the booking lifecycle for audit trails and integrations.

Testing

Run the package tests:

composer test

Run tests with coverage:

composer test-coverage

Run static analysis:

composer analyse

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.