{"id":15031320,"url":"https://github.com/mikebronner/laravel-model-caching","last_synced_at":"2026-03-01T18:07:48.883Z","repository":{"id":38311141,"uuid":"103836049","full_name":"mikebronner/laravel-model-caching","owner":"mikebronner","description":"Eloquent model-caching made easy.","archived":false,"fork":false,"pushed_at":"2025-02-27T00:39:39.000Z","size":2239,"stargazers_count":2302,"open_issues_count":8,"forks_count":227,"subscribers_count":40,"default_branch":"master","last_synced_at":"2025-05-08T20:55:38.408Z","etag":null,"topics":["caching","laravel","laravel-5-package","models"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mikebronner.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-09-17T14:40:24.000Z","updated_at":"2025-05-01T15:13:05.000Z","dependencies_parsed_at":"2022-07-12T17:24:36.459Z","dependency_job_id":"3d8ce496-e71e-4262-943c-4a85c04fabf2","html_url":"https://github.com/mikebronner/laravel-model-caching","commit_stats":null,"previous_names":["genealabs/laravel-model-caching"],"tags_count":159,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikebronner%2Flaravel-model-caching","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikebronner%2Flaravel-model-caching/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikebronner%2Flaravel-model-caching/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mikebronner%2Flaravel-model-caching/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mikebronner","download_url":"https://codeload.github.com/mikebronner/laravel-model-caching/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253156195,"owners_count":21862850,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","laravel","laravel-5-package","models"],"created_at":"2024-09-24T20:15:25.812Z","updated_at":"2026-03-01T18:07:48.877Z","avatar_url":"https://github.com/mikebronner.png","language":"PHP","funding_links":["https://github.com/sponsors/mikebronner"],"categories":[],"sub_categories":[],"readme":"# 🚀 Model Caching for Laravel\n\n[![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)\n[![Packagist](https://img.shields.io/packagist/dt/GeneaLabs/laravel-model-caching.svg)](https://packagist.org/packages/genealabs/laravel-model-caching)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/mikebronner/laravel-model-caching/master/LICENSE)\n[![PHP Version](https://img.shields.io/packagist/php-v/mikebronner/laravel-model-caching)](https://packagist.org/packages/mikebronner/laravel-model-caching)\n[![Laravel](https://img.shields.io/badge/Laravel-11%20%7C%2012%20%7C%2013-FF2D20)](https://laravel.com)\n[![Latest Stable Version](https://img.shields.io/packagist/v/mikebronner/laravel-model-caching)](https://packagist.org/packages/mikebronner/laravel-model-caching)\n[![GitHub Stars](https://img.shields.io/github/stars/mikebronner/laravel-model-caching)](https://github.com/mikebronner/laravel-model-caching/stargazers)\n[![codecov](https://codecov.io/gh/mikebronner/laravel-model-caching/graph/badge.svg?token=ACk1Kk4OLO)](https://codecov.io/gh/mikebronner/laravel-model-caching)\n[![Tests](https://img.shields.io/badge/tests-335%2B-brightgreen)](https://github.com/mikebronner/laravel-model-caching/tree/master/tests)\n\n![Model Caching for Laravel masthead image](https://repository-images.githubusercontent.com/103836049/b0d89480-f1b1-11e9-8e13-a7055f008fe6)\n\n## 🗂️ Table of Contents\n- [📖 Summary](#-summary)\n- [📦 Installation](#-installation)\n- [🚀 Getting Started](#-getting-started)\n- [⚙️ Configuration](#️-configuration)\n- [🤝 Contributing](#-contributing)\n- [⬆️ Upgrading](#️-upgrading)\n- [🔐 Security](#-security)\n- [📚 Further Reading](#-further-reading)\n\n## 📖 Summary\nAutomatic, self-invalidating Eloquent model and relationship caching. Add a\ntrait to your models and all query results are cached automatically — no manual\ncache keys, no forgetting to invalidate. When a model is created, updated, or\ndeleted the relevant cache entries are flushed for you.\n\n⚡ Typical performance improvements range from 100–900% reduction in database\nqueries on read-heavy pages. 🧪 Backed by 335+ integration tests across PHP\n8.2–8.5 and Laravel 11–13.\n\n**Use this package when** your application makes many repeated Eloquent queries\nand you want a drop-in caching layer that stays in sync with your data without\nany manual bookkeeping.\n\n### 🔄 Before \u0026 After\n\n❌ **Without this package** — manual cache keys, manual invalidation:\n```php\n$posts = Cache::remember('posts:active:page:1', 3600, function () {\n    return Post::where('active', true)-\u003ewith('comments')-\u003epaginate();\n});\n\n// And in every observer or event listener…\nCache::forget('posts:active:page:1');\n// Hope you remembered every key variant! 😅\n```\n\n✅ **With this package** — add the trait, query normally:\n```php\n// Just query. Caching and invalidation happen automatically. ✨\n$posts = Post::where('active', true)-\u003ewith('comments')-\u003epaginate();\n```\n\n### ✅ What Gets Cached\n- Model queries (`get`, `first`, `find`, `all`, `paginate`, `pluck`, `value`, `exists`)\n- Aggregations (`count`, `sum`, `avg`, `min`, `max`)\n- Eager-loaded relationships (via `with()`)\n\n### 🚫 What Does Not Get Cached\n- Lazy-loaded relationships — only eager-loaded (`with()`) relationships are cached. Use `with()` to benefit from caching.\n- Queries using `select()` clauses — custom column selections bypass the cache.\n- Queries inside transactions — cache is not automatically flushed when a transaction commits; call `flushCache()` manually if needed.\n- `inRandomOrder()` queries — caching is automatically disabled since results should differ each time.\n\n### 💾 Cache Drivers\n\n| Driver | Supported |\n|--------|-----------|\n| Redis | ✅ (recommended) |\n| Memcached | ✅ |\n| APC | ✅ |\n| Array | ❌ |\n| File | ❌ |\n| Database | ❌ |\n| DynamoDB | ❌ |\n\n### 📋 Requirements\n- PHP 8.2+\n- Laravel 11, 12, or 13\n\n## 📦 Installation\n```\ncomposer require genealabs/laravel-model-caching\n```\n\n✨ The service provider is auto-discovered. No additional setup is required.\n\n## 🚀 Getting Started\nAdd the `Cachable` trait to your models. The recommended approach is a base\nmodel that all other models extend:\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse GeneaLabs\\LaravelModelCaching\\Traits\\Cachable;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nabstract class BaseModel extends Model\n{\n    use Cachable;\n}\n```\n\nAlternatively, extend the included `CachedModel` directly:\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse GeneaLabs\\LaravelModelCaching\\CachedModel;\n\nclass Post extends CachedModel\n{\n    // ...\n}\n```\n\n🎉 That's it — all Eloquent queries and eager-loaded relationships on these\nmodels are now cached and automatically invalidated.\n\n\u003e **⚠️ Note:** You can cache the `User` model — the `Cachable` trait does not\n\u003e conflict with Laravel's authentication. Just avoid using cache cool-down\n\u003e periods on it, and ensure user updates always go through Eloquent (not raw\n\u003e `DB::table()` queries) so cache invalidation fires correctly.\n\n### 🌍 Real-World Example\nConsider a blog with posts, comments, and tags:\n\n```php\nclass Post extends BaseModel\n{\n    public function comments()\n    {\n        return $this-\u003ehasMany(Comment::class);\n    }\n\n    public function tags()\n    {\n        return $this-\u003ebelongsToMany(Tag::class);\n    }\n}\n\n// All cached automatically — the query, the eager loads, everything. 🪄\n$posts = Post::with('comments', 'tags')\n    -\u003ewhere('published', true)\n    -\u003elatest()\n    -\u003epaginate(15);\n```\n\nWhen a new comment is created, the cache for `Post` and `Comment` queries is\nautomatically invalidated — no manual `Cache::forget()` calls needed. 🧹\n\n## ⚙️ Configuration\nPublish the config file:\n```sh\nphp artisan modelCache:publish --config\n```\n\nThis creates `config/laravel-model-caching.php`:\n\n```php\nreturn [\n    'cache-prefix'         =\u003e '',\n    'enabled'              =\u003e env('MODEL_CACHE_ENABLED', true),\n    'use-database-keying'  =\u003e env('MODEL_CACHE_USE_DATABASE_KEYING', true),\n    'store'                =\u003e env('MODEL_CACHE_STORE'),\n    'fallback-to-database' =\u003e env('MODEL_CACHE_FALLBACK_TO_DB', false),\n];\n```\n\n### 🔧 Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `MODEL_CACHE_ENABLED` | `true` | ✅ Enable or disable caching globally. |\n| `MODEL_CACHE_STORE` | `null` | 💾 Cache store name from `config/cache.php`. Uses the default store when not set. |\n| `MODEL_CACHE_USE_DATABASE_KEYING` | `true` | 🔑 Include database connection and name in cache keys. Important for multi-tenant or multi-database apps. |\n| `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. |\n\n\u003e **📝 Note:** The `cache-prefix` option is set directly in the config file (not via\n\u003e an environment variable). For dynamic prefixes (e.g. multi-tenant), use the\n\u003e per-model `$cachePrefix` property shown below.\n\n### 💾 Custom Cache Store\nTo use a dedicated cache store for model caching, define one in\n`config/cache.php` and reference it:\n```\nMODEL_CACHE_STORE=model-cache\n```\n\n### 🏷️ Cache Key Prefix\nFor multi-tenant applications you can isolate cache entries per tenant. Set the\nprefix globally in config:\n```php\n'cache-prefix' =\u003e 'tenant-123',\n```\n\nOr per-model via a property:\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse GeneaLabs\\LaravelModelCaching\\Traits\\Cachable;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Post extends Model\n{\n    use Cachable;\n\n    protected $cachePrefix = 'tenant-123';\n}\n```\n\n### 🔌 Multiple Database Connections\nWhen `use-database-keying` is enabled (the default), cache keys automatically\ninclude the database connection and name. This keeps cache entries separate\nacross connections without any extra configuration.\n\n### 🚫 Disabling Cache\nThere are three ways to bypass caching:\n\n**1. Per-query** (only affects this query chain, not subsequent queries):\n```php\n$results = MyModel::disableCache()-\u003ewhere('active', true)-\u003eget();\n```\n\n**2. Globally via environment:**\n```\nMODEL_CACHE_ENABLED=false\n```\n\n**3. For a block of code:**\n```php\n$result = app('model-cache')-\u003erunDisabled(function () {\n    return MyModel::get();\n});\n\n// or via the Facade\nuse GeneaLabs\\LaravelModelCaching\\Facades\\ModelCache;\n\nModelCache::runDisabled(function () {\n    return MyModel::get();\n});\n```\n\n\u003e **💡 Tip:** Use option 1 in seeders to avoid pulling stale cached data during\n\u003e reseeds.\n\n### ❄️ Cache Cool-Down Period\nIn high-traffic scenarios (e.g. frequent comment submissions) you may want to\nprevent every write from immediately flushing the cache. Cool-down requires two\nsteps:\n\n**Declare the default duration** on the model (this alone does nothing — it\njust sets the value):\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse GeneaLabs\\LaravelModelCaching\\Traits\\Cachable;\nuse Illuminate\\Database\\Eloquent\\Model;\n\nclass Comment extends Model\n{\n    use Cachable;\n\n    protected $cacheCooldownSeconds = 300; // 5 minutes ⏱️\n}\n```\n\n**Activate the cool-down** by calling `withCacheCooldownSeconds()` in your\nquery. This writes the cool-down window into the cache store:\n\n```php\n// Activate using the model's default (300 seconds)\nComment::withCacheCooldownSeconds()-\u003eget();\n\n// Or override with a specific duration\nComment::withCacheCooldownSeconds(30)-\u003eget();\n```\n\nOnce activated, writes during the cool-down window will not flush the cache.\nAfter the window expires, the next write triggers a flush and re-warms the\ncache. 🔄\n\n### 🛡️ Graceful Fallback\nWhen enabled, if the cache backend (e.g. Redis) is unavailable the package logs\na warning and falls back to querying the database directly — your application\ncontinues to function without caching rather than throwing an exception.\n\n```\nMODEL_CACHE_FALLBACK_TO_DB=true\n```\n\n### 🧹 Cache Invalidation\nCache is automatically flushed when:\n\n| Trigger | Behavior |\n|---------|----------|\n| Model created | Flush model cache |\n| Model updated/saved | Flush model cache |\n| Model deleted | Flush only if rows were actually deleted |\n| Model force-deleted | Flush only if rows were actually deleted |\n| Pivot `attach` / `detach` / `sync` / `updateExistingPivot` | Flush relationship cache |\n| `increment` / `decrement` | Flush model cache |\n| `insert` / `update` (builder) | Flush model cache |\n| `truncate` | Flush model cache |\n\nCache tags are generated for the primary model, each eager-loaded relationship,\njoined tables, and morph-to target types, so only the relevant entries are\ninvalidated. 🎯\n\n### 🔗 BelongsToMany with Custom Pivot Models\nCache invalidation works for `BelongsToMany` relationships using custom pivot\nmodels (`-\u003eusing(CustomPivot::class)`) as long as either the parent or the\nrelated model uses the `Cachable` trait.\n\n### 🧹 Manual Cache Flushing\n\n**Artisan command — single model:**\n```sh\nphp artisan modelCache:clear --model='App\\Models\\Post'\n```\n\n**Artisan command — all models:**\n```sh\nphp artisan modelCache:clear\n```\n\n**🔧 Programmatic via Facade:**\n```php\nuse GeneaLabs\\LaravelModelCaching\\Facades\\ModelCache;\n\n// Single model\nModelCache::invalidate(App\\Models\\Post::class);\n\n// Multiple models\nModelCache::invalidate([\n    App\\Models\\Post::class,\n    App\\Models\\Comment::class,\n]);\n```\n\n### ⏰ Cache Expiration (TTL)\nCached queries are stored indefinitely (`rememberForever`) and rely on automatic\ninvalidation (see above) to stay fresh. There is no per-query TTL option. If you\nneed time-based expiry, use the cool-down period feature or flush the cache on a\nschedule via the Artisan command.\n\n### 🧪 Testing\nIn your test suite you can either disable model caching entirely or use the\n`array` cache driver:\n\n**🚫 Disable caching in tests:**\n```php\n// In your TestCase setUp() or phpunit.xml\nconfig(['laravel-model-caching.enabled' =\u003e false]);\n```\n\n**✅ Use the array driver** (useful for testing cache behavior itself):\n```php\nconfig(['cache.stores.model-test' =\u003e ['driver' =\u003e 'array']]);\nconfig(['laravel-model-caching.store' =\u003e 'model-test']);\n```\n\n### 👷 Queue Workers\nThe package has no special queue or Horizon integration. Cached queries inside\nqueued jobs work the same as in HTTP requests. Cache invalidation triggered in a\nweb request is immediately visible to queue workers (assuming a shared cache\nstore like Redis). No additional configuration is needed.\n\n## 🤝 Contributing\nContributions are welcome! 🎉 Please review the\n[Contribution Guidelines](https://github.com/GeneaLabs/laravel-model-caching/blob/master/CONTRIBUTING.md)\nand observe the\n[Code of Conduct](https://github.com/GeneaLabs/laravel-model-caching/blob/master/CODE_OF_CONDUCT.md)\nbefore submitting a pull request.\n\n## ⬆️ Upgrading\nFor breaking changes and upgrade instructions between versions, see the\n[Releases](https://github.com/GeneaLabs/laravel-model-caching/releases) page on\nGitHub.\n\n## 🔐 Security\nPlease review the [Security Policy](https://github.com/GeneaLabs/laravel-model-caching/blob/master/SECURITY.md)\nfor information on supported versions and how to report vulnerabilities.\n\n## 📚 Further Reading\nThe [test suite](https://github.com/GeneaLabs/laravel-model-caching/tree/master/tests)\nserves as living documentation — browse it for detailed examples of every\nsupported query type, relationship pattern, and edge case. 📖\n\n---\n\n\u003cp align=\"center\"\u003e\nBuilt with ❤️ for the Laravel community using lots of ☕️ by \u003ca href=\"https://github.com/mikebronner\"\u003eMike Bronner\u003c/a\u003e.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\nThis is an MIT-licensed open-source project. Its continued development is made\npossible by the community. If you find it useful, please consider\n\u003ca href=\"https://github.com/sponsors/mikebronner\"\u003e💖 becoming a sponsor\u003c/a\u003e\nand \n\u003ca href=\"https://github.com/mikebronner/laravel-model-caching\"\u003e⭐ing it on GitHub\u003c/a\u003e.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n🙏 Thank you to all \u003ca href=\"https://github.com/mikebronner/laravel-model-caching/graphs/contributors\"\u003econtributors\u003c/a\u003e who have helped make this package better!\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikebronner%2Flaravel-model-caching","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmikebronner%2Flaravel-model-caching","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmikebronner%2Flaravel-model-caching/lists"}