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

mtferreira/repository

A Laravel repository pattern package with base repositories, criteria, presenters, validators, and caching to keep data access clean and testable. Generate repositories and interfaces via Artisan and swap implementations without changing application code.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a fork of Rinvex Repository (rinvex/repository, archived). The PHP namespace in your code is still Rinvex\Repository\*; only the Composer vendor/package name changed. Treat it as a pinned, frozen dependency, not something you expect upstream fixes for.

1. Install

composer require mtferreira/repository

Laravel auto-discovery registers the service provider. If a config file is published (check vendor/mtferreira/repository/src/Providers), publish it with:

php artisan vendor:publish --tag=rinvex.repository.config

Otherwise configure caching entirely per-repository via class properties.

2. Create your first repository

namespace App\Repositories;

use App\Models\User;
use Rinvex\Repository\Repositories\BaseRepository;

class UserRepository extends BaseRepository
{
    protected $model = User::class;

    // Optional: caching
    protected $cacheEnabled = true;
    protected $cacheMinutes = 60;          // TTL
    protected $cacheExcept  = ['password', 'remember_token'];
    protected $cacheKeys    = ['users'];   // cache tags, requires Redis/Memcached
}

BaseRepository resolves the model via the container (so $model can be a class string or a bound instance). Everything below is normal Eloquent under the hood.

3. First use case

$users = app(UserRepository::class);

$user   = $users->find(1);                 // cached
$active = $users->where('active', true)->get();  // cached
$new    = $users->create(['name' => 'Jane', 'email' => '[email protected]']); // flushes cache

Where to look first: BaseRepository (all the query/CRUD methods), the CacheableRepository trait (all caching behaviour), and any Events directory — the package fires created/updated/deleted events you can hook into.


Implementation Patterns

Interface binding + injection

The main payoff of a repository layer is testability. Bind an interface in a service provider:

// app/Providers/RepositoryServiceProvider.php
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);

Then inject into controllers, jobs, and commands. Because repositories are stateful (they accumulate where/with/scopes), bind them as transient (bind, not singleton) unless you're disciplined about resetScope().

Chained query building

The repository proxies Eloquent builder methods and returns $this, so you write familiar chains:

$posts = $posts->with(['author', 'comments'])
               ->where('published', true)
               ->orderBy('published_at', 'desc')
               ->paginate(20);

$count = $posts->where('status', 'draft')->count();
$emails = $posts->pluck('email');

Supported passthroughs include where, orWhere, whereIn, orderBy, take, skip, with, withCount, has, whereHas, plus aggregates (count, sum, avg, min, max) and paginate/simplePaginate.

Repetitive filters as Criteria

Push reusable predicates instead of repeating where chains:

class OnlyPublished implements RepositoryCriteriaContract
{
    public function apply($model) { return $model->where('published', true); }
}

$posts->pushCriteria(new OnlyPublished)->get();
$posts->popCriteria(OnlyPublished::class);

Criteria survive across calls until popped — this is both the feature and the footgun. Use resetCriteria() at the end of a request or in a middleware/terminator if your repo is long-lived (queues, Octane).

Ad-hoc filtering with scopeQuery

For one-off filters that don't deserve a Criteria class:

$repo->scopeQuery(fn ($q) => $q->whereBetween('created_at', [$from, $to]))->get();

Note: scopeQuery closures are used to build the cache key, so non-deterministic closures (closures capturing now() results, external state) will produce cache misses or colliding keys.

Cache strategy in practice

The package caches the result set, keyed by method + arguments + serialized scope/criteria:

class PostRepository extends BaseRepository
{
    protected $model = Post::class;
    protected $cacheEnabled = true;
    protected $cacheMinutes = 15;
    protected $cacheKeys = ['posts', 'cms'];
    protected $cacheExcept = [];   // attributes stripped from cached payloads
}

Day-to-day rules that keep you sane:

  • Tag everything. Set $cacheKeys and use Redis or Memcached. Tags are how you do targeted invalidation (flushCache()), and they're the only invalidation primitive the package gives you beyond full flush-on-write.
  • Cache read-heavy lookups, not everything. A CountryRepository::all() is a great candidate. A PostRepository::paginate() keyed on arbitrary page/scope combinations is not — you'll get a huge key space for little hit rate.
  • Turn caching off per-request for admin/back-office screens where freshness matters:
    $repo->skipCache()->all();   // bypass cache for this call
    
  • Flush explicitly after bulk operations the repository didn't see (raw queries, other services):
    $repo->flushCache();
    

create/update/delete/deleteWhere in the base repository already flush the repository's cache.

Handling writes and events

Writes delegate to Eloquent, so model events, observers, attribute casting, and boot-time traits all fire normally. The repository additionally dispatches RepositoryEntityCreated, Updated, Deleted, Restored, ForceDeleted — listen to those if you want repository-level side effects (search indexing, audit logs) without touching the model.

Soft deletes

$repo->delete($id);        // soft delete if the model uses SoftDeletes
$repo->forceDelete($id);   // hard delete
$repo->restore($id);

All three flush cache. There's no built-in "only trashed" helper — use scopeQuery(fn ($q) => $q->onlyTrashed()).


Gotchas and Tips

It's abandoned and archived. There are zero dependents listed and no upstream maintenance. Pin to an exact version in composer.json, and assume no compatibility work for Laravel 10+/11+ PHP 8.x unless the fork already did it. If you adopt it, plan a migration path: the repository pattern here is thin enough that replacing BaseRepository with your own Eloquent wrapper is a day or two of work — do that before it becomes load-bearing.

Cache tags require a tag-capable driver. $cacheKeys is a no-op (or an exception, depending on Laravel version) on file and database stores. If your local .env uses CACHE_DRIVER=file and production uses Redis, cache behaviour differs between environments — test invalidation on Redis.

Cache keys are coarse. The key is derived from method, args, criteria, and scope — not from limit/offset unless those are applied before the cached call in a way the key captures. Two consequences:

  • paginate() across pages can thrash the key space or return wrong pages.
  • Eager loads via with() on an already-populated key may return data hydrated without those relations.

Prefer caching narrowly-scoped, deterministic reads (find, findBy, findWhere, all on small tables).

all() will happily cache your entire table. On any table over a few thousand rows this is a memory bomb against Redis. Use skipCache() or a scoped scopeQuery.

Full flush on every write. Writes flush the repository's entire cache namespace, not a single entity. High-write, high-read tables will see near-zero hit rate. Either partition by tag ($cacheKeys = ['posts'] and separate read-optimized repos) or accept that caching only helps static lookups.

State leakage between calls. where, with, orderBy, pushCriteria, and scopeQuery all mutate the instance. In long-running workers, Octane, or a shared container binding you'll get filters from request N bleeding into request N+1. Always resetScope() / resetCriteria() in a finally block, or bind transiently.

find() returns null, not ModelNotFoundException. Controllers relying on Laravel's default 404 behaviour need explicit checks, or wrap with findOrFail—verify it exists in your pinned version before relying on it.

$cacheExcept is not a security boundary. It strips attributes from the cached payload, but if other code paths fetch the model uncached you'll see the field. Don't use it as a substitute for $hidden / API resources.

Closure-based criteria and cache keys. serialize() on a closure fails; the package handles it inconsistently across versions. If you mix scopeQuery with caching, test that you don't get a PHP error or an undifferentiated key that collides across requests.

Extension points that matter:

  • Override cacheKey() / getCacheKey() if you need a custom key (e.g. to include tenant ID).
  • Override model() to resolve the model from the container rather than a hardcoded $model.
  • Override flushCache() to add tag-level flushes for related repositories.
  • Subclass BaseRepository with a project-wide AppRepository base to add resetScope() teardown, logging, and query-time assertions in one place — this is the single highest-leverage thing you can do with an abandoned package.

Debugging tips: dump getCacheKey() output inside a test to see exactly what the repository thinks it's caching. Reproduce caching bugs in a Pest/PHPUnit test with Cache::flush() between assertions — this is the only reliable way to prove invalidation works before it bites you in staging.

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
shitoudev/permissionmanager