├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── cacheable.php └── src ├── CacheableServiceProvider.php ├── Database └── Query │ └── CacheableQueryBuilder.php └── Models └── Traits └── Cacheable.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## 0.5.1 - 2025-03-07 9 | 10 | fix: deprecations because of implicit nullable parameters 11 | 12 | **Full Changelog**: https://github.com/elipZis/laravel-cacheable-model/compare/v0.5.0...v0.5.1 13 | 14 | ## 0.5.0 - 2025-02-28 15 | 16 | ### What's Changed 17 | 18 | * Bumped PHP (8.4) and Laravel (12) by one version (current and last supported) 19 | * Add support for dynamic log channel by @Sleepy4k in https://github.com/elipZis/laravel-cacheable-model/pull/37 20 | * build(deps): Bump actions/checkout from 4.1.7 to 4.2.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/38 21 | * build(deps): Bump actions/checkout from 4.2.0 to 4.2.1 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/39 22 | * fix(CacheableQueryBuilder): remove expired items from tagged cache by @kingyyy in https://github.com/elipZis/laravel-cacheable-model/pull/40 23 | * build(deps): Bump actions/checkout from 4.2.1 to 4.2.2 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/41 24 | * build(deps): Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/42 25 | 26 | ### New Contributors 27 | 28 | * @Sleepy4k made their first contribution in https://github.com/elipZis/laravel-cacheable-model/pull/37 29 | 30 | **Full Changelog**: https://github.com/elipZis/laravel-cacheable-model/compare/v0.4.1...v0.5.0 31 | 32 | ## 0.4.1 - 2024-07-09 33 | 34 | ### What's Changed 35 | 36 | * fix(CacheableQueryBuilder): fixed bug when query has whereIn('id', Expression) by @kingyyy in https://github.com/elipZis/laravel-cacheable-model/pull/35 37 | 38 | ### New Contributors 39 | 40 | * @kingyyy made their first contribution in https://github.com/elipZis/laravel-cacheable-model/pull/35 41 | 42 | ### Chore 43 | 44 | * build(deps): Bump actions/checkout from 4.1.1 to 4.1.2 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/26 45 | * build(deps): Bump ramsey/composer-install from 2 to 3 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/28 46 | * build(deps): Bump dependabot/fetch-metadata from 1.6.0 to 2.0.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/27 47 | * build(deps): Bump actions/checkout from 4.1.2 to 4.1.3 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/29 48 | * build(deps): Bump actions/checkout from 4.1.3 to 4.1.4 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/30 49 | * build(deps): Bump dependabot/fetch-metadata from 2.0.0 to 2.1.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/31 50 | * build(deps): Bump actions/checkout from 4.1.4 to 4.1.5 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/32 51 | * build(deps): Bump actions/checkout from 4.1.5 to 4.1.6 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/33 52 | * build(deps): Bump actions/checkout from 4.1.6 to 4.1.7 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/34 53 | * build(deps): Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/36 54 | 55 | **Full Changelog**: https://github.com/elipZis/laravel-cacheable-model/compare/v0.4.0...v0.4.1 56 | 57 | ## 0.4.0 - 2024-03-22 58 | 59 | Bump to Laravel 11 and up to PHP 8.3 60 | 61 | ### What's Changed 62 | 63 | * build(deps): Bump actions/checkout from 3.4.0 to 3.5.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/12 64 | * build(deps): Bump actions/checkout from 3.5.0 to 3.5.2 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/13 65 | * build(deps): Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/14 66 | * build(deps): Bump dependabot/fetch-metadata from 1.4.0 to 1.5.1 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/16 67 | * build(deps): Bump actions/checkout from 3.5.2 to 3.5.3 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/17 68 | * build(deps): Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/18 69 | * build(deps): Bump actions/checkout from 3.5.3 to 3.6.0 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/19 70 | * build(deps): Bump actions/checkout from 3.6.0 to 4.1.1 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/23 71 | * build(deps): Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/elipZis/laravel-cacheable-model/pull/22 72 | * refactor: Bump composer by @nea in https://github.com/elipZis/laravel-cacheable-model/pull/24 73 | * refactor: Bump for L11 by @nea in https://github.com/elipZis/laravel-cacheable-model/pull/25 74 | 75 | ### New Contributors 76 | 77 | * @nea made their first contribution in https://github.com/elipZis/laravel-cacheable-model/pull/24 78 | 79 | **Full Changelog**: https://github.com/elipZis/laravel-cacheable-model/compare/v0.3.0...v0.4.0 80 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) elipZis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automatically cache Laravel Eloquent models by queries 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/elipzis/laravel-cacheable-model.svg?style=flat-square)](https://packagist.org/packages/elipzis/laravel-cacheable-model) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/elipzis/laravel-cacheable-model/run-tests.yml?branch=main)](https://github.com/elipzis/laravel-cacheable-model/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/elipzis/laravel-cacheable-model/php-cs-fixer.yml?branch=main)](https://github.com/elipzis/laravel-cacheable-model/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/elipzis/laravel-cacheable-model.svg?style=flat-square)](https://packagist.org/packages/elipzis/laravel-cacheable-model) 7 | 8 | Easy and automatic select-query caching for your Eloquent models! 9 | 10 | * Get cached query results and reduce your database load automatically 11 | * Configure TTL, prefixes, unique-queries etc. 12 | * No manual cache calls needed 13 | * Automated cache flush in case of updates, inserts or deletions 14 | 15 | You can make any Eloquent model cacheable by adding the trait 16 | 17 | ```php 18 | ... 19 | use ElipZis\Cacheable\Models\Traits\Cacheable; 20 | ... 21 | 22 | class YourModel extends Model { 23 | 24 | use Cacheable; 25 | ... 26 | ``` 27 | 28 | and leverage the power of a Redis, memcached or other caches. 29 | 30 | ## Installation 31 | 32 | You can install the package via composer: 33 | 34 | ```bash 35 | composer require elipzis/laravel-cacheable-model 36 | ``` 37 | 38 | You can publish the config file with: 39 | 40 | ```bash 41 | php artisan vendor:publish --tag="cacheable-model-config" 42 | ``` 43 | 44 | This is the contents of the published config file: 45 | 46 | ```php 47 | //Default values for the Cacheable trait - Can be overridden per model 48 | return [ 49 | //How long should cache last in general? 50 | 'ttl' => 300, 51 | //By what should cache entries be prefixed? 52 | 'prefix' => 'cacheable', 53 | //What is the identifying, unique column name? 54 | 'identifier' => 'id', 55 | //Do you need logging? 56 | 'logging' => [ 57 | 'channel' => null, //Which channel should be used? 58 | 'enabled' => false, 59 | 'level' => 'debug', 60 | ], 61 | ]; 62 | ``` 63 | 64 | ## Usage 65 | 66 | Make your model cacheable by adding the trait: 67 | 68 | ```php 69 | ... 70 | use ElipZis\Cacheable\Models\Traits\Cacheable; 71 | ... 72 | 73 | class YourModel extends Model { 74 | 75 | use Cacheable; 76 | ... 77 | ``` 78 | 79 | and then just use your normal model query, for example 80 | 81 | ```php 82 | YourModel::query()->get(); 83 | ``` 84 | 85 | ```php 86 | YourModel::query()->where('field', 'test')->first(); 87 | ``` 88 | 89 | ```php 90 | YourModel::query()->insert([...]); 91 | ``` 92 | 93 | The package overrides the QueryBuilder and scans for the same queries to capture and return the cached values. 94 | 95 | You do not need to do anything else but just use your model as you would and leverage the power of cached entries! 96 | 97 | ### Configuration 98 | 99 | The following configuration can be overridden per model 100 | 101 | ```php 102 | public function getCacheableProperties(): array { 103 | return [ 104 | 'ttl' => 300, 105 | 'prefix' => 'cacheable', 106 | 'identifier' => 'id', 107 | 'logging' => [ 108 | 'channel' => 'anotherChannel', 109 | 'enabled' => false, 110 | 'level' => 'debug', 111 | ], 112 | ]; 113 | } 114 | ``` 115 | 116 | ### Disable cache 117 | 118 | Depending on your cache and database performance, you might like to retrieve a query without caching sometimes: 119 | 120 | ```php 121 | YourModel::query()->withoutCache()->get(); 122 | ``` 123 | 124 | ### Flush cache 125 | 126 | If your data is updated outside of this package, you can flush it yourself by calling: 127 | 128 | ```php 129 | YourModel::query()->flushCache(); 130 | ``` 131 | 132 | ### Note on using caching 133 | 134 | This package overrides the native QueryBuilder and is capturing every database query, therefore it imposes a load and 135 | performance burden. 136 | 137 | If you use caching intensively on a model, this package and its use can help. If an entity is permanently changing, it 138 | won't make sense to make it `Cacheable`. 139 | 140 | It is recommended to only make models `Cacheable` which have a reasonable caching time in your system. Do not use the 141 | trait on any other or all models out of the box, but think about where it makes sense. 142 | 143 | ## Testing 144 | 145 | ```bash 146 | composer test 147 | ``` 148 | 149 | ## Changelog 150 | 151 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 152 | 153 | ## Contributing 154 | 155 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 156 | 157 | ## Security Vulnerabilities 158 | 159 | Please review [our security policy](.github/SECURITY.md) on how to report security vulnerabilities. 160 | 161 | ## Credits 162 | 163 | - [elipZis GmbH](https://elipZis.com) 164 | - [NeA](https://github.com/nea) 165 | - [All Contributors](https://github.com/elipZis/laravel-cacheable-model/contributors) 166 | 167 | ## License 168 | 169 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 170 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elipzis/laravel-cacheable-model", 3 | "description": "Automatic query-based model cache for your Laravel app", 4 | "keywords": [ 5 | "elipZis", 6 | "laravel", 7 | "cache", 8 | "model", 9 | "eloquent", 10 | "querybuilder", 11 | "query", 12 | "config" 13 | ], 14 | "homepage": "https://github.com/elipzis/laravel-cacheable-model", 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "elipZis GmbH", 19 | "email": "contact@elipzis.com", 20 | "role": "Developer", 21 | "homepage": "https://elipZis.com" 22 | } 23 | ], 24 | "require": { 25 | "php": "^8.3|^8.4", 26 | "spatie/laravel-package-tools": "^1.14", 27 | "illuminate/contracts": "^11.0|^12.0" 28 | }, 29 | "require-dev": { 30 | "friendsofphp/php-cs-fixer": "^3.8", 31 | "nunomaduro/collision": "^8.0", 32 | "nunomaduro/larastan": "^2.0.1", 33 | "orchestra/testbench": "^9.0|^10.0", 34 | "pestphp/pest": "^3.0", 35 | "pestphp/pest-plugin-laravel": "^3.0", 36 | "phpstan/extension-installer": "^1.1", 37 | "phpstan/phpstan-deprecation-rules": "^1.0", 38 | "phpstan/phpstan-phpunit": "^1.0", 39 | "phpunit/phpunit": "^11.0|^12.0", 40 | "spatie/laravel-ray": "^1.26" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "ElipZis\\Cacheable\\": "src" 45 | } 46 | }, 47 | "autoload-dev": { 48 | "psr-4": { 49 | "ElipZis\\Cacheable\\Tests\\": "tests" 50 | } 51 | }, 52 | "scripts": { 53 | "analyse": "vendor/bin/phpstan analyse", 54 | "test": "vendor/bin/pest", 55 | "test-coverage": "vendor/bin/pest coverage", 56 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes" 57 | }, 58 | "config": { 59 | "sort-packages": true, 60 | "allow-plugins": { 61 | "pestphp/pest-plugin": true, 62 | "phpstan/extension-installer": true 63 | } 64 | }, 65 | "extra": { 66 | "laravel": { 67 | "providers": [ 68 | "ElipZis\\Cacheable\\CacheableServiceProvider" 69 | ] 70 | } 71 | }, 72 | "minimum-stability": "dev", 73 | "prefer-stable": true 74 | } 75 | -------------------------------------------------------------------------------- /config/cacheable.php: -------------------------------------------------------------------------------- 1 | 300, 7 | //By what should cache entries be prefixed? 8 | 'prefix' => 'cacheable', 9 | //What is the identifying, unique column name? 10 | 'identifier' => 'id', 11 | //Do you need logging? 12 | 'logging' => [ 13 | 'channel' => null, //Which channel should be used? 14 | 'enabled' => false, 15 | 'level' => 'debug', 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /src/CacheableServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-cacheable-model')->hasConfigFile('cacheable'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Database/Query/CacheableQueryBuilder.php: -------------------------------------------------------------------------------- 1 | modelClass = $modelClass ?? static::class; 84 | $this->cacheableProperties = $cacheableProperties; 85 | 86 | //Prefill some members 87 | $this->modelIdentifier = $cacheableProperties['identifier'] ?? null; 88 | $this->ttl = $cacheableProperties['ttl'] ?? null; 89 | $this->prefix = $cacheableProperties['prefix'] ?? null; 90 | $this->logChannel = Arr::get($cacheableProperties, 'logging.channel', null); 91 | $this->logEnabled = Arr::get($cacheableProperties, 'logging.enabled', false); 92 | $this->logLevel = Arr::get($cacheableProperties, 'logging.level', null); 93 | 94 | if ($this->logChannel == null) { 95 | $this->logChannel = Log::getDefaultDriver(); 96 | } elseif (! in_array($this->logChannel, array_keys(config('logging.channels')))) { 97 | Log::log('error', "[Cacheable] Log channel '{$this->logChannel}' does not exist, using default driver instead."); 98 | $this->logChannel = Log::getDefaultDriver(); 99 | } 100 | } 101 | 102 | /** 103 | * Pass our configuration to newly created queries 104 | * 105 | * @return $this|CacheableQueryBuilder 106 | */ 107 | public function newQuery() 108 | { 109 | return new static( 110 | $this->connection, 111 | $this->grammar, 112 | $this->processor, 113 | $this->modelClass, 114 | $this->cacheableProperties 115 | ); 116 | } 117 | 118 | /** 119 | * Run the query as a "select" statement against the connection. 120 | * 121 | * Check the cache based on the query beforehand and return 122 | * a cached value or cache it if not already. 123 | * 124 | * @return array 125 | */ 126 | protected function runSelect() 127 | { 128 | if (! $this->enabled) { 129 | return parent::runSelect(); 130 | } 131 | 132 | //Use the query as common cache key 133 | $cacheKey = $this->getCacheKey(); 134 | 135 | //Check if taggable store 136 | $isTaggableStore = Cache::getStore() instanceof TaggableStore; 137 | //and create additional identifiers 138 | $modelClasses = $this->getIdentifiableModelClasses($this->getIdentifiableValue()); 139 | 140 | // clear expired cached queries in tagged cache 141 | if ($isTaggableStore && Cache::tags($modelClasses) instanceof RedisTaggedCache) { 142 | Cache::tags($modelClasses)->flushStale(); 143 | } 144 | 145 | //If cached, return 146 | if (($isTaggableStore && Cache::tags($modelClasses)->has($cacheKey)) || Cache::has($cacheKey)) { 147 | $this->log("Found cache entry for '{$cacheKey}'"); 148 | 149 | return $isTaggableStore ? Cache::tags($modelClasses)->get($cacheKey) : Cache::get($cacheKey); 150 | } 151 | 152 | //If not, run normally -> this is what to cache and return 153 | $retVal = parent::runSelect(); 154 | 155 | //Are tags supported? Makes life easier! 156 | if ($isTaggableStore) { 157 | $this->log("Using taggable store to cache value of {$cacheKey} for {$this->ttl} ttl for " . implode(',', $modelClasses)); 158 | Cache::tags($modelClasses)->put($cacheKey, $retVal, $this->ttl); 159 | } else { 160 | $this->log("Using cache to store value of {$cacheKey} for {$this->ttl} ttl for " . implode(',', $modelClasses)); 161 | Cache::put($cacheKey, $retVal, $this->ttl); 162 | 163 | //Cache the query if not, for purging purposes 164 | foreach ($modelClasses as $modelClass) { 165 | $modelCacheKey = $this->getModelCacheKey($modelClass); 166 | $queries = Cache::get($modelCacheKey, []); 167 | $queries[] = $cacheKey; 168 | Cache::put($modelCacheKey, $queries); 169 | } 170 | } 171 | 172 | return $retVal; 173 | } 174 | 175 | /** 176 | * Check if to cache against just the class or a specific identifiable e.g. id 177 | * 178 | * @param mixed|null $value 179 | * @return string[] 180 | */ 181 | protected function getIdentifiableModelClasses(mixed $value = null): array 182 | { 183 | $retVals = [$this->modelClass]; 184 | if ($value) { 185 | if (is_array($value)) { 186 | foreach ($value as $v) { 187 | if ($v instanceof Expression) { 188 | $v = $v->getValue($this->getGrammar()); 189 | } 190 | $retVals[] = "{$this->modelClass}#{$v}"; 191 | } 192 | } else { 193 | if ($value instanceof Expression) { 194 | $value = $value->getValue($this->getGrammar()); 195 | } 196 | $retVals[] = "{$this->modelClass}#{$value}"; 197 | } 198 | } 199 | 200 | return $retVals; 201 | } 202 | 203 | /** 204 | * @param array|null $wheres 205 | * @return mixed 206 | */ 207 | protected function getIdentifiableValue(?array $wheres = null): mixed 208 | { 209 | $wheres = $wheres ?? $this->wheres; 210 | foreach ($wheres as $where) { 211 | if (isset($where['type']) && $where['type'] === 'Nested') { 212 | return $this->getIdentifiableValue($where['query']->wheres); 213 | } 214 | if (isset($where['column']) && $where['column'] === $this->modelIdentifier) { 215 | return $where['value'] ?? $where['values']; 216 | } 217 | } 218 | 219 | return null; 220 | } 221 | 222 | /** 223 | * Check if identifier query (id-driven) 224 | * 225 | * @param array|null $wheres 226 | * @return bool 227 | */ 228 | protected function isIdentifiableQuery(?array $wheres = null): bool 229 | { 230 | return $this->getIdentifiableValue($wheres) !== null; 231 | } 232 | 233 | /** 234 | * Purge all model queries and results 235 | * 236 | * @param mixed|null $identifier 237 | * @return bool 238 | */ 239 | public function flushCache(mixed $identifier = null): bool 240 | { 241 | if (! $this->enabled) { 242 | return false; 243 | } 244 | 245 | $this->log("Flushing cache for {$this->modelClass}"); 246 | 247 | //If tag-support, just flush all results 248 | $modelClasses = $this->getIdentifiableModelClasses($identifier); 249 | if (Cache::getStore() instanceof TaggableStore) { 250 | return Cache::tags($modelClasses)->flush(); 251 | } else { 252 | //If not, forget based on the cached queries 253 | foreach ($modelClasses as $modelClass) { 254 | $modelCacheKey = $this->getModelCacheKey($modelClass); 255 | $queries = Cache::get($modelCacheKey); 256 | if (! empty($queries)) { 257 | foreach ($queries as $query) { 258 | Cache::forget($query); 259 | } 260 | 261 | Cache::forget($modelCacheKey); 262 | } 263 | } 264 | } 265 | 266 | return true; 267 | } 268 | 269 | /** 270 | * Build a cache key based on the SQL statement and its bindings 271 | * 272 | * @return string 273 | */ 274 | protected function getCacheKey(): string 275 | { 276 | $sql = $this->toSql(); 277 | $bindings = $this->getBindings(); 278 | if (! empty($bindings)) { 279 | $bindings = Arr::join($this->getBindings(), '_'); 280 | 281 | return $sql . '_' . $bindings; 282 | } 283 | 284 | return $sql; 285 | } 286 | 287 | /** 288 | * @param string|null $modelClass 289 | * @return string 290 | */ 291 | protected function getModelCacheKey(?string $modelClass = null): string 292 | { 293 | return $this->prefix . '_' . ($modelClass ?? $this->modelClass); 294 | } 295 | 296 | /** 297 | * @param string $message 298 | * @param string $level 299 | * @return bool 300 | */ 301 | protected function log(string $message, string $level = 'debug'): bool 302 | { 303 | if (! $this->logEnabled) { 304 | return false; 305 | } 306 | 307 | if ($this->logLevel) { 308 | Log::channel($this->logChannel)->log($this->logLevel, "[Cacheable] {$message}"); 309 | } else { 310 | Log::channel($this->logChannel)->log($level, "[Cacheable] {$message}"); 311 | } 312 | 313 | return true; 314 | } 315 | 316 | /** 317 | * Disable cache for this query 318 | * 319 | * @return $this 320 | */ 321 | public function withoutCache(): static 322 | { 323 | $this->enabled = false; 324 | 325 | return $this; 326 | } 327 | 328 | /** 329 | * Enable logging for this query 330 | * 331 | * @return $this 332 | */ 333 | public function withLogging(): static 334 | { 335 | $this->logEnabled = true; 336 | 337 | return $this; 338 | } 339 | 340 | /** 341 | * Change the ttl for this query 342 | * 343 | * @param int $ttl 344 | * @return $this 345 | */ 346 | public function withTtl(int $ttl): static 347 | { 348 | $this->ttl = $ttl; 349 | 350 | return $this; 351 | } 352 | 353 | /** 354 | * @param array $values 355 | * @return int 356 | */ 357 | public function update(array $values) 358 | { 359 | $this->flushCache(); 360 | 361 | return parent::update($values); 362 | } 363 | 364 | /** 365 | * @param array $values 366 | * @return int 367 | */ 368 | public function updateFrom(array $values) 369 | { 370 | $this->flushCache(); 371 | 372 | return parent::updateFrom($values); 373 | } 374 | 375 | /** 376 | * @param array $values 377 | * @return bool 378 | */ 379 | public function insert(array $values) 380 | { 381 | $this->flushCache(); 382 | 383 | return parent::insert($values); 384 | } 385 | 386 | /** 387 | * @param array $values 388 | * @param $sequence 389 | * @return int 390 | */ 391 | public function insertGetId(array $values, $sequence = null) 392 | { 393 | $this->flushCache(); 394 | 395 | return parent::insertGetId($values, $sequence); 396 | } 397 | 398 | /** 399 | * @param array $values 400 | * @return int 401 | */ 402 | public function insertOrIgnore(array $values) 403 | { 404 | $this->flushCache(); 405 | 406 | return parent::insertOrIgnore($values); 407 | } 408 | 409 | /** 410 | * @param array $columns 411 | * @param $query 412 | * @return int 413 | */ 414 | public function insertUsing(array $columns, $query) 415 | { 416 | $this->flushCache(); 417 | 418 | return parent::insertUsing($columns, $query); 419 | } 420 | 421 | /** 422 | * @param array $values 423 | * @param $uniqueBy 424 | * @param $update 425 | * @return int 426 | */ 427 | public function upsert(array $values, $uniqueBy, $update = null) 428 | { 429 | $this->flushCache(); 430 | 431 | return parent::upsert($values, $uniqueBy, $update); 432 | } 433 | 434 | /** 435 | * @param $id 436 | * @return int 437 | */ 438 | public function delete($id = null) 439 | { 440 | $this->flushCache($id); 441 | 442 | return parent::delete($id); 443 | } 444 | 445 | /** 446 | * @return void 447 | */ 448 | public function truncate() 449 | { 450 | $this->flushCache(); 451 | 452 | parent::truncate(); 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /src/Models/Traits/Cacheable.php: -------------------------------------------------------------------------------- 1 | getConnection(); 19 | 20 | return new CacheableQueryBuilder( 21 | $conn, 22 | $conn->getQueryGrammar(), 23 | $conn->getPostProcessor(), 24 | static::class, 25 | $this->getCacheableProperties() 26 | ); 27 | } 28 | 29 | /** 30 | * @return array 31 | */ 32 | public function getCacheableProperties(): array 33 | { 34 | return [ 35 | 'ttl' => config('cacheable.ttl', 300), 36 | 'prefix' => config('cacheable.prefix', 'cacheable'), 37 | 'identifier' => config('cacheable.identifier', 'id'), 38 | 'logging' => [ 39 | 'channel' => config('cacheable.logging.channel', null), 40 | 'enabled' => config('cacheable.logging.enabled', false), 41 | 'level' => config('cacheable.logging.level', 'debug', ), 42 | ], 43 | ]; 44 | } 45 | 46 | /** 47 | * Get the database connection for the model. 48 | * 49 | * @return Connection 50 | */ 51 | abstract public function getConnection(); 52 | } 53 | --------------------------------------------------------------------------------