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.
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.
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.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.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.?limit=. If a client can pass limit=1000000, cap it — the package honours the request value and there's no built-in maximum.'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.$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".validator() returns nothing, create/update skip validation entirely rather than failing loudly — easy to think you have validation when you don't.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.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).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.How can I help you explore Laravel packages today?