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

Repository Laravel Package

caffeinated/repository

A Laravel repository pattern package that adds a repository layer over Eloquent models, with support for criteria, caching, and eager loading. Keep controllers lean and queries reusable by moving data access into dedicated repository classes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install (carefully). composer require caffeinated/repository — but check the result before you celebrate. The last release was September 2018, which puts its illuminate/* constraints in the Laravel 5.6/5.7 era. On Laravel 8+ you will likely hit an unresolvable dependency graph. The package is two or three small classes, so the pragmatic move on a modern app is to copy src/Contracts/Repository.php, src/Repositories/EloquentRepository.php (and Traits/Cacheable.php if you want it) into app/Repositories/ and adjust namespaces. You get the same API with none of the version risk.

Look here first. Read these three files in this order — the whole package is smaller than most service classes:

  1. src/Contracts/Repository.php — the interface you will type-hint and mock.
  2. src/Repositories/EloquentRepository.php — the abstract base. This is the real documentation; there is no hosted docs site anymore. Skim the public methods to see exactly what query helpers exist in your vendored version.
  3. src/Traits/Cacheable.php — an opt-in read-cache trait, if present in your copy.

First use case. A repository subclass bound to a contract, resolved from the container, injected into a controller:

namespace App\Repositories;

use App\Models\User;
use Caffeinated\Repository\Repositories\EloquentRepository;

class UserRepository extends EloquentRepository
{
    protected $model = User::class;
}

Bind it in a service provider ($this->app->bind(UserRepository::class) or, better, against an interface), then constructor-inject it. The base class resolves the model lazily from the container and proxies query building to it. That is the entire value proposition: a seam between your controllers/services and Eloquent.

Implementation Patterns

Interface-first binding. The interface is the point of the package. Define your own contract extending the package one, and bind the concrete class:

// app/Repositories/Contracts/UserRepository.php
interface UserRepository extends \Caffeinated\Repository\Contracts\Repository {}

// AppServiceProvider::register()
$this->app->bind(
    \App\Repositories\Contracts\UserRepository::class,
    \App\Repositories\UserRepository::class
);

Now controllers and jobs depend on the contract; tests swap in a fake without touching a database.

Query chaining for reads. The Eloquent base returns $this from builder-style calls so you can compose:

$users = $this->users
    ->with(['posts', 'roles'])
    ->where('active', true)
    ->orderBy('created_at', 'desc')
    ->paginate(25);

Always reset() (or whatever your version calls the reset method) between chained calls, or you leak constraints into the next query on the same injected instance.

Closure-scoped queries. For anything the fluent proxy does not cover, use the built-in scope hook rather than punching a hole in the abstraction:

$repo->scopeQuery(function ($query) {
    return $query->whereHas('subscription', fn ($q) => $q->where('status', 'active'));
})->get();

Keep these closures small. If a scope closure grows past three lines, the logic belonged in a query object or a model scope instead.

Cacheable reads. Where the trait exists, apply it to hot, read-mostly repositories (settings, categories, permissions) and set the cache key and TTL as class properties. Treat writes as the invalidation trigger and verify explicitly what your version flushes — see Gotchas.

Per-module repositories. The package's original home was caffeinated/modules. If you are maintaining a modular app of that vintage, this is where it fits: one Repositories/ folder per module, each binding its contract in the module's service provider. That is the strongest remaining argument for using it.

Testing seam. Because everything routes through the contract, $this->mock(UserRepository::class) in a feature test replaces Eloquent entirely. This is the day-to-day payoff — assert your controller calls find() with the right ID instead of booting a database.

Gotchas and Tips

It is archived. Plan for that. No security patches, no Laravel 8/9/10 support, no lists() (removed in Laravel 5.3 — calls will fatal), and pluck-era semantics throughout. Do not add this to a greenfield Laravel 10/11 app; copy the two files or, honestly, use Model::query() in a thin service class. The repository pattern earns its keep in large, long-lived, multi-database codebases — this package is a convenience, not a framework.

No Criteria objects. Unlike l5-repository/andersao, this package has no composable Criteria classes. Filtering lives in scope closures or your own methods. If you need Criteria, you need a different package or you need to write them.

Return-type surprises. Most implementations mirror Eloquent: find() returns null on a miss rather than throwing. Confirm this against your vendored copy before writing catch (EntityNotFoundException) — that pattern exists in rival packages and does not reliably apply here.

$model must be the fully-qualified class name string, not an instance and not a ::class reference to a bound abstraction. Passing a container alias that resolves to a non-Eloquent class produces confusing "method does not exist" errors deep in the proxy. Debug with $repo->getModel() (or dd() the property) first when things blow up.

Caching is not self-invalidating by default. Confirm whether create/update/delete flush the cache key in the Cacheable trait version you have; in practice you will usually need to call the flush explicitly after writes, or key the cache per-entity (user.{$id}) so a stale row is impossible to serve. This is the single most common source of "works on my machine, wrong in production" bugs with this package.

Resetting state. A repository resolved as a singleton and reused across a request accumulates where clauses. Either bind it as a fresh instance (bind, not singleton) or be disciplined about resetting after each chain.

Repositories do not fix N+1. Eager-loading still has to be explicit via with(). Wrapping Eloquent in a repository changes nothing about query count — check with DB::listen() or Telescope during review.

Extension points are plain PHP. There is no event system or plugin surface: override a method in your subclass, add your own methods, or extend through the scopeQuery closure. That is the whole vocabulary — which is also why copying the source is a viable strategy.

Migration path. If you are modernizing, the usual destination is a 40-line abstract repository you own plus Laravel's Model::query(), or a light spatie/laravel-query-builder setup for filtering. Expect an afternoon per repository to unwire the interface bindings; the contract-first structure ports cleanly even though the base class does not.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings