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 Base Repository Laravel Package

dannyweeks/laravel-base-repository

A base repository package for Laravel that reduces boilerplate when working with Eloquent models. It provides reusable CRUD methods, query helpers, eager loading, and caching so you can keep controllers and models lean and your data layer consistent.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is essentially one abstract class, so "getting started" is genuinely a five-minute job.

  1. Install and pin it. Last release was March 2016, so install the exact tag that resolves rather than floating a constraint:

    composer require dannyweeks/laravel-base-repository
    

    There is no service provider to register and no config file to publish — the package ships only Dannyweeks\Repositories\BaseRepository and an exception class.

  2. Look at the source first. Open vendor/dannyweeks/laravel-base-repository/src/Dannyweeks/Repositories/BaseRepository.php. It is short enough to read end-to-end in a few minutes, and that is the fastest way to learn the real method surface (find, findBy, findAll, findAllBy, paginate, create, with, scopeQuery, resetScope, getModel, newModel). Everything else in this assessment is commentary on that file.

  3. Create one repository. The only required configuration is the $model property:

    namespace App\Repositories;
    
    use App\User;
    use Dannyweeks\Repositories\BaseRepository;
    
    class UserRepository extends BaseRepository
    {
        protected $model = User::class;
    }
    
  4. Use it once, for real. Do not start by migrating every controller. Pick the read path with the most duplicated query logic and replace it:

    public function show(UserRepository $users, $id)
    {
        $user = $users->find($id, ['id', 'name', 'email']);
    
        abort_if(is_null($user), 404);
    
        return view('users.show', compact('user'));
    }
    

    Because the constructor takes no arguments, the container resolves the repository automatically when it is type-hinted — no binding needed.

First use case: treat BaseRepository as a home for query logic that would otherwise be copy-pasted across controllers. If a controller only ever calls User::find($id), the repository earns its keep the moment you add a second method like activeWithPosts(). Start there.

Implementation Patterns

Class-string $model, not an instance. Set protected $model = User::class;. The base class instantiates internally (newModel()), which keeps the underlying model fresh instead of sharing one stateful instance across every call on the repository.

Custom domain methods are the actual product. The inherited CRUD is boilerplate; the value is in named intent:

public function activeWithRecentPosts(int $perPage = 15)
{
    return $this->with('posts')
        ->scopeQuery(function ($query) {
            return $query->where('active', true)->latest();
        })
        ->paginate($perPage);
}

public function findByEmail(string $email)
{
    return $this->findBy('email', $email);
}

Controllers then read as business operations rather than query-builder archaeology.

Chaining with() for eager loading. with() mutates the query context and returns $this, so chain it before the terminal call (find, findAll, paginate). Do not call it after — the query has already run.

scopeQuery() for one-off constraints. Use it for ad-hoc filters in a controller or a one-off command, and reserve dedicated methods for anything used more than once. It accepts a closure receiving the Eloquent builder, so any builder API is available inside it.

Interface binding for testability. If you want to fake the repository in tests, define UserRepositoryInterface, bind implementation to interface in a service provider, and type-hint the interface. This is the only reason to add container configuration — do not add it otherwise.

Composition over inheritance depth. If two repositories share custom behaviour (e.g. a searchable trait), use traits on top of BaseRepository. Avoid multi-level inheritance chains; the base class is deliberately thin and there is no reason to build a hierarchy on it.

Register repositories as singletons only with care (see gotchas). The default — fresh instance per resolution — is the safe option.

Gotchas and Tips

  • It is unmaintained since 2016. Treat it as a Laravel 5.x-era class. It has no findOrFail, updateOrCreate, firstOrCreate, soft-delete helpers, or transaction wrapper. Plan to write those yourself in the concrete repository; do not assume parity with modern Eloquent conveniences.
  • State leaks across calls on the same instance. with() and scopeQuery() store state on the repository. In a standard per-request lifecycle this is invisible. In queue workers, Octane, or a repository registered as a singleton, the constraints from the previous job will silently apply to the next query. Call resetScope() after a scoped query, or bind the repository as a non-singleton (the default).
  • $model must be set or you get an exception. The abstract class throws if the property is missing or does not resolve to an Eloquent model. If you see that exception, you forgot the property in a subclass, not the parent.
  • The abstraction is leaky by design. Methods return Eloquent models and collections, so callers still depend on Eloquent. If your goal is swappable storage or DTO-shaped output, this package is the wrong tool — it is a query-logic container, not a persistence abstraction.
  • Eloquent events and observers still fire. The repository does not bypass model events, mass-assignment protection, or global scopes. Do not use it expecting to sidestep $fillable.
  • Debugging tip: dump the SQL before the terminal call with dd($repo->getModel()->toSql()), or wire DB::listen() in a service provider. Because the builder is held internally, dd() on the repository itself is rarely useful.
  • Extension point: override newModel() in a subclass when you need to pre-configure the query (e.g. ->newQuery()->withTrashed() or a specific connection). This is the cleanest hook for cross-cutting model setup and avoids repeating it in every custom method.
  • Migration advice: do not refactor an existing codebase onto this. Introduce it for new repositories, and only back-fill where duplication is already painful. The package is small enough that, if it becomes a constraint, porting your repositories to a hand-rolled base class is an afternoon's work — keep that escape hatch in mind before letting it spread.
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