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

Cache Repository Laravel Package

drapor/cache-repository

drapor/cache-repository provides a clean repository-style layer over Laravel caching, letting you store, retrieve, and invalidate cached data through a simple, consistent interface. A lightweight package for keeping cache logic organized and easy to swap or extend.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin

composer require drapor/cache-repository

Before writing any code, open vendor/drapor/cache-repository/composer.json and confirm the Laravel and PHP constraints. This package has very few stars and zero dependents, so it has not been battle-tested across framework versions; pin it to an exact version ("drapor/cache-repository": "1.x" or a commit hash) rather than trusting ^ resolution.

Read the source first

There is no meaningful documentation surface for a package this small. Spend ten minutes on:

ls vendor/drapor/cache-repository/src
cat vendor/drapor/cache-repository/composer.json

You are looking for: the base repository class, whether it is abstract, the cache key strategy (prefix, model class, md5 of query), whether it uses Cache::remember or manual get/put, and whether it implements Illuminate\Contracts\Cache\Repository. Those four facts determine everything else below.

Register the service provider if it is not auto-discovered

Laravel 5.5+ auto-discovers via extra.laravel.providers. If that key is absent, add the provider manually in config/app.php and check whether a config file exists to publish:

php artisan vendor:publish --provider="Drapor\CacheRepository\CacheRepositoryServiceProvider"

First use case: a hot, rarely changing read

The best first target is a query that is read on every request but written rarely — a settings table, a list of categories, a navigation menu. Build one repository, point it at one model, give it a short TTL, and measure. Do not start by wrapping your largest, most write-heavy aggregate; you will spend the day debugging invalidation instead of learning the API.

namespace App\Repositories;

use App\Models\Category;
use Drapor\CacheRepository\...; // confirm the exact class name in src/

class CategoryRepository extends BaseCacheRepository
{
    protected function model(): string
    {
        return Category::class;
    }

    protected function ttl(): int
    {
        return 300; // seconds
    }
}

Then bind it in a service provider so controllers depend on the abstraction, not the concrete class:

$this->app->bind(CategoryRepository::class, fn () => new CategoryRepository());

Implementation Patterns

One repository per aggregate, not one per model

Treat the repository as the cache boundary for a single aggregate root. If Order and OrderItem are always read together, they belong in one repository with one key namespace. Splitting them creates two caches that can disagree.

Centralize key construction

Every stale-data bug you will hit traces back to a key that was built in two places. Put key generation in one protected method and never hand-write a cache key outside it:

protected function key(string $operation, array $params = []): string
{
    return sprintf(
        '%s:%s:%s',
        config('cache.prefix'),
        class_basename($this->model()),
        $operation . ':' . md5(serialize($params))
    );
}

Including cache.prefix means php artisan cache:clear and per-environment Redis databases behave as expected.

Invalidate on write, inside the same transaction boundary as the write

The safest pattern is model events or an observer, not manual calls scattered across controllers:

protected static function booted(): void
{
    static::saved(fn () => Cache::tags(['categories'])->flush());
    static::deleted(fn () => Cache::tags(['categories'])->flush());
}

If the package does not support tags, fall back to versioned keys — store an integer version per aggregate in the cache and include it in the key, incrementing it on write. That is the only invalidation strategy that works correctly with the file and database drivers, neither of which supports tags.

Read-through with explicit TTL per method, not per repository

Different methods have wildly different tolerance for staleness. A find($id) can live for an hour; a paginate() filtered by user input should probably not be cached at all. Expose TTL as a parameter defaulted at the method level rather than a single class property:

public function find(int $id, int $ttl = 3600): ?Model
{
    return $this->remember($this->key('find', [$id]), $ttl, fn () => $this->model()::find($id));
}

Bind to an interface and fake it in tests

Because a caching repository is a behavior change, not just a performance tweak, bind an interface (CategoryRepositoryInterface) and provide a non-caching implementation for tests. Otherwise your feature tests will pass or fail depending on residual cache state from a previous test.

Bypass for writes and admin screens

Give the repository an escape hatch — a fresh() or withoutCache() variant — and use it in admin panels, exports, and any place where "I just saved it, show me the new value" matters. This one method will save you more debugging time than any other line in the package.

Gotchas and Tips

Zero dependents means you are the QA team

With 12 stars and no downstream users, assume the package was written against one Laravel version and never revisited. Check the illuminate/* constraints and test against your actual framework version before rolling it into anything beyond a prototype. If the constraint is loose (^8.0|^9.0), that is not evidence it works — it is evidence nobody checked.

Serialized Eloquent models go stale silently

Caching model instances means relationships, accessors, and appended attributes are frozen at serialization time. If you later change an accessor or add a relationship, cached entries keep returning the old shape until they expire. Cache arrays or DTOs for anything with derived attributes, and always ->fresh() after retrieval if you need live relations.

Tags are driver-dependent

Cache::tags() throws on file and database drivers. If the package's invalidation relies on tags, it will work in your Redis staging environment and fail in production if production uses a different driver. Verify config/cache.php for every environment and prefer the versioned-key approach if you cannot guarantee Redis or Memcached.

Pagination and user-specific filters are cache poison

Any query whose result depends on request input (?page=, ?sort=, ?search=) will generate unbounded cache keys and blow up your Redis memory. Cap it: either do not cache filtered queries, or only cache them for a fixed, small set of enumerated filters. Add a max key count per repository and log when you exceed it.

Cache key collisions across tenants

If you run multi-tenant or per-user scopes, the model class alone is not a unique key. Include the tenant or user ID in every key, ideally via a keyScope() method the repository calls, or you will serve one tenant's data to another — a security bug, not a performance one.

Watch for the N+1 after the cache miss

The first request after expiration hits the database with whatever eager loading the repository applies. If the repository calls Model::all() without with(), you will get a thundering herd of N+1 queries on every cache expiry. Add with() in the repository's base query builder method, and consider a short lock (Cache::lock) around the read-through for expensive queries to prevent cache stampedes.

Debugging: log hits and misses

The package almost certainly does not ship observability. Wrap remember so you can log hit/miss and key name at debug level, then tail the log during a load test. If you cannot tell whether a key is being hit, you cannot tell whether the package is helping at all.

Testing

Use the array cache driver in phpunit.xml and call Cache::flush() in setUp(). Write one test that asserts a second identical read does not touch the database (DB::enableQueryLog() plus a query count assertion). That test is the entire value proposition of the package in one assertion.

Extension points to look for

Before you fork, check whether the base class exposes protected key(), ttl(), query(), and remember() methods. If it does, subclass and override — that is a clean seam. If everything is private or the class is final, do not fight it; copy the class into app/Support/ and own it. For a package this size, owning ~150 lines is often cheaper than tracking upstream changes that will never come.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel