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.
- Host: GitHub
- URL: https://github.com/mikebronner/laravel-model-caching
- Owner: mikebronner
- License: mit
- Created: 2017-09-17T14:40:24.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2025-02-27T00:39:39.000Z (over 1 year ago)
- Last Synced: 2025-05-08T20:55:38.408Z (about 1 year ago)
- Topics: caching, laravel, laravel-5-package, models
- Language: PHP
- Homepage:
- Size: 2.14 MB
- Stars: 2,302
- Watchers: 40
- Forks: 227
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# π Model Caching for Laravel
[](https://github.com/mikebronner/laravel-model-caching/actions?query=workflow%3A%22Laravel+Package%22)
[](https://packagist.org/packages/genealabs/laravel-model-caching)
[](https://raw.githubusercontent.com/mikebronner/laravel-model-caching/master/LICENSE)
[](https://packagist.org/packages/mikebronner/laravel-model-caching)
[](https://laravel.com)
[](https://packagist.org/packages/mikebronner/laravel-model-caching)
[](https://github.com/mikebronner/laravel-model-caching/stargazers)
[](https://codecov.io/gh/mikebronner/laravel-model-caching)
[](https://github.com/mikebronner/laravel-model-caching/tree/master/tests)

## ποΈ 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!