omnisense / omnistay
Omnistay: where to stay and what to book for a trip, under any PHP host, whoever sells it - hotels and homes rented by their owners searched through one interface, a property's availability, nightly rates, fees, taxes and cancellation policy, offers re-priced, bookings made and cancelled, a property
Requires
- php: >=8.2
- ext-mbstring: *
- omnisense/contracts: ^1.0@dev
- symfony/http-client-contracts: ^3.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
- symfony/http-client: ^6.4|^7.0|^8.0
Suggests
- omnisense/omnibridge: The host contracts; omnibridge/symfony and omnibridge/laravel wire omnistay into their host when a site needs it
- omnistay/amadeus: Hotels through Amadeus' hotel APIs: a city's or a point's hotels, their offers, an offer re-priced, a booking (a client id and secret)
- omnistay/local: A site's own listings as a provider: homes rented by their hosts, their calendar, seasonal prices, minimum stays, bookings held without double booking
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-10 12:34:07 UTC
README
Where to stay and what to book for a trip, under any PHP host, whoever sells it. Hotels from a distribution system and homes rented by their owners answer the same contracts, so a site searches both through one interface, prices a stay line by line, books it and cancels it.
use Omnistay\Amadeus\AmadeusFactory; use Omnistay\Local\LocalFactory; use Omnistay\Model\{Guests, Stay, StayQuery}; use Omnistay\Registry; $registry = new Registry([new AmadeusFactory($http), new LocalFactory($store)], [ 'hotels' => ['factory' => 'amadeus', 'options' => ['client_id' => getenv('AMADEUS_CLIENT_ID'), 'client_secret' => getenv('AMADEUS_CLIENT_SECRET')]], 'homes' => ['factory' => 'local', 'options' => ['service_fee' => 12]], ]); $results = $registry->searchAll(new StayQuery(Stay::of('2026-12-18', '2026-12-21'), new Guests(2), latitude: 45.899, longitude: 6.129, radius: 10)); foreach ($results->items as $result) { echo $result->property->name, ' ', $result->nightly(), "\n"; // hotels and homes, the cheapest night first } $offer = $registry->availabilityOf($results->items[0]->property->ref, $stay, $guests)->cheapest(); $booking = $registry->bookOffer(new BookingRequest($offer, [new Traveller('Alice', 'Example', 'alice@example.org')], paymentReference: $paid));
The shape
- Models (
Omnistay\Model):Property(a hotel or a home: kind,Location,Amenitys,Photos,RoomTypes, stars, guests' rating),StayandGuests,StayQuery,SearchResult,Offerwith itsRate(the nights one by one,Charges: cleaning, service, taxes, discounts),CancellationPolicyandPaymentTiming,Availability,CalendarDay,BookingRequest,BookingandBookingStatus,Money;CarHireandPackage, shared models for the providers that sell them. - Contracts by capability, each provider saying which it has (
Capabilities):SearchInterface,AvailabilityInterface(a property's offers, an offer re-priced),CalendarInterface,BookInterface,CancelInterface,ContentInterface. Registry(providers by name;searchAll()merges several providers' answers; aPropertyRefnames the provider it came from, so every question about it goes back there),Config,ProviderFactory, as in the other Omnisense families.Testing:FixedProvider(every contract, in memory),RecordedHttpClient,BareTestCase.- Providers are packages: omnistay/amadeus (hotels), omnistay/local (a site's own listings).
Usage policies
Each provider declares its usage policy as data (its policy.json: rate limits, rights,
attribution, requirements, where and when it was read), and every call through search(),
availability(), calendar(), book(), cancel() or content() - and through searchAll(),
availabilityOf(), propertyOf(), bookOffer() - is held to it: checked before, counted after,
refused with UsageLimitReachedException once a limit is reached or while the provider asks to
wait (a 429 and its Retry-After, a RateLimit field with nothing left). In searchAll(), a
provider over its limit is one of the failures; the others' answers stand. A site may tighten a
policy in its configuration, never loosen it:
'homes' => [ 'factory' => 'local', 'policy' => ['rateLimits' => [['max' => 60, 'window' => 'minute', 'operations' => ['book']]]], 'usage' => ['commercial' => true], ],
$registry->policy('homes') gives the policy as the site runs it. Pass a shared
RateLimiterInterface as the registry's third argument when the application runs in many
processes. See docs/policies.md.
The core needs no framework: it requires omnisense/contracts (the kernel every omnisense family
builds on: provider, factory, registry, JSON client, usage policies, testing kit) and
symfony/http-client-contracts, nothing else.
Manual: docs/index.md. License: MIT.