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

naldi/l5-repository

Laravel package that implements the Repository pattern, giving you an abstraction layer over your data source. It ships with criteria, presenters, validators, caching, Fractal transformers, and Artisan generators to keep your app clean and testable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire up (5 minutes)

composer require naldi/l5-repository

On Laravel 5.5+ the service provider is auto-discovered. On older versions add to config/app.php:

Prettus\Repository\Providers\RepositoryServiceProvider::class,

Publish the config so you can tune query-string params later:

php artisan vendor:publish --provider="Prettus\Repository\Providers\RepositoryServiceProvider"

Note: this package is a maintained fork of prettus/l5-repository and keeps the Prettus\Repository\* namespace. That means it's a drop-in replacement — verify the namespace after install with composer show naldi/l5-repository and check vendor/naldi/l5-repository/src.

Scaffold your first repository

php artisan make:entity Post

The command is interactive; it can generate the model, migration, repository interface, Eloquent repository, and a service-provider binding stub. If you prefer to stay manual:

php artisan make:model Post -m
php artisan make:repository Post

Then fill in the two generated classes:

// app/Repositories/PostRepository.php (interface)
namespace App\Repositories;

use Prettus\Repository\Contracts\RepositoryInterface;

interface PostRepository extends RepositoryInterface {}
// app/Repositories/Eloquent/PostRepositoryEloquent.php
namespace App\Repositories\Eloquent;

use App\Models\Post;
use App\Repositories\PostRepository;
use Prettus\Repository\Eloquent\BaseRepository;

class PostRepositoryEloquent extends BaseRepository implements PostRepository
{
    protected $fieldSearchable = [
        'title'  => 'like',
        'status' => '=',
    ];

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

    public function boot(): void
    {
        $this->pushCriteria(app(\Prettus\Repository\Criteria\RequestCriteria::class));
    }
}

Bind the interface in a service provider:

$this->app->bind(
    \App\Repositories\PostRepository::class,
    \App\Repositories\Eloquent\PostRepositoryEloquent::class
);

First use case — a controller that never touches Eloquent:

class PostController extends Controller
{
    public function __construct(private PostRepository $posts) {}

    public function index()
    {
        return $this->posts->paginate(15);
    }
}

GET /posts?search=laravel&order=created_at|DESC now filters and sorts with zero controller code.

Where to look first

  1. config/repository.php — controls search, filter, order, sort, include param names and the searchable-field conventions.
  2. vendor/naldi/l5-repository/src/Eloquent/BaseRepository.php — every public method; skim it once, it's the whole API surface.
  3. src/Criteria/RequestCriteria.php — the class you'll debug most often.

Implementation Patterns

Interface + Eloquent pair per aggregate. One repository per aggregate root (not per table). Keep the interface free of Eloquent types so you can swap in a cache decorator or an in-memory implementation for tests.

RequestCriteria as the default listing pipeline. Push it in boot() and every paginate() / all() picks up query-string filtering. Supported syntax out of the box:

?search=laravel            # searches $fieldSearchable
?search=laravel&searchFields=title:like;body:like
?filter=status:published;author_id:3    # exact match (or per 'condition' in config)
?order=created_at|DESC&sort=title|asc
?include=author,comments                # eager loads relations

Custom criteria for domain filters. Anything that isn't a naive column match belongs in a criteria class, not a controller:

class PublishedOnlyCriteria implements CriteriaInterface
{
    public function apply($model, RepositoryInterface $repository)
    {
        return $model->where('status', 'published')
                     ->whereNotNull('published_at');
    }
}
// call site
$this->posts->pushCriteria(new PublishedOnlyCriteria)->paginate(15);

Criteria are cumulative and applied in push order — order matters when a criterion depends on a join or a select alias.

Scopes for one-off query shapes. scopeQuery lets you drop to the builder without losing the repository contract:

$this->posts->scopeQuery(function ($query) {
    return $query->whereHas('author', fn ($q) => $q->where('active', 1));
})->paginate(20);

Presenters/transformers for API output. Return a Fractal presenter from presenter() and the repository will shape every collection/entity — including paginate(), which returns a data + meta structure instead of a raw paginator:

public function presenter(): string
{
    return PostPresenter::class;
}

// when you need raw Eloquent (jobs, exports, internal service):
$this->posts->skipPresenter()->all();

Caching for read-heavy lookups. Add the trait and let the repository handle invalidation on create/update/delete:

use Prettus\Repository\Traits\CacheableRepository;

class PostRepositoryEloquent extends BaseRepository implements PostRepository
{
    use CacheableRepository;

    protected $cacheMinutes = 60;
    protected $cacheExcept  = ['paginate']; // be careful here
}

Cache keys are derived from the method name plus serialized arguments plus active criteria — so pushCriteria(new FooCriteria) produces a different key automatically.

Repository events for cross-cutting side effects. The base class dispatches RepositoryEvent::creating/created/updating/updated/saving/saved/deleting/deleted, which fire before/after the persistence call (unlike Eloquent observers, which can't see the repository's criteria or the pre-save payload):

$this->posts->create($data); // fires creating + created

Validation hooks. validator() integrates with prettus/laravel-validation; if you use it, add that package explicitly — it's not always pulled in transitively. Many teams skip it and validate in FormRequests instead, which keeps the repository free of HTTP concerns.

Testing tip. Because consumers type-hint the interface, a fake repository is trivial:

$this->app->instance(PostRepository::class, new InMemoryPostRepository());

No database, no RefreshDatabase for controller tests.

Gotchas and Tips

$fieldSearchable or searchableFields() is mandatory. If RequestCriteria is pushed and the model has no searchable fields defined, ?search=foo throws. Define the array in the repository, or override searchableFields() if the list needs to be dynamic.

scopeQuery is consumed and reset. You cannot chain two scopeQuery calls — each one applies and then resets the scope. Combine them into a single closure, or express one as a criteria.

Custom methods bypass criteria. Any method you write that touches $this->model directly must call $this->applyCriteria() and $this->applyScope() first:

public function activeCount(): int
{
    $this->applyCriteria();
    $this->applyScope();
    $count = $this->model->count();
    $this->resetModel();
    return $count;
}

Forgetting resetModel() leaks state into the next call — the repository reuses a single model instance across the request.

model() must return a class-string. Returning $this or an instance causes "Class name must be a valid object or a string" at construction. Return Post::class.

Presenter changes the return shape. paginate() with a presenter no longer returns a LengthAwarePaginator — code doing ->links() in Blade will break. Use skipPresenter() for HTML views, presenters for JSON APIs.

Criteria ordering and leakage. pushCriteria() mutates the repository instance for the rest of the request. If a controller pushes a criterion and another controller call reuses the same singleton, the filter persists. Use resetCriteria() after a scoped query, or resolve a fresh instance.

Cache invalidation edge cases. The CacheableRepository trait flushes on repository events, but it does not know about writes that bypass the repository (raw DB::table() updates, queue workers on other connections, Eloquent calls in other services). If your app mixes access paths, either route all writes through the repository or drop caching for that model.

Case-insensitive search is a database concern. RequestCriteria uses LIKE %term%; on PostgreSQL this is case-sensitive. Switch the field config to ilike semantics with a custom criterion or use LOWER() manually — the package won't do it for you.

Generator namespaces are config-driven. If you move repositories out of App\Repositories, update repository.generator.rootNamespace and the paths section in config/repository.php, otherwise make:repository writes to the wrong place and autoloading fails silently.

Version pinning matters. This is a fork tracking a package with a long history against Laravel 5→11+. Pin the constraint in composer.json ("naldi/l5-repository": "^2.6" or whatever your installed major is) and re-read BaseRepository after upgrades — method signatures (e.g. paginate()'s third $method argument, model() return types) have changed across majors.

Don't over-repository. The pattern pays off when you have multiple consumers of the same query (API + console + queue) or need swappable data sources. For a CRUD controller over one table, a plain Eloquent scope is less code and less indirection. Keep repositories thin: query composition, eager loading, caching — not business rules, not HTTP validation, not response formatting.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core