forxer/ovh-sdk
forxer/ovh-sdk is a PHP client for the OVH API that handles authentication, request signing, and responses behind a clean, Laravel-friendly interface, making it easy to manage domains, servers, and cloud resources directly from your application code.
What shipped. 0.5.0 is a pre-1.0 minor release that is functionally breaking for any consumer with try/catch blocks around SDK calls. The changes are not additive: exception types are replaced, and one previously-tolerated response shape (empty body on a list route) changes from "returns []" to "throws."
Architecture fit. The package is a Saloon-based HTTP client for the OVHcloud API with a Laravel-style resource facade (tasks()->ids(), tasks()->list()). This release shows the SDK acting as a correctness shim over an inconsistent upstream contract: OVHcloud's published schema advertises status=problem as a valid filter, but the live API rejects it with 400 Invalid inputs. The SDK now refuses to send it. That is the right call — fail before the network hop with a typed, actionable exception — but it means the package carries permanent upstream-compensation logic that will drift as OVHcloud's schema and behaviour diverge further.
The exception redesign is a genuine improvement. The new hierarchy encodes a distinction that was previously unknowable to callers:
| Condition | Before | After | Retryable? |
|---|---|---|---|
| 2xx body not JSON (HTML error page, empty body) | JsonException |
CouldNotDecodeResponse (⊂ CouldNotReadResponse) |
Yes — transient upstream/proxy |
| 2xx body JSON of wrong shape | TypeError |
CouldNotReadResponse |
No — contract violation |
Non-integer GET /auth/time |
Silently coerced to 0 |
Throws | Yes — transient, then hard fail |
status=problem filter |
Sent, then BadRequestException |
CouldNotFilterZoneTasks pre-flight |
No — caller bug |
The auth-time fix is the highest-value item in the release. A silent 0 produced a wrong time delta, cached it for an hour, and invalidated the signature of every request in that window — a one-hour, whole-tenant outage class of bug that surfaces as unexplained 401s. Converting that to an immediate, local throw is a large reliability win.
Technical risk.
catch (JsonException) does not fail to compile or fail static analysis (unless throw-path analysis is configured). It becomes dead code and the new exception propagates as an uncaught 500. This is the single largest migration hazard.CouldNotDecodeResponse extends CouldNotReadResponse. A catch (CouldNotReadResponse) placed first silently swallows the retryable/non-retryable distinction the release was designed to create. This warrants an explicit code-review rule.[] for a 204/empty list response now throws. Typically exercised only during upstream maintenance windows — i.e., it will first appear in production, during an incident.problem is refused as a filter, you can no longer ask OVHcloud "which zone tasks are in problem state?" You must list unfiltered and filter client-side, or track task IDs yourself. This changes query patterns and can increase call volume and payload size against rate limits.Key questions.
CouldNotReadResponse (or a documented isRetryable()), or must consumers hard-code the class list?composer update safe?Stack fit. The package assumes a Laravel app with the standard HTTP client stack (Saloon) and queue-driven background work for zone polling. Integration surface is narrow — a handful of resource calls — which makes the migration mechanical but also means the cost is concentrated entirely in error handling, not in call-site rewrites.
Recommended pattern: one anti-corruption boundary. The strategic move is to stop vendor exception types from leaking into application code. Introduce a single adapter/service that wraps every SDK call and translates SDK exceptions into a small, app-owned taxonomy (e.g. UpstreamUnavailable, UpstreamContractViolation, InvalidRequest). After this, 0.x churn in the SDK touches one file instead of N call sites — and the catch (JsonException) class of silent breakage becomes structurally impossible. This is worth doing as part of the 0.5.0 upgrade, not after it.
Migration path.
JsonException, TypeError, BadRequestException, and any broad catch (\Throwable) around SDK calls, including queue jobs, console commands, and scheduled tasks. Broad catches are the dangerous ones — they will swallow the new types and mis-classify them.0.5.0 exactly (avoid ^0.5 until the policy question is answered). Upgrade on a branch, not on main.CouldNotDecodeResponse before CouldNotReadResponse. Route them through the adapter.CouldNotDecodeResponse and auth-time failures to retryable-with-backoff; map CouldNotReadResponse to non-retryable/alerting; map CouldNotFilterZoneTasks to a caller-side bug that should be caught by a unit test, not a retry loop.tasks()->list(TaskStatus::Problem) with an unfiltered list plus client-side filtering, or an explicit ID-tracking table. Verify rate-limit headroom after the change.TaskStatus::Problem rejected pre-flight (assert no HTTP request is made).Compatibility. Nothing here breaks at the PHP level; it is purely behavioural. The practical constraint is downstream: any shared internal library or package that wraps this SDK must be upgraded in lockstep, so sequence the SDK bump before the services that depend on the wrapper.
Sequencing estimate. Half a day to a day for a mid-level engineer on a typical integration: exception audit and rewrite, adapter extraction, four new tests, canary. Adding the anti-corruption layer pushes it to ~1–2 days and pays for itself on the next 0.x release.
Maintenance. This release sets the expectation: expect breaking exception changes on every minor until 1.0. Budget a recurring upgrade line item rather than treating this as one-off. The underlying driver — OVHcloud's schema disagreeing with its runtime — is not going away, so the SDK's compensation logic will keep shifting.
Support and observability. Net positive. The new taxonomy is directly actionable at the alerting layer:
CouldNotDecodeResponse → upstream degradation or WAF/proxy interference. Alert on rate, not on individual occurrence.CouldNotReadResponse → contract drift. Page someone; the SDK needs a patch or the API changed.CouldNotFilterZoneTasks → developer error. Should never page; should be caught in CI.Recommend distinct log channels/metrics per class so these don't collapse into a generic "SDK error" bucket.
Scaling. The loss of server-side problem filtering is the only real scaling concern. Unfiltered task listing pulls more data per call and consumes more rate-limit budget on accounts with many zones. At small scale this is noise; at hundreds of zones polled frequently it is a measurable increase in call volume and payload. Model the per-poll cost before rollout if zone count is non-trivial, and consider ID-tracking instead of repeated full listings.
Failure modes to plan for.
GET /auth/time now blocks all traffic rather than silently signing incorrectly. A single short retry on that specific call is a reasonable resilience hedge — worth deciding deliberately rather than by default.catch surfacing as a 500 in a path that "never failed before."Ramp-up. Small surface, well-documented error semantics (the release links to dedicated errors and DNS-zones docs). A developer new to the package needs roughly half a day to internalise the exception hierarchy and the catch-ordering rule; the rest is Laravel/Saloon familiarity they likely already have. The non-obvious knowledge — that problem cannot be filtered server-side, and that empty bodies now throw — should be captured in the team's internal runbook, because neither is discoverable from the happy-path API surface.
How can I help you explore Laravel packages today?