jooservices/laravel-repository
A Laravel repository package that adds a clean data-access layer over Eloquent, helping organize queries, reuse filtering and caching logic, and keep controllers and models lean. Suitable for building testable, maintainable Laravel applications.
v4.0.0 is a major release driven by a standards alignment ("Align package to JOOservices v4 standards") rather than a feature launch. The changelog is terse, so treat the upgrade as a breaking change until you've read the diff between v3.x and v4.0.0 and confirmed the package's PHP/Laravel support matrix.
Minimum steps:
composer require jooservices/laravel-repository:^4.0
php artisan vendor:publish --tag=repository-config # if a config file ships
php artisan test
First places to look, in order:
CHANGELOG.md / the compare view .../compare/v3.x...v4.0.0 — the real breaking changes live here, not in the release notes.composer.json — confirm php and illuminate/* constraints before you touch anything else; a standards bump usually raises both.src/Contracts/ — the interfaces are the API surface you bind against. If signatures changed, your implementations break before anything else does.config/repository.php (if present) — cache, pagination, and default-model settings are usually here.tests/ — the package's own tests are the fastest, most accurate documentation of intended v4 usage.First use case: one aggregate, one repository. Pick your most-read model, define an interface that returns domain-shaped results (Collection, DTOs, or paginators — not query builders), and wire it in a service provider:
// app/Providers/RepositoryServiceProvider.php
public function register(): void
{
$this->app->bind(
\App\Contracts\UserRepositoryInterface::class,
\App\Repositories\UserRepository::class,
);
}
Don't retrofit your whole codebase on day one. Get one repository working end-to-end under v4, run it against real queries, then scale out.
Repository per aggregate root, not per table. Join tables and pivot models don't get repositories; they're reached through their aggregate. This is the single biggest driver of whether the pattern helps or becomes ceremony.
Keep Eloquent inside the boundary. The value of the pattern is that Illuminate\Database\Eloquent\Builder never escapes into controllers, jobs, or Blade. If a method returns a builder "for flexibility," you've paid the abstraction cost and kept the coupling.
Criteria for cross-cutting filters. The JOOservices lineage leans on a criteria/filter pattern for reusable query constraints (active-only, tenant-scoped, date-ranged). Prefer criteria over boolean flags accumulating in method signatures:
final class ActiveOnly implements CriterionInterface
{
public function apply(mixed $query): mixed
{
return $query->where('is_active', true);
}
}
Caching at the repository layer, invalidation at the write method. Read methods (find, all, paginate) are the natural cache seam. Pair every cached read with explicit invalidation inside create/update/delete — not in observers, not in the model. Cache tags keyed by aggregate make this tractable; if the configured store doesn't support tags, fall back to versioned keys.
Transactions belong in services, not repositories. A repository method wrapping DB::transaction() looks tidy but forces nested transactions the moment a service composes two repositories. Let the service own the unit of work.
Pagination as a first-class return type. Decide per method: LengthAwarePaginator for API index endpoints, Collection for internal composition, scalar/?Model for lookups. Encode that in the interface docblocks so callers don't guess.
Testing seam. Bind a fake or an in-memory implementation in tests to exercise service logic without a database; use a real repository with SQLite in-memory against migrations for a thin set of integration tests that actually verify the queries. Both, not either.
Integration points to wire deliberately: form requests → service → repository (not request → repository); API resources consuming repository results; queued jobs resolving interfaces via the container rather than constructing repositories.
mixed/generics in docblocks), possibly renamed namespaces or contracts, and a raised PHP floor. Run a static analyser (PHPStan/Larastan) after upgrading — it will surface the interface drift faster than your test suite will.Collection and now returns an array (or vice versa) won't fail until something calls ->map() on it. Grep your call sites for collection methods before trusting a green build.apply() produces queries that silently carry filters from a previous call — a classic heisenbug that only appears under load.all() that returns hydrated models still lazy-loads relations. Either accept eager-load hints as a parameter or expose dedicated methods; don't let the abstraction convince you the problem went away.DB::listen() or ->dd() on the query builder inside the repository is fine and fast. Telescope's query panel is better for seeing whether the issue is your query or your cache layer — clear the cache before profiling, or you'll profile a hit.app/, open an issue instead — that's the signal the extension point is missing."jooservices/laravel-repository": "^4.0" — not *, and not dev-main, especially given how little the release notes tell you.How can I help you explore Laravel packages today?