torann/remote-model
torann/remote-model brings Eloquent-style remote models to Laravel, letting your app query, create, update, and delete RESTful API resources over HTTP as if they were local database models, with support for scopes, relationships, and caching.
Architecture fit. torann/remote-model is a thin, Eloquent-shaped HTTP adapter: a model class maps to a remote endpoint, and find/where/paginate/save forward to that API. Architecturally it belongs as an anti-corruption layer / migration seam between our domain code and an external system — internal admin dashboards over CRM/ERP/legacy REST, prototype integrations, and read-through layers during a system replacement. It is not a fit for customer-facing, write-heavy, or transactional flows. The core framing question for the team is whether remote data should be modeled as Eloquent models at all; this package forces that decision deliberately rather than by default, which is itself a useful outcome of the evaluation.
Integration feasibility. Feasibility splits cleanly by stack:
| Stack | Path | Effort | Confidence |
|---|---|---|---|
| Legacy Laravel 5.x / PHP 7.0–7.1 | composer require torann/remote-model:^0.2.1, manual provider registration |
Hours | High |
| Modern Laravel 10/11 / PHP 8.2+ | Vendor source into app/Remote/, port transport to Laravel Http/Guzzle 7, add tests |
1–2 sprints | Medium — unverified, must be spiked |
On a modern stack composer require will almost certainly fail dependency resolution. The README-era install instructions no longer apply, so the realistic proposal is adopt as a pattern, not as a dependency: treat the repo as an unmaintained reference implementation and budget the fork explicitly.
Technical risk. The risk profile is dominated by decay, not licensing (BSD-2-Clause is permissive and low-friction). The high-severity items are:
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Unmaintained (last release 2017, 0 dependents, no maintainer) | Certain | High | Internal owner, vendored fork, no upstream dependency |
Silent attribute drift on upstream schema change (null instead of type error) |
High | High | Validation at hydration boundary + contract tests on recorded fixtures |
Pagination truncation (pre-0.2.1 bug; still needs assertion on 0.2.1) |
Medium | High | Pin ^0.2.1; test full result set + page metadata |
| No retries/backoff/rate limiting/circuit breaking — remote failure becomes a hard exception | High | High | Add at transport layer; define degradation path per call site |
| No transactions; partial write failure is a real state problem | Medium | High | Restrict to single-record CRUD; never chain two dependent remote writes |
| Naive local caching in multi-pod deployment (stampede, inconsistent reads) | Medium | Medium | Bypass package cache; cache above the repository with a shared store + lock |
| Old Guzzle / Laravel 5.x contracts block modern stack | High | Medium | Fork costed at 1–2 sprints including tests |
| No security-response path (no CVE triage, no patch stream) | Certain | Medium | Accepted risk, documented; internal security review owns it |
Key questions to resolve before committing.
0.2.1)?Stack fit and decision gate. Sequence the decision so the fork cost is measured, not assumed:
Http, and get a single User::find(42) working end-to-end against a real endpoint (correct URL, correct auth, attributes hydrated). This validates the whole path — everything harder is additive scope.Compatibility and migration path. On the modern path: vendor to app/Remote/, register the service provider from our own provider (the package predates auto-discovery), port the transport to Guzzle 7 / Laravel Http, and port the model base class to Laravel 10/11 contract signatures. Publish and own config/remote-model.php in-repo — host/prefix joining is the single most common source of "package bugs," so that file must be under our change control. Substitute Http::fake() for Guzzle mocks in tests so the test suite does not depend on legacy Guzzle.
Sequencing. Deliver in risk-ascending order:
find() end-to-end, port effort estimated, owner named.CustomerRepository) so domain code never depends on App\Remote\*. This is the highest-leverage pattern — it is what makes the fork survivable and caps the blast radius at one class.try/catch.nulls.Explicit non-goals. Do not build multi-step workflows depending on two remote writes; do not rely on relationships/lazy loading; do not treat where() as a local query builder; do not place the package on a customer-facing critical path.
Maintenance. Every future bug fix, framework bump, and security patch is ours. There is no maintainer, no community, and 0 dependents — no external bug-fixing velocity and no knowledge base to lean on. Operationally this means: the fork lives in our repo, follows our dependency policy, and gets an owner in the on-call rotation. Budget a recurring small allocation (a few days per Laravel major) rather than a one-time cost. Treat the dependency as unmaintained code sample in the dependency register so intake criteria for future packages are informed by this profile.
Support. No upstream escalation path exists. Remote failures surface as hard exceptions in our request path because the package has no retries, backoff, rate limiting, or circuit breaking. Support therefore requires:
config/remote-model.php → model $endpoint → raw remote response → transport/Guzzle request log.find() failing before debugging scopes or pagination.Scaling. Each model operation is a synchronous HTTP call, so latency and throughput are bound by the remote API, not by us. Watch for:
Failure modes and required behavior.
| Failure | Symptom | Required handling |
|---|---|---|
| Remote 5xx / timeout | Hard exception mid-request | Retry with backoff; circuit breaker; documented fallback per call site |
| Upstream schema drift | null attribute, no error |
Hydration-boundary validation + contract tests in CI |
| Pagination defect | Silently truncated result set | Pin ^0.2.1; assert count and page metadata in tests |
| Host/prefix misconfiguration | 404 against an endpoint that exists | Dump the built URL before changing anything else |
| Auth token expiry | 401s under load | Refresh-and-retry handler owned by us |
| Cache stampede | Thundering herd on expiry | Shared store + lock in the cache decorator |
| Partial write | Inconsistent remote state | Restrict to single-record CRUD; try/catch treats partial failure as a real state problem |
Ramp-up. The dominant onboarding risk is the Eloquent illusion: the API looks like Eloquent and invites assumptions it cannot honor — no relationships/lazy loading, no real query scopes, no transactions, no persistence. Junior engineers will write Eloquent-shaped code that fails at runtime unless onboarding leads with these limits. Ramp-up plan: a short internal doc covering the limits, the repository-interface pattern, the fixture set, and the debugging order; plus the contract tests as the working reference for what the remote actually returns. Given 0 dependents and no upstream tests, our fixture suite is the only reliable regression net — it should exist before the first production call site, not after.
Success metrics and exit criteria. Track mean integration time per new remote resource (the velocity claim the package makes), incident count attributable to remote calls, contract-test drift catches, and cache hit rate / remote call volume after the decorator lands. Exit criteria: sunset date reached, remote system replaced, or fork maintenance cost exceeds the internal-adapter alternative — at which point the repository interface means replacement touches one class.
How can I help you explore Laravel packages today?