Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Pms Clients Laravel Package

shelfwood/php-pms-clients

PHP clients for property management system (PMS) APIs, giving Laravel and PHP applications a consistent way to connect to PMS platforms and work with property, reservation, and related data.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a framework-agnostic PHP library — there is no Laravel service provider or facade. You wire it in yourself in about four steps:

  1. Install via Composer:
    composer require shelfwood/php-pms-clients
    
  2. Read the source first — and check the version you're on. v5.0.0 is a breaking release. The Mews client now calls versioned endpoints (/api/connector/v1/services/getAvailability/2024-01-22, /reservations/getAll/2023-06-06), so the method signatures and response shapes in src/ are what matters, not older blog snippets. Grep for __construct, public function, and the endpoint constants to map the surface.
  3. Add credentials to config/services.php and .env so they don't leak into code:
    'mews' => [
        'base_url'    => env('MEWS_BASE_URL'),
        'client_token'=> env('MEWS_CLIENT_TOKEN'),
        'access_token'=> env('MEWS_ACCESS_TOKEN'),
    ],
    
  4. First use case: pull reservations into your own model. This is still 90% of why you'd use a PMS client from Laravel — mirror bookings into a local reservations table so you can join them to CRM/billing data instead of hitting the PMS on every page load. Note that getAll now uses the versioned /reservations/getAll/2023-06-06 endpoint with pagination/extent alignment, so expect to page through results rather than get one giant array:
$client = new MewsClient(config('services.mews.base_url'), config('services.mews.access_token'));
$reservations = $client->getReservations(['startUtc' => now()->subWeek(), 'endUtc' => now()]);

Implementation Patterns

Bind the client as a singleton — it now caches state internally. The Mews client fetches enterprise timezone from configuration/get and caches it per client instance to do its UTC-normalization of availability/pricing boundaries. Reusing one instance avoids an extra round-trip per call:

$this->app->singleton(MewsClient::class, fn () => new MewsClient(
    config('services.mews.base_url'),
    config('services.mews.access_token'),
));

Wrap it behind your own interface. Mews and Booking Manager return different shapes and the Mews availability response changed in v5 — it now returns ResourceCategoryAvailabilities with a per-category Metrics map, not a flat availability list. A PmsClient contract with reservations(), availability(), pricing() adapters insulates the rest of the app from these provider contract churn events.

Supply Metrics explicitly. In v5 the Mews availability request requires Metrics (omitting it defaults to all metrics, which is heavier and slower than you probably want). Decide per call-site what you need and pass it — don't rely on the default:

$client->getAvailability([
    'StartUtc' => $start->copy()->utc(),
    'EndUtc'   => $end->copy()->utc(),
    'Metrics'  => ['AvailableRooms', 'Occupancy'], // be explicit
]);

Let the client own time-unit boundaries, but verify tenant timezone. v5 normalizes availability/pricing boundaries to the Mews-required "00:00 converted to UTC" using the enterprise timezone pulled from configuration/get. That's a genuine win — the old assessment told you to hand-roll UTC conversion. But it's cached per client instance, so if you run Octane/long-lived queue workers and the enterprise timezone changes, restart workers. For multi-tenant setups, one client instance per tenant.

Sync via queued jobs, not requests. PMS APIs are slow and rate-limited. Pattern:

  • SyncMewsReservations job — accepts a date range, calls the client, upserts into reservations. Since getAll is now paginated, make the job paging-aware or split ranges across jobs.
  • SyncPmsReservationsCommand — scheduled in routes/console.php, dispatches the job daily (or hourly for high-turnover properties).
  • Use upsert() keyed on the PMS reservation ID so re-running is safe.

Normalize into DTOs, then Eloquent. Map raw responses into a readonly DTO (ReservationData, AvailabilityData) via fromPms(array $data) factories. v5 changed the availability response envelope — if you were reading the old shape directly you'll want the DTO boundary to absorb that blast radius next time.

Handle webhooks if the provider offers them. Mews supports push notifications. Prefer a webhook controller dispatching a job over polling. Store raw payloads in webhook_logs so you can replay failed syncs.

Fire domain events after upsert. ReservationCreated / ReservationUpdated so notifications, billing, and availability cache invalidation react without knowing about the PMS.

Gotchas and Tips

  • v5.0.0 is breaking — budget for a migration, not a patch bump. Three things will bite existing integrations:
    • Availability moved to getAvailability/2024-01-22; the endpoint path changed and the response now nests ResourceCategoryAvailabilities with a per-category Metrics map. Any code reading the old flat structure breaks.
    • Metrics is now required on availability requests (defaulting to all metrics if omitted) — your calls will "work" but pull more than you asked for and cost more latency.
    • Pricing no longer sends OccupancyConfiguration on rates/getPricing. If you relied on the library populating it, you now need to pass whatever the current contract requires yourself.
  • Customers "search by email" changed under the hood. It's now implemented via customers/getAll + Extent + Limitation. Behaviour (pagination, fields returned) may differ from what your code assumed about the old dedicated search call — re-check that path.
  • No Laravel package auto-discovery. No vendor:publish for config. Register everything yourself.
  • Zero dependents + tiny user base = read the code before trusting it. Check whether the client returns typed DTOs or raw arrays, and whether it uses PSR-18/HTTPlug (injectable — you can point it at Guzzle and get Laravel's retry/timeout) or hardcoded cURL. The latter bypasses Laravel's HTTP middleware entirely.
  • Auth differs per provider. Mews' client token → access token flow produces a short-lived token. Cache::remember it rather than re-authenticating per call.
  • Rate limiting is on you. Wrap with RateLimiter::attempt() or ->delay() on dispatch. v5's paginated getAll means more calls per sync than before — a nightly range that used to be one request is now N. Spread large ranges across jobs.
  • Time zones: the client now handles the boundary math, but not everything. Availability/pricing boundaries get the "00:00 → UTC" treatment. Reservation payloads, customer data, and Booking Manager calls are still on you. Store UTC, convert at the edge.
  • Pagination. getAll is explicitly versioned and pagination-aligned in v5. Check whether the client returns a generator (ideal) or requires a manual loop; if manual, wrap it in a generator in your adapter.
  • Idempotency for bookings. Reservations get cancelled and re-created with new IDs, and modified in place. Match on PMS ID but also handle cancelled status updates — don't just insert.
  • Testing. v5 ships improved coverage asserting outgoing Availability/Pricing requests include required fields and correct UTC-normalized boundaries — mirror that pattern. Use Http::fake() if it rides Laravel's HTTP layer, otherwise inject a mocked PSR-18 client. Record redacted real responses as fixtures; the docs are still thin and response shapes are undocumented.
  • Debugging. Still no built-in logging. Wrap calls in a decorator logging request/response (redact tokens) plus timing — especially useful when diagnosing whether your Metrics omission or boundary normalization is causing an unexpected response.
  • Extension points are still thin. No middleware hooks or event dispatchers. If an endpoint is missing, subclass the client and add the method — safe if the HTTP client is constructor-injected. Watch the versioned paths: hardcoded endpoint strings in a subclass will need updating next time Mews bumps a version.
  • Upgrade checklist for v5: pin the version in composer.json, run your fixture/snapshot tests against the new availability response shape, audit every availability call for an explicit Metrics value, check anything that depended on OccupancyConfiguration in pricing, and re-verify customers-by-email call-sites.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen