plum/l5-repository
Laravel package that adds repositories, criteria, presenters, and transformers to your applications for cleaner, testable data access. Supports caching, request criteria, and easy model swapping—a practical way to organize query logic and keep controllers and models lean.
Architecture fit
plum/l5-repository (namespace Prettus\Repository) implements the classic repository pattern on top of Eloquent: BaseRepository / RepositoryEloquent provide a CRUD contract (all, find, create, update, delete, paginate), and a Criteria pipeline lets you push composable query modifiers (pushCriteria, skipCriteria, scopeQuery) ahead of execution. Optional traits add presenters (Fractal transformers) and repository-level caching. Artisan generators (make:repository, make:criteria, make:presenter, make:entity, make:bindings) scaffold the wiring.
The architectural question a TPM should force early: Eloquent already is a data-access abstraction. Adopting this package buys you (a) a swappable, mockable interface per aggregate, (b) a reusable cross-cutting filter mechanism (RequestCriteria turns ?search=, ?filter=, ?order= into a query without hand-written controller code), and (c) a place to hang caching. It costs you one class per aggregate, an interface + binding per aggregate, and a layer that leaks whenever a caller needs Eloquent-specific power (chunking, cursors, complex joins, upsert, relationship aggregates). Fit is strongest for CRUD-heavy internal APIs, admin panels, and multi-tenant apps where a single criteria (tenant scoping, soft-delete policy, authorization filter) must apply uniformly. Fit is weak for reporting, event-sourced, or read-model-heavy services.
Integration feasibility
Low friction mechanically: composer require plum/l5-repository, publish config/repository.php, register RepositoryServiceProvider (or a project-owned provider binding Interface::class => ConcreteRepository::class). A single aggregate can be migrated in an afternoon; the generators make the 20th repository as cheap as the first. No database changes, no schema opinions, no runtime service dependency. Reversible — delete the provider bindings and the layer is gone.
Technical risk
composer why-not against the target Laravel and PHP versions before any design work. Historical maintenance has been single-maintainer and intermittent.Dependents: 0 is almost certainly an undercount or missing data (4,195 stars is inconsistent with zero downstream consumers). Do not read it as abandonment evidence either way — verify against Packagist's actual dependent count and GitHub issue/PR latency.boot()) apply to every query on that repository. Silent filters — an unexpected tenant scope, an eager-load, an orderBy — are the most common production surprise and are painful to trace because the SQL is assembled in a class the debugging developer never opened.RequestCriteria with $fieldSearchable misconfigured exposes column-level filtering/searching to arbitrary client input. Treat fieldSearchable as a security-reviewed allowlist, not boilerplate.CacheableRepository requires a taggable cache store (Redis/Memcached/array) — file and database stores silently fail or throw. Critically, invalidation is driven by the repository's own write methods, so writes that bypass the repository (queue jobs, console commands, raw DB:: queries, other services, sibling repositories on the same table) leave stale cache with no signal.Key questions
RequestCriteria shipping enabled-by-default match our threat model for public endpoints?Stack fit
Natural fit for a conventional Laravel monolith or modular monolith using service classes over Eloquent, especially where controllers are thin and the team has already accepted some form of layered architecture. Poor fit where the codebase leans on Eloquent expressiveness (query scopes, relationships as first-class), or where livewire/Inertia-style direct model usage is the norm — the repository layer becomes a competing convention.
Migration path
Strangler, not rewrite. Introduce the package in one bounded context, behind the existing service layer, so callers are unchanged and the repository is an implementation detail of the service. Do not let controllers depend on RepositoryInterface directly in the first wave; that couples the whole app to a third-party contract before you have evidence the layer earns its keep. Convert the second aggregate only after the first has shipped and been debugged.
Compatibility
Pin exact versions. Verify the illuminate/support constraint, PHP constraint, and the Fractal version pulled transitively. Run the generators against the target Laravel version in a scratch branch — generator command signatures are a common breakage point across Laravel majors. If the app uses Laravel's native API Resources, disable presenters outright rather than running both.
Sequencing
RequestCriteria is opt-in per repository (recommended) or global.fieldSearchable rules) and then require it in review. Expansion beyond the pilot without that doc is how these layers sprawl.Maintenance
Ongoing cost is class-count, not dependency risk: every aggregate carries an interface, a repository, a binding, and often a presenter — roughly 3–4 files of boilerplate where Eloquent needed zero. That is manageable when the generators are used and review enforces convention, and corrosive when half the team writes repositories and half queries models directly. Upstream maintenance is the second-order cost; budget for the possibility that a Laravel major requires a community fork or a local patch, and keep the package's blast radius narrow enough that forking is survivable.
Support
Internal: a mid-level Laravel developer is productive in 2–3 days; the failure modes (criteria ordering, silent global criteria) take a senior reviewer to catch, so pair the adoption with code-review guidance rather than training alone. External: community support is GitHub-issues-grade with no SLA and an intermittent maintainer. Treat it as "supported by the team" from day one; there is no vendor to escalate to.
Scaling
Application-level scaling is fine — the layer adds one method call per query. Two things scale badly. First, criteria accumulation: global criteria lists grow append-only and each adds a query mutation whose interactions are untested combinatorially; audit that list quarterly. Second, cache key and tag growth in Redis as cached repositories multiply — taggable invalidation is cheap per key but tag sets can grow large on high-cardinality caches, so define a key-prefix convention and watch tag memory.
Failure modes
skipCriteria in tests, query-log assertions).Ramp-up
Two to three days to first productive commit for a Laravel-fluent developer; two to three weeks before they can reliably diagnose criteria and caching behavior in production. The senior-review requirement never goes away entirely, which is the honest operational price of the abstraction — if the team cannot sustain that review capacity, the correct decision is to skip this package and lean on Eloquent scopes plus a thin service layer for the same cross-cutting concerns.
How can I help you explore Laravel packages today?