rinvex/laravel-bookings
Rinvex Laravel Bookings provides a flexible booking system for Laravel applications, with Eloquent models, migrations, and configurable services, resources, availability, and pricing to manage reservations and schedules with ease.
Read this first: the package is archived upstream and its composer constraints target older Laravel/Illuminate majors. Before you invest a day in it, run composer require rinvex/laravel-bookings in a throwaway branch and check composer why-not laravel/framework. If it won't resolve on your current Laravel, plan to fork it into packages/ (or a private VCS repo) and bump the constraints rather than fighting --ignore-platform-reqs. The code is small and readable — a fork is usually cheaper than a workaround.
Assuming it installs:
php artisan vendor:publish --provider="Rinvex\Bookings\Providers\BookingsServiceProvider"
php artisan migrate
vendor/rinvex/laravel-bookings/src/Traits/ — this is the fastest way to see the real API surface (relations, scopes, helpers) rather than trusting the README.morphMap entry for each bookable/customer type in AppServiceProvider if you don't want FQCNs in your database.First use case: a single-resource, single-slot booking — a meeting room booked by the hour. Create one Booking against one Room, then immediately write the availability query yourself (see below). Do not expect the package to tell you a slot is free; it gives you the schema, polymorphic relations, and traits, not a scheduling engine.
Create through a service, not the controller. Wrap creation in a transaction and re-check availability inside it:
public function book(Bookable $resource, array $data): Booking
{
return DB::transaction(function () use ($resource, $data) {
$conflict = Booking::query()
->where('bookable_type', $resource->getMorphClass())
->where('bookable_id', $resource->getKey())
->whereNotIn('status', ['cancelled', 'no_show'])
->where('booked_from', '<', $data['booked_to']) // half-open interval
->where('booked_to', '>', $data['booked_from'])
->lockForUpdate()
->exists();
abort_if($conflict, 409, 'Slot no longer available');
return Booking::create([...$data, 'status' => 'pending']);
});
}
The booked_from < $end AND booked_to > $start shape is the only correct overlap test for half-open intervals — it also catches "starts inside" and "ends inside" cases that naive between queries miss.
Own the status lifecycle. The package stores status; it does not enforce transitions. Put a small enum + a transitionTo() method on your own Booking subclass (or a trait) so pending → confirmed → completed and → cancelled are validated in one place, and record cancelled_at/confirmed_at in your own migration additions.
Extend the model via config, not by editing vendor. Rinvex packages generally allow model swapping in the published config; bind your App\Models\Booking extends Rinvex\Bookings\Models\Booking there so you can add casts, accessors, and scopes without touching vendor/. Always follow with php artisan config:clear.
Model extra data as a separate table. Participant counts, add-ons, deposits, and external calendar IDs don't belong in the core table — a booking_meta or booking_addons table keyed by booking_id survives package upgrades and keeps your queries fast.
Reporting. Build read models with plain Eloquent/query builder against bookings, indexed on (bookable_type, bookable_id, booked_from, booked_to) and (customer_type, customer_id). Aggregations (revenue per resource, utilization %) are your queries, not the package's.
API integration. Validate that booked_to > booked_from, that the customer is authorized for the bookable (tenant/org scoping), and that the resource is active — none of this is in the package. Map a 409 from the conflict check to a "slot taken, pick another" UI state.
FORK.md note, and budget for a Laravel upgrade every cycle.lockForUpdate() (or a unique constraint / exclusion constraint on a Postgres range type) will double-book under load. If you're on Postgres, add a tstzrange exclusion constraint in a follow-up migration — it's a real guarantee, not a convention.booked_from/booked_to in UTC and convert at the edge. If the published migration used timestamp instead of timestampTz/dateTime, cast the column explicitly on your model. Mixing app timezone and DB timezone silently shifts every overlap check.price/total are decimal or integer minor units before writing formatting code; be consistent across price, quantity, and total, and compute total server-side, never from the request. Currency is a string column — validate it against your supported list.bookable_type values, adding a morphMap later breaks existing queries. Decide on morph aliases before the first booking row is written.config:clear / cache:clear; a stale cached config referencing the vendor model is a classic "my scope doesn't exist" error.<= where < was needed.How can I help you explore Laravel packages today?