Installation
composer require comsa/booking-bundle
Publish the bundle’s assets and configuration:
php artisan vendor:publish --provider="Comsa\BookingBundle\BookingServiceProvider" --tag="config"
php artisan vendor:publish --provider="Comsa\BookingBundle\BookingServiceProvider" --tag="migrations"
php artisan vendor:publish --provider="Comsa\BookingBundle\BookingServiceProvider" --tag="public"
php artisan migrate
Service Provider & Routes
Ensure the BookingServiceProvider is registered in config/app.php under providers.
Add the bundle’s routes in routes/web.php:
Route::prefix('admin')->middleware(['auth', 'admin'])->group(function () {
require __DIR__.'/booking/routes.php';
});
First Use Case: Basic Booking
Create a model extending Comsa\BookingBundle\Model\Booking (e.g., App\Models\MyBooking):
use Comsa\BookingBundle\Model\Booking;
class MyBooking extends Booking
{
protected $table = 'my_bookings';
}
Define a booking type in config/booking.php:
'types' => [
'my_booking' => [
'label' => 'My Booking',
'model' => \App\Models\MyBooking::class,
'fields' => [
'title' => 'string',
'start_at' => 'datetime',
'end_at' => 'datetime',
],
],
],
Admin Panel Access
Log in as an admin and navigate to /admin/bookings to manage bookings via the built-in admin panel.
use Comsa\BookingBundle\Services\BookingService;
$booking = app(BookingService::class)->create('my_booking', [
'title' => 'Team Offsite',
'start_at' => now()->addDays(7),
'end_at' => now()->addDays(10),
'user_id' => auth()->id(),
]);
end_at must be after start_at).Booking model or override fields in config/booking.php:
'types' => [
'custom_type' => [
'fields' => [
'custom_field' => [
'type' => 'string',
'label' => 'Custom Label',
'rules' => 'required|max:255',
],
],
],
],
// In EventServiceProvider
public function boot()
{
Booking::created(function ($booking) {
// Send notification, log, etc.
});
}
php artisan vendor:publish --tag="booking-views"
Copy resources/views/vendor/booking/ to your project’s resources/views/booking/.// In a service provider
Event::listen('booking.admin.list.columns', function ($columns) {
$columns[] = ['label' => 'Custom Column', 'field' => 'custom_field'];
});
BookingService to fetch bookings for APIs:
$bookings = app(BookingService::class)->all('my_booking', [
'with' => ['user', 'events'],
'filter' => ['start_at' => 'this_week'],
]);
Route::apiResource('bookings', BookingApiController::class);
config/booking.php:
'types' => [
'recurring' => [
'recurrence_rule' => 'FREQ=WEEKLY;BYDAY=MO,WE',
],
],
$recurringBooking = $booking->generateNextInstance();
$bookings = MyBooking::query()
->available()
->forUser(auth()->id())
->get();
class Event extends Model
{
public function bookings()
{
return $this->morphMany(MyBooking::class, 'bookable');
}
}
const calendar = new BookingCalendar('#calendar', {
bookingType: 'my_booking',
apiEndpoint: '/api/bookings',
});
@booking('my_booking', $booking->id)
<div class="booking-details">
{{ $booking->title }}
</div>
@endbooking
BookingService in tests:
public function test_booking_creation()
{
$booking = app(BookingService::class)->create('my_booking', [...]);
$this->assertDatabaseHas('my_bookings', [...]);
}
$this->actingAs($admin)
->get('/admin/bookings')
->assertSee('My Booking');
Configuration Overrides
config/booking.php may not reflect in the admin panel if the view cache isn’t cleared.php artisan view:clear or php artisan config:clear.Migration Conflicts
--force:
php artisan migrate --force
Recurrence Rule Parsing
recurrence_rule formats (e.g., FREQ=DAILY;INTERVAL=2) may silently fail.RRule library:
use Sabberworm\PHP-RRule\RRule;
$rule = new RRule($booking->recurrence_rule);
Admin Middleware
admin middleware to routes may expose the panel.Route::middleware(['auth', 'admin'])->group(...);
Timezone Handling
config/app.php) doesn’t match the database’s.config(['app.timezone' => 'UTC']);
Log Events
Enable booking-related logging in config/booking.php:
'debug' => env('BOOKING_DEBUG', false),
Check logs at storage/logs/laravel.log.
Query Logging Enable Eloquent logging to debug queries:
DB::enableQueryLog();
$bookings = MyBooking::all();
dd(DB::getQueryLog());
Admin Panel Debugging
Event::listen('booking.admin.list.data', function ($data) {
dd($data);
});
Custom Services
Extend the BookingService by binding your own:
$this->app->bind(BookingService::class, function ($app) {
return new CustomBookingService($app->make('booking'));
});
Webhooks Trigger actions via events:
// In a listener
public function handle(BookingCreated $event)
{
Http::post('https://your-webhook-url', $event->booking->toArray());
}
Multi-Tenancy Scope bookings by tenant:
class MyBooking extends Booking
{
public function scopeForTenant($query, $tenant)
{
return $query->where('tenant_id', $tenant->id);
}
}
Localization Override labels/translations:
'labels' => [
How can I help you explore Laravel packages today?