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 Repositories Laravel Package

matthewbdaly/laravel-repositories

View on GitHub
Deep Wiki
Context7

Getting Started

Treat this package as a reference implementation, not a framework dependency. It is a handful of classes — a base Eloquent repository, an interface, and a caching decorator — last tagged in mid-2018. The value is the pattern and the ~150 lines of code you can read in ten minutes, not the composer require.

Minimal setup

  1. composer require matthewbdaly/laravel-repositories
  2. Immediately check vendor/matthewbdaly/laravel-repositories/composer.json. On Laravel 9/10/11 the illuminate/* constraint will almost certainly block installation. If it does, either pin the package in a legacy service or copy src/ into app/Support/Repositories/ and own it. Given the size, copying is usually the right call.
  3. Read src/ before writing anything. There are only a few files: the interface, the base repository, and the cache decorator. Confirm how the base class resolves the model (a protected $model property vs. a model() method — this differs between the author's packages and blog posts) and which methods the interface actually declares.

First repository

namespace App\Repositories;

use App\Models\User;
use Matthewbdaly\LaravelRepositories\Repositories\BaseRepository;

class EloquentUserRepository extends BaseRepository
{
    protected $model = User::class; // or: public function model() { return User::class; }
}

Bind it once, in a dedicated RepositoryServiceProvider:

$this->app->bind(
    \App\Repositories\UserRepositoryInterface::class,
    \App\Repositories\EloquentUserRepository::class
);

First use case

Start with a single read-heavy controller — a listing page or an API endpoint — and inject the interface, never the Eloquent class. The payoff only appears when you swap the concrete binding (cache decorator on/off, different persistence) without touching the consumer. If you can't name the second implementation you'd ever bind, skip the abstraction and use Eloquent directly.

Implementation Patterns

Interface-first, base-class-second. Define your own UserRepositoryInterface extending the package interface, so the package's method surface is an implementation detail you can replace later. The package interface is a floor, not a contract you should leak into controllers.

Constructor injection everywhere. Bind the interface in a service provider and type-hint it in controllers, jobs, and console commands. The base repository is a plain class — it resolves cleanly from the container with no facade magic, which makes it trivially mockable:

$this->mock(UserRepositoryInterface::class, function ($mock) {
    $mock->shouldReceive('find')->with(1)->andReturn(new User(['id' => 1]));
});

Caching decorator — wrap, don't inherit. The decorator pattern here is the genuinely useful bit. Compose it around the real repository rather than adding cache logic to the base class:

$this->app->bind(UserRepositoryInterface::class, function ($app) {
    return $app->make(CacheableUserRepository::class);
});

Where the cacheable implementation holds an inner UserRepositoryInterface and delegates. Verify the decorator's constructor signature against source — you may need to pass the inner repository and a cache store/TTL explicitly.

Invalidate on write, not on read. Reads go through Cache::remember; create/update/delete must flush the affected keys (or the whole tag/store). If the shipped decorator doesn't do this, that is the first thing you extend — a stale-cache bug is the single most likely production incident with this package.

Eager loading via a $with property. Most base repositories of this vintage support a protected $with = ['posts'] applied to every query. Use it for the two or three relations every caller needs; pass per-call with() for the rest. Avoid making it a dumping ground — it silently affects every query in the app.

Service-layer wrapper for multi-step writes. The base repository is CRUD-shaped. Anything transactional (create order + line items + decrement stock) belongs in an action/service class that calls the repository, not in a createWithItems() method bolted onto the repository. Keeping the repository thin is what makes the interface swappable.

Testing. Bind the array cache driver in phpunit.xml so the decorator doesn't leak state between tests, and assert on the repository interface in unit tests rather than hitting the database through the base class. Use RefreshDatabase for the Eloquent-level tests only.

Gotchas and Tips

The package is effectively abandoned. Twelve stars, last release June 2018, no Laravel 6+ activity. Budget for it breaking on PHP 8 (illuminate/support constraint) and for security scanners flagging an unmaintained dependency. The correct long-term move is to fork the two or three classes into your own namespace. Do that on day one unless you have a hard reason not to.

It is not persistence-agnostic. The base class is Eloquent to the core — it returns Collections, Model instances, and LengthAwarePaginators. The interface buys you testability and cache-swapping, not a future move to an API or document store. Don't sell it internally as a portability layer.

Model resolution quirks. If model resolution goes through app($this->model) rather than new $this->model, the container will inject constructor dependencies into your models — occasionally useful, frequently surprising. Check BaseRepository's constructor and any newQuery() helper before relying on either behaviour.

Cache key collisions. If the decorator derives keys from method name plus arguments, two repositories sharing a key namespace will collide (find(1) for users vs. posts). Namespace keys by repository class. If the driver supports tags, tag by model; if it's file or database, tags silently don't work in older Laravel — verify your driver.

Debugging stale reads. First suspect the decorator, not Eloquent. Confirm the binding order in the provider (the decorator must wrap the inner repo, not itself), then Cache::flush() locally and re-run. Log the generated key and TTL once — it's the fastest way to spot a missing invalidation.

Bypass the cache deliberately. Provide an escape hatch (a fresh() or direct Eloquent call) for admin screens and post-write redirects, otherwise users will see their own edits disappear for the TTL window.

Extension points worth knowing. Override the model property/method, the base query builder hook (newQuery()), the eager-load $with array, and — in the decorator — the key generator and TTL. Those four cover ~95% of real customization. Anything beyond that, write your own method rather than fighting the base class; it has no documented extension contract.

Don't decorate everything. Caching a repository whose reads are already cheap (single indexed find()) adds a cache round-trip and an invalidation bug surface for no gain. Measure with a query log first; decorate the listing/search paths where the win is real.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package