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

L5 Repository Laravel Package

sevenymedia/l5-repository

A Laravel repository pattern package offering base repositories, reusable query criteria, presenters, validators, and caching helpers to keep your application's data layer clean, swappable, and testable.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a maintained fork of prettus/l5-repository (the repository/ presenter/ validator pattern package). It keeps the Prettus\Repository\* namespace, so existing use statements and docs from the upstream package still apply — only the Composer package name differs. Release 1.3.0 is a compatibility bump: it adds Laravel 5.8 support (which means a PHP >= 7.1.3 constraint in practice). No API changes, so this assessment describes the package as a whole.

Install and wire up

composer require sevenymedia/l5-repository
# do NOT also require prettus/l5-repository - same namespace, conflicting classes

On Laravel 5.5+ the provider is auto-discovered; otherwise add to config/app.php:

Prettus\Repository\Providers\RepositoryServiceProvider::class,

Publish the config (this is the first file you should read):

php artisan vendor:publish --provider="Prettus\Repository\Providers\RepositoryServiceProvider"
# -> config/repository.php

First use case — scaffold an entity

php artisan make:entity Post
# creates app/Repositories/PostRepository.php + PostRepositoryEloquent.php
# optionally Presenter, Transformer, Validator, Model, Controller, migration, seeder

Then bind interface → implementation (an AppServiceProvider or a dedicated RepositoryServiceProvider):

$this->app->bind(PostRepositoryInterface::class, PostRepositoryEloquent::class);

And inject the interface into your controller:

public function __construct(PostRepositoryInterface $repository)
{
    $this->repository = $repository;
    $this->repository->pushCriteria(app(RequestCriteria::class));
}

Order of files to read: the generated PostRepositoryEloquent (it's just a BaseRepository subclass with a model() method and $fieldSearchable), then config/repository.php, then Prettus\Repository\Criteria\RequestCriteria, which is what most day-to-day behaviour (search/filter/sort/pagination) flows through.

Implementation Patterns

Thin interface, thin Eloquent implementation. Keep the interface as the contract your controllers/services/commands type-hint, and let the Eloquent class stay declarative:

class PostRepositoryEloquent extends BaseRepository implements PostRepository
{
    protected $fieldSearchable = [
        'title'        => 'like',   // operator matters
        'status'       => '=',
        'author.name'  => 'like',   // dot notation traverses relations
    ];

    public function model(): string
    {
        return Post::class;
    }
}

Search/filter/sort for free via RequestCriteria. Pushing it in the controller constructor gives you ?search=, ?filter[status]=published, ?orderBy=created_at&sortedBy=desc, ?limit=50 with no extra code. The accepted parameter names are configurable in config/repository.php. That is the single highest-leverage pattern in the package — one line gives a list endpoint real querying.

Custom criteria for anything non-trivial or reused. Criteria are the right extension point instead of fat repository methods:

class PublishedOnly implements CriteriaInterface
{
    public function apply($model, RepositoryInterface $repository)
    {
        return $model->where('status', 'published');
    }
}

$posts = $this->repository->pushCriteria(new PublishedOnly())->paginate(20);

Criteria execute in push order, are introspectable (getCriteria()), and removable by class (popCriteria(PublishedOnly::class)) or wholesale (skipCriteria()), which makes them composable per-endpoint and testable in isolation.

Presenters/transformers for API shaping. Extend FractalPresenter, implement transform() returning a PostTransformer, set it on the repository, and transform() at call time. Use this when response shape must be stable and decoupled from the schema; use skipPresenter() when you want raw Eloquent models (e.g. for a Blade view or a form).

Validation at the repository boundary. Give the repository a Prettus\Validator\LaravelValidator subclass with create/update rule sets; create() and update() will validate and throw Prettus\Validator\Exceptions\ValidatorException. Catch it in the controller and redirect()->withErrors($e->getMessageBag()). Useful for admin/console write paths that bypass FormRequests.

Caching when reads dominate. Implement CacheableInterface and use the CacheableRepository trait, set $cacheMinutes, and configure repository.cache.enabled in the config. Cache keys incorporate the method plus arguments plus serialized criteria, so distinct criteria combinations are cached separately — but see the caveats below.

Repository events for side effects. The Eloquent base class fires RepositoryEntityCreated / Updated / Deleted, which is a clean hook for search-index syncs, audit logs, and cache flushing without touching the repository itself.

Testing. Bind the interface to a fake or an in-memory implementation in a test service provider; because controllers depend on the interface, the controller tests never touch the repository implementation at all. For integration tests, disable caching (config(['repository.cache.enabled' => false])) and skipCriteria() so no request state leaks in.

Gotchas and Tips

  • RequestCriteria is request-coupled. It reads the current HTTP request; in queued jobs, console commands, or long-running workers a stale request can supply bogus filter/search params. Call skipCriteria() or popCriteria(RequestCriteria::class) in non-HTTP entry points.
  • Presenters change return types. With a presenter set, all()/paginate() return plain arrays and find()/first() return arrays or null — code that assumed an Eloquent model ($post->save(), $post->relation) breaks. Reach for skipPresenter() explicitly rather than debugging a confusing null.
  • scopeQuery() persists on the instance. If the repository is bound as a singleton or resolved once per request, a scopeQuery closure can leak into later calls. resetScope() after use, or prefer criteria (which have explicit pop/skip semantics).
  • lists() is gone — use pluck(). Older tutorials still show lists($value, $key); it will fatal on modern Illuminate versions.
  • Cache invalidation is repository-local. The cacheable trait flushes on writes it performs, not on seeders, direct Model::save(), bulk DB queries, or writes from other repositories. If your data changes outside the repository, you need explicit invalidation (an event listener, or a cache-key versioning scheme). Also decide deliberately what cacheMinutes of zero should mean for you and verify against your driver.
  • Pagination limit comes from ?limit=. If a client can pass limit=1000000, cap it — the package honours the request value and there's no built-in maximum.
  • Dot-notation search only works for declared relations. Relation search via 'author.name' => 'like' requires the relation to actually exist and be queryable; complex relation filters (nested whereHas with constraints) are usually cleaner as a custom criterion than as a $fieldSearchable entry.
  • Operators in $fieldSearchable are meaningful. = requires an exact match; like wraps with %...%. Getting this wrong is the most common cause of "search returns nothing for one field but works for others".
  • Validators are optional and silent. If validator() returns nothing, create/update skip validation entirely rather than failing loudly — easy to think you have validation when you don't.
  • Soft deletes are respected. delete() soft-deletes when the model uses SoftDeletes, and all() excludes trashed rows unless a criterion explicitly includes them.
  • make:entity is interactive. For scripted scaffolding pass the flags (--model, --repository, --presenter, --transformer, --validator, --controller, --migration, --seeder) or use --force; generated namespaces/folders come from config/repository.php's generator section, so adjust that first if your project layout differs.
  • Fork hygiene. Because the namespace is still Prettus\, upstream README examples work verbatim, but the Composer package identity differs: don't install both, and if you're migrating from prettus/l5-repository, remove it in the same composer require step (or add a replace) so Composer doesn't resolve two providers with clashing classes. Pin versions against your Laravel version — this fork's releases track Laravel support (1.3.0 → 5.8).
  • Extension points worth knowing: override parserResult() in a BaseRepository subclass to normalise every result; subclass BaseRepository for shared behaviour across repositories; swap repository.cache.repository for a custom cache class; register additional accepted request params in config/repository.php rather than patching RequestCriteria.
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.
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus