An open API service indexing awesome lists of open source software.

https://github.com/mikebronner/laravel-model-caching

Eloquent model-caching made easy.
https://github.com/mikebronner/laravel-model-caching

caching laravel laravel-5-package models

Last synced: 4 months ago
JSON representation

Eloquent model-caching made easy.

Awesome Lists containing this project

README

          

# πŸš€ Model Caching for Laravel

[![Laravel Package](https://github.com/mikebronner/laravel-model-caching/workflows/Laravel%20Package/badge.svg?branch=master)](https://github.com/mikebronner/laravel-model-caching/actions?query=workflow%3A%22Laravel+Package%22)
[![Packagist](https://img.shields.io/packagist/dt/GeneaLabs/laravel-model-caching.svg)](https://packagist.org/packages/genealabs/laravel-model-caching)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/mikebronner/laravel-model-caching/master/LICENSE)
[![PHP Version](https://img.shields.io/packagist/php-v/mikebronner/laravel-model-caching)](https://packagist.org/packages/mikebronner/laravel-model-caching)
[![Laravel](https://img.shields.io/badge/Laravel-11%20%7C%2012%20%7C%2013-FF2D20)](https://laravel.com)
[![Latest Stable Version](https://img.shields.io/packagist/v/mikebronner/laravel-model-caching)](https://packagist.org/packages/mikebronner/laravel-model-caching)
[![GitHub Stars](https://img.shields.io/github/stars/mikebronner/laravel-model-caching)](https://github.com/mikebronner/laravel-model-caching/stargazers)
[![codecov](https://codecov.io/gh/mikebronner/laravel-model-caching/graph/badge.svg?token=ACk1Kk4OLO)](https://codecov.io/gh/mikebronner/laravel-model-caching)
[![Tests](https://img.shields.io/badge/tests-335%2B-brightgreen)](https://github.com/mikebronner/laravel-model-caching/tree/master/tests)

![Model Caching for Laravel masthead image](https://repository-images.githubusercontent.com/103836049/b0d89480-f1b1-11e9-8e13-a7055f008fe6)

## πŸ—‚οΈ Table of Contents
- [πŸ“– Summary](#-summary)
- [πŸ“¦ Installation](#-installation)
- [πŸš€ Getting Started](#-getting-started)
- [βš™οΈ Configuration](#️-configuration)
- [🀝 Contributing](#-contributing)
- [⬆️ Upgrading](#️-upgrading)
- [πŸ” Security](#-security)
- [πŸ“š Further Reading](#-further-reading)

## πŸ“– Summary
Automatic, self-invalidating Eloquent model and relationship caching. Add a
trait to your models and all query results are cached automatically β€” no manual
cache keys, no forgetting to invalidate. When a model is created, updated, or
deleted the relevant cache entries are flushed for you.

⚑ Typical performance improvements range from 100–900% reduction in database
queries on read-heavy pages. πŸ§ͺ Backed by 335+ integration tests across PHP
8.2–8.5 and Laravel 11–13.

**Use this package when** your application makes many repeated Eloquent queries
and you want a drop-in caching layer that stays in sync with your data without
any manual bookkeeping.

### πŸ”„ Before & After

❌ **Without this package** β€” manual cache keys, manual invalidation:
```php
$posts = Cache::remember('posts:active:page:1', 3600, function () {
return Post::where('active', true)->with('comments')->paginate();
});

// And in every observer or event listener…
Cache::forget('posts:active:page:1');
// Hope you remembered every key variant! πŸ˜…
```

βœ… **With this package** β€” add the trait, query normally:
```php
// Just query. Caching and invalidation happen automatically. ✨
$posts = Post::where('active', true)->with('comments')->paginate();
```

### βœ… What Gets Cached
- Model queries (`get`, `first`, `find`, `all`, `paginate`, `pluck`, `value`, `exists`)
- Aggregations (`count`, `sum`, `avg`, `min`, `max`)
- Eager-loaded relationships (via `with()`)

### 🚫 What Does Not Get Cached
- Lazy-loaded relationships β€” only eager-loaded (`with()`) relationships are cached. Use `with()` to benefit from caching.
- Queries using `select()` clauses β€” custom column selections bypass the cache.
- Queries inside transactions β€” cache is not automatically flushed when a transaction commits; call `flushCache()` manually if needed.
- `inRandomOrder()` queries β€” caching is automatically disabled since results should differ each time.

### πŸ’Ύ Cache Drivers

| Driver | Supported |
|--------|-----------|
| Redis | βœ… (recommended) |
| Memcached | βœ… |
| APC | βœ… |
| Array | ❌ |
| File | ❌ |
| Database | ❌ |
| DynamoDB | ❌ |

### πŸ“‹ Requirements
- PHP 8.2+
- Laravel 11, 12, or 13

## πŸ“¦ Installation
```
composer require genealabs/laravel-model-caching
```

✨ The service provider is auto-discovered. No additional setup is required.

## πŸš€ Getting Started
Add the `Cachable` trait to your models. The recommended approach is a base
model that all other models extend:

```php
**⚠️ Note:** You can cache the `User` model β€” the `Cachable` trait does not
> conflict with Laravel's authentication. Just avoid using cache cool-down
> periods on it, and ensure user updates always go through Eloquent (not raw
> `DB::table()` queries) so cache invalidation fires correctly.

### 🌍 Real-World Example
Consider a blog with posts, comments, and tags:

```php
class Post extends BaseModel
{
public function comments()
{
return $this->hasMany(Comment::class);
}

public function tags()
{
return $this->belongsToMany(Tag::class);
}
}

// All cached automatically β€” the query, the eager loads, everything. πŸͺ„
$posts = Post::with('comments', 'tags')
->where('published', true)
->latest()
->paginate(15);
```

When a new comment is created, the cache for `Post` and `Comment` queries is
automatically invalidated β€” no manual `Cache::forget()` calls needed. 🧹

## βš™οΈ Configuration
Publish the config file:
```sh
php artisan modelCache:publish --config
```

This creates `config/laravel-model-caching.php`:

```php
return [
'cache-prefix' => '',
'enabled' => env('MODEL_CACHE_ENABLED', true),
'use-database-keying' => env('MODEL_CACHE_USE_DATABASE_KEYING', true),
'store' => env('MODEL_CACHE_STORE'),
'fallback-to-database' => env('MODEL_CACHE_FALLBACK_TO_DB', false),
];
```

### πŸ”§ Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `MODEL_CACHE_ENABLED` | `true` | βœ… Enable or disable caching globally. |
| `MODEL_CACHE_STORE` | `null` | πŸ’Ύ Cache store name from `config/cache.php`. Uses the default store when not set. |
| `MODEL_CACHE_USE_DATABASE_KEYING` | `true` | πŸ”‘ Include database connection and name in cache keys. Important for multi-tenant or multi-database apps. |
| `MODEL_CACHE_FALLBACK_TO_DB` | `false` | πŸ›‘οΈ When `true`, falls back to direct database queries if the cache backend is unavailable (e.g. Redis is down) instead of throwing an exception. |

> **πŸ“ Note:** The `cache-prefix` option is set directly in the config file (not via
> an environment variable). For dynamic prefixes (e.g. multi-tenant), use the
> per-model `$cachePrefix` property shown below.

### πŸ’Ύ Custom Cache Store
To use a dedicated cache store for model caching, define one in
`config/cache.php` and reference it:
```
MODEL_CACHE_STORE=model-cache
```

### 🏷️ Cache Key Prefix
For multi-tenant applications you can isolate cache entries per tenant. Set the
prefix globally in config:
```php
'cache-prefix' => 'tenant-123',
```

Or per-model via a property:
```php
where('active', true)->get();
```

**2. Globally via environment:**
```
MODEL_CACHE_ENABLED=false
```

**3. For a block of code:**
```php
$result = app('model-cache')->runDisabled(function () {
return MyModel::get();
});

// or via the Facade
use GeneaLabs\LaravelModelCaching\Facades\ModelCache;

ModelCache::runDisabled(function () {
return MyModel::get();
});
```

> **πŸ’‘ Tip:** Use option 1 in seeders to avoid pulling stale cached data during
> reseeds.

### ❄️ Cache Cool-Down Period
In high-traffic scenarios (e.g. frequent comment submissions) you may want to
prevent every write from immediately flushing the cache. Cool-down requires two
steps:

**Declare the default duration** on the model (this alone does nothing β€” it
just sets the value):

```php
get();

// Or override with a specific duration
Comment::withCacheCooldownSeconds(30)->get();
```

Once activated, writes during the cool-down window will not flush the cache.
After the window expires, the next write triggers a flush and re-warms the
cache. πŸ”„

### πŸ›‘οΈ Graceful Fallback
When enabled, if the cache backend (e.g. Redis) is unavailable the package logs
a warning and falls back to querying the database directly β€” your application
continues to function without caching rather than throwing an exception.

```
MODEL_CACHE_FALLBACK_TO_DB=true
```

### 🧹 Cache Invalidation
Cache is automatically flushed when:

| Trigger | Behavior |
|---------|----------|
| Model created | Flush model cache |
| Model updated/saved | Flush model cache |
| Model deleted | Flush only if rows were actually deleted |
| Model force-deleted | Flush only if rows were actually deleted |
| Pivot `attach` / `detach` / `sync` / `updateExistingPivot` | Flush relationship cache |
| `increment` / `decrement` | Flush model cache |
| `insert` / `update` (builder) | Flush model cache |
| `truncate` | Flush model cache |

Cache tags are generated for the primary model, each eager-loaded relationship,
joined tables, and morph-to target types, so only the relevant entries are
invalidated. 🎯

### πŸ”— BelongsToMany with Custom Pivot Models
Cache invalidation works for `BelongsToMany` relationships using custom pivot
models (`->using(CustomPivot::class)`) as long as either the parent or the
related model uses the `Cachable` trait.

### 🧹 Manual Cache Flushing

**Artisan command β€” single model:**
```sh
php artisan modelCache:clear --model='App\Models\Post'
```

**Artisan command β€” all models:**
```sh
php artisan modelCache:clear
```

**πŸ”§ Programmatic via Facade:**
```php
use GeneaLabs\LaravelModelCaching\Facades\ModelCache;

// Single model
ModelCache::invalidate(App\Models\Post::class);

// Multiple models
ModelCache::invalidate([
App\Models\Post::class,
App\Models\Comment::class,
]);
```

### ⏰ Cache Expiration (TTL)
Cached queries are stored indefinitely (`rememberForever`) and rely on automatic
invalidation (see above) to stay fresh. There is no per-query TTL option. If you
need time-based expiry, use the cool-down period feature or flush the cache on a
schedule via the Artisan command.

### πŸ§ͺ Testing
In your test suite you can either disable model caching entirely or use the
`array` cache driver:

**🚫 Disable caching in tests:**
```php
// In your TestCase setUp() or phpunit.xml
config(['laravel-model-caching.enabled' => false]);
```

**βœ… Use the array driver** (useful for testing cache behavior itself):
```php
config(['cache.stores.model-test' => ['driver' => 'array']]);
config(['laravel-model-caching.store' => 'model-test']);
```

### πŸ‘· Queue Workers
The package has no special queue or Horizon integration. Cached queries inside
queued jobs work the same as in HTTP requests. Cache invalidation triggered in a
web request is immediately visible to queue workers (assuming a shared cache
store like Redis). No additional configuration is needed.

## 🀝 Contributing
Contributions are welcome! πŸŽ‰ Please review the
[Contribution Guidelines](https://github.com/GeneaLabs/laravel-model-caching/blob/master/CONTRIBUTING.md)
and observe the
[Code of Conduct](https://github.com/GeneaLabs/laravel-model-caching/blob/master/CODE_OF_CONDUCT.md)
before submitting a pull request.

## ⬆️ Upgrading
For breaking changes and upgrade instructions between versions, see the
[Releases](https://github.com/GeneaLabs/laravel-model-caching/releases) page on
GitHub.

## πŸ” Security
Please review the [Security Policy](https://github.com/GeneaLabs/laravel-model-caching/blob/master/SECURITY.md)
for information on supported versions and how to report vulnerabilities.

## πŸ“š Further Reading
The [test suite](https://github.com/GeneaLabs/laravel-model-caching/tree/master/tests)
serves as living documentation β€” browse it for detailed examples of every
supported query type, relationship pattern, and edge case. πŸ“–

---


Built with ❀️ for the Laravel community using lots of β˜•οΈ by Mike Bronner.


This is an MIT-licensed open-source project. Its continued development is made
possible by the community. If you find it useful, please consider
πŸ’– becoming a sponsor
and
⭐ing it on GitHub.


πŸ™ Thank you to all contributors who have helped make this package better!