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

Laravel Repository Laravel Package

salehhashemi/laravel-repository

A Laravel package implementing the repository pattern with a clean abstraction layer for database access. It adds dynamic filtering, reusable criteria, custom search logic, and a make:repository command to scaffold repositories, interfaces, and filters.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and check compatibility first. The package requires PHP 8.2+ and declares support for Laravel 12.x/13.x only (v0.6.0). If you're on Laravel 10/11, pin an older tag or skip it — this is a small package (20 stars, 0 dependents) so you should expect to read the source rather than rely on community answers.

composer require salehhashemi/laravel-repository
php artisan make:repository User

Where to look, in order:

  1. The files make:repository just generated (app/Repositories/UserRepository.php, UserRepositoryInterface.php, app/Filters/UserFilter.php) — these are the real documentation, more so than the README.
  2. vendor/salehhashemi/laravel-repository/src/BaseEloquentRepository.php — the whole method surface (findOne, findAll, findList, paginate, orderBy, addCriteria, lockForUpdate, sharedLock, applyConditions, with) lives there. It's a short read.
  3. config/repository.php — publish it now, not later:
php artisan vendor:publish --provider="Salehhashemi\Repository\RepositoryServiceProvider" --tag="config"
php artisan config:clear

First use case: bind the interface in a dedicated RepositoryServiceProvider, then inject it into a controller. Keep the first repository dumb — findAll and paginate only — and add filtering/criteria once the binding works and you've confirmed the container resolves it.

$this->app->bind(UserRepositoryInterface::class, UserRepository::class);

Implementation Patterns

The triad is the pattern. Every model gets a repository (extends BaseEloquentRepository, implements your interface), an interface (extends RepositoryInterface, plus SearchableRepositoryInterface if you use the trait), and a filter (extends BaseFilter). Both the repository and the filter must implement getModelClass() pointing at the same model — that duplication is the most common source of subtle breakage, so generate them with the command rather than hand-writing.

Filters are for user-driven query params; criteria are for constraints the code owns. PostFilter::applyFilter(array $queryParams) is where request input lands — whereLike, whereValue, compare, dateFrom, dateTo, plus raw getQuery() escapes for relation filtering (whereHas). Criteria (CriteriaInterface::apply(Model, Builder)) are for invariants: FeaturedPostCriteria, a tenant scope, an is_published guard. Don't push user input into criteria — you'll lose the single place where input is validated.

Fluent, then terminal. The repository is a mutable query builder: with(...), orderBy(...), addCriteria(...) all decorate state, then findOne / findAll / paginate / search consume it. The idiomatic shape is a named method on the repository that composes the decoration and ends with the terminal call:

public function searchVisible(array $queryParams, int $perPage): Paginator
{
    $this->orderBy('sort');
    $this->with('categories');

    return $this->search($queryParams, $perPage);
}

This keeps controllers one line and keeps query intent named and testable.

search() is the "filter + paginate" shortcut. With use Searchable and SearchableRepositoryInterface, search($params, $perPage) routes through getFilterManager() → applyFilter($params) → pagination. The upshot: your filter IS your search implementation. Anything you want searchable has to be handled in applyFilter, and a filter that ignores a param silently drops it.

findList('id', 'name') for selects/dropdowns, paginate() for index endpoints, findOne (nullable) vs findOneOrFail (throws ModelNotFoundException) for show endpoints. Add @method docblocks on the interface for the generic return types — the package ships PHPStan level 8, so mirroring that discipline pays off immediately in your own static analysis.

Locking belongs in a transaction. lockForUpdate() / sharedLock() only mean something inside DB::transaction(...); use them in queued jobs and balance mutations, not in web controllers.

Testing tip: don't mock the repository with Mockery. Bind a fake implementation of the interface in the test container, or point the binding at a real repository against SQLite. Mocking the fluent interface is misery because every call returns $this.

Gotchas and Tips

  • The README's PostFilter example has a syntax error. ->compare('amount', '<=' $queryParams['max_amount'] ?? '') is missing a comma. Copy-pasting it will produce a parse error on the first run — fix to '<=', $queryParams['max_amount'] ?? ''.
  • getFilterManager() is abstract-ish. If you use Searchable but don't implement getFilterManager() returning a filter with setQuery($this->getQuery()), search() fails at runtime. The setQuery call is easy to forget and produces confusing "no query" behaviour.
  • Repository state is mutable — never bind as a singleton. addCriteria, orderBy, and with accumulate on the instance. Under Octane, in long-running queue workers, or with $this->app->singleton(...) bindings, criteria and orderings leak between requests/jobs. Use bind (default), and if a repository method adds criteria, be aware a second call on the same instance stacks them.
  • Order matters for criteria vs. filters. Criteria applied after applyConditions/filter clauses append WHERE clauses; applying a criteria before a with() doesn't change joins but does change what findOne matches (it can pivot "first matching" results). Add criteria first, decorate relations last.
  • Pagination config is env-driven. After v0.3.5 the page size is no longer a constructor argument — it comes from config/repository.php (env var) and is overridable per call. Passing an invalid page size throws InvalidArgumentException, so validate limit from the request (your controller example passes $request->integer('limit'), which is 0 when absent — make sure your repository treats 0 as "use default" or use a FormRequest with a min:1 rule).
  • applyConditions isn't in the README's "Available Methods" list but is used in the examples. Read the base class before assuming what's available; the README lags the source.
  • The interface binding is a hard requirement, not a nicety. Without it, Laravel throws Target [XInterface] is not instantiable — and if you're on Laravel 11+, register the provider in bootstrap/providers.php, not config/app.php as the README shows (that snippet is stale for modern skeletons).
  • Config caching will bite you. The README's config:clear step is there for a reason; if you publish config after php artisan config:cache in a deploy, the change won't appear until you re-cache.
  • Extension points: subclass BaseEloquentRepository for shared behaviour across models, implement CriteriaInterface for reusable constraints, and add your own helpers to a BaseFilter subclass if you find yourself repeating whereHas blocks. The package is intentionally thin — that's the seam to build on.
  • Don't over-apply it. A repository that only forwards Model::find() adds a layer and an interface for nothing. This package earns its keep when you have repeated, composable query constraints across controllers — filtering, search, criteria, and locking. If a model has one or two simple queries, use Eloquent directly and keep the abstraction budget for where it pays.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource