Completed
Push — develop ( c0300d...84d4c2 )
by Abdelrahman
03:19
created

CacheableEloquent::flushCacheKeys()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
nc 2
cc 2
eloc 9
nop 1
1
<?php
2
3
/*
4
 * NOTICE OF LICENSE
5
 *
6
 * Part of the Rinvex Cacheable Package.
7
 *
8
 * This source file is subject to The MIT License (MIT)
9
 * that is bundled with this package in the LICENSE file.
10
 *
11
 * Package: Rinvex Cacheable Package
12
 * License: The MIT License (MIT)
13
 * Link:    https://rinvex.com
14
 */
15
16
namespace Rinvex\Cacheable;
17
18
use Closure;
19
use Illuminate\Database\Eloquent\Model;
20
use Illuminate\Database\Eloquent\Builder;
21
22
trait CacheableEloquent
23
{
24
    /**
25
     * Indicate if the model cache clear is enabled.
26
     *
27
     * @var bool
28
     */
29
    protected static $cacheClearEnabled = true;
30
31
    /**
32
     * The model cache driver.
33
     *
34
     * @var string
35
     */
36
    protected $cacheDriver;
37
38
    /**
39
     * The model cache lifetime.
40
     *
41
     * @var float|int
42
     */
43
    protected $cacheLifetime = -1;
44
45
    /**
46
     * Register an updated model event with the dispatcher.
47
     *
48
     * @param \Closure|string $callback
49
     *
50
     * @return void
51
     */
52
    abstract public static function updated($callback);
53
54
    /**
55
     * Register a created model event with the dispatcher.
56
     *
57
     * @param \Closure|string $callback
58
     *
59
     * @return void
60
     */
61
    abstract public static function created($callback);
62
63
    /**
64
     * Register a deleted model event with the dispatcher.
65
     *
66
     * @param \Closure|string $callback
67
     *
68
     * @return void
69
     */
70
    abstract public static function deleted($callback);
71
72
    /**
73
     * Forget model cache on create/update/delete.
74
     *
75
     * @return void
76
     */
77
    public static function bootCacheableEloquent()
78
    {
79
        static::attacheEvents();
80
    }
81
82
    /**
83
     * Store the given cache key for the given model by mimicking cache tags.
84
     *
85
     * @param string $modelName
86
     * @param string $cacheKey
87
     *
88
     * @return void
89
     */
90
    protected static function storeCacheKey(string $modelName, string $cacheKey)
91
    {
92
        $keysFile = storage_path('framework/cache/data/rinvex.cacheable.json');
93
        $cacheKeys = static::getCacheKeys($keysFile);
94
95
        if (! isset($cacheKeys[$modelName]) || ! in_array($cacheKey, $cacheKeys[$modelName])) {
96
            $cacheKeys[$modelName][] = $cacheKey;
97
            file_put_contents($keysFile, json_encode($cacheKeys));
98
        }
99
    }
100
101
    /**
102
     * Get cache keys from the given file.
103
     *
104
     * @param string $file
105
     *
106
     * @return array
107
     */
108
    protected static function getCacheKeys($file)
109
    {
110
        if (! file_exists($file)) {
111
            file_put_contents($file, null);
112
        }
113
114
        return json_decode(file_get_contents($file), true) ?: [];
115
    }
116
117
    /**
118
     * Flush cache keys of the given model by mimicking cache tags.
119
     *
120
     * @param string $modelName
121
     *
122
     * @return array
123
     */
124
    protected static function flushCacheKeys(string $modelName): array
125
    {
126
        $flushedKeys = [];
127
        $keysFile = storage_path('framework/cache/data/rinvex.cacheable.json');
128
        $cacheKeys = static::getCacheKeys($keysFile);
129
130
        if (isset($cacheKeys[$modelName])) {
131
            $flushedKeys = $cacheKeys[$modelName];
132
133
            unset($cacheKeys[$modelName]);
134
135
            file_put_contents($keysFile, json_encode($cacheKeys));
136
        }
137
138
        return $flushedKeys;
139
    }
140
141
    /**
142
     * Set the model cache lifetime.
143
     *
144
     * @param float|int $cacheLifetime
145
     *
146
     * @return $this
147
     */
148
    public function setCacheLifetime($cacheLifetime)
149
    {
150
        $this->cacheLifetime = $cacheLifetime;
151
152
        return $this;
153
    }
154
155
    /**
156
     * Get the model cache lifetime.
157
     *
158
     * @return float|int
159
     */
160
    public function getCacheLifetime()
161
    {
162
        return $this->cacheLifetime;
163
    }
164
165
    /**
166
     * Set the model cache driver.
167
     *
168
     * @param string $cacheDriver
169
     *
170
     * @return $this
171
     */
172
    public function setCacheDriver($cacheDriver)
173
    {
174
        $this->cacheDriver = $cacheDriver;
175
176
        return $this;
177
    }
178
179
    /**
180
     * Get the model cache driver.
181
     *
182
     * @return string
183
     */
184
    public function getCacheDriver()
185
    {
186
        return $this->cacheDriver;
187
    }
188
189
    /**
190
     * Determine if model cache clear is enabled.
191
     *
192
     * @return bool
193
     */
194
    public static function isCacheClearEnabled()
195
    {
196
        return static::$cacheClearEnabled;
197
    }
198
199
    /**
200
     * Forget the model cache.
201
     *
202
     * @return void
203
     */
204
    public static function forgetCache()
205
    {
206
        static::fireCacheFlushEvent('cache.flushing');
207
208
        // Flush cache tags
209
        if (method_exists(app('cache')->getStore(), 'tags')) {
210
            app('cache')->tags(__CLASS__)->flush();
211
        } else {
212
            // Flush cache keys, then forget actual cache
213
            foreach (static::flushCacheKeys(__CLASS__) as $cacheKey) {
214
                app('cache')->forget($cacheKey);
215
            }
216
        }
217
218
        static::fireCacheFlushEvent('cache.flushed', false);
219
    }
220
221
    /**
222
     * Fire the given event for the model.
223
     *
224
     * @param string $event
225
     * @param bool   $halt
226
     *
227
     * @return mixed
228
     */
229
    protected static function fireCacheFlushEvent($event, $halt = true)
230
    {
231
        if (! isset(static::$dispatcher)) {
232
            return true;
233
        }
234
235
        // We will append the names of the class to the event to distinguish it from
236
        // other model events that are fired, allowing us to listen on each model
237
        // event set individually instead of catching event for all the models.
238
        $event = "eloquent.{$event}: ".__CLASS__;
239
240
        $method = $halt ? 'until' : 'fire';
241
242
        return static::$dispatcher->$method($event, __CLASS__);
243
    }
244
245
    /**
246
     * Reset cached model to its defaults.
247
     *
248
     * @return $this
249
     */
250
    public function resetCacheConfig()
251
    {
252
        $this->cacheDriver = null;
253
        $this->cacheLifetime = null;
254
255
        return $this;
256
    }
257
258
    /**
259
     * Generate unique cache key.
260
     *
261
     * @param \Illuminate\Database\Eloquent\Builder $builder
262
     * @param array                                 $columns
263
     *
264
     * @return string
265
     */
266
    protected function generateCacheKey(Builder $builder, array $columns)
267
    {
268
        $query = $builder->getQuery();
269
        $vars = [
270
            'aggregate'   => $query->aggregate,
271
            'columns'     => $query->columns,
272
            'distinct'    => $query->distinct,
273
            'from'        => $query->from,
274
            'joins'       => $query->joins,
275
            'wheres'      => $query->wheres,
276
            'groups'      => $query->groups,
277
            'havings'     => $query->havings,
278
            'orders'      => $query->orders,
279
            'limit'       => $query->limit,
280
            'offset'      => $query->offset,
281
            'unions'      => $query->unions,
282
            'unionLimit'  => $query->unionLimit,
283
            'unionOffset' => $query->unionOffset,
284
            'unionOrders' => $query->unionOrders,
285
            'lock'        => $query->lock,
286
        ];
287
288
        return md5(json_encode([
289
            $vars,
290
            $columns,
291
            __CLASS__,
292
            $this->getCacheDriver(),
293
            $this->getCacheLifetime(),
294
            $builder->getEagerLoads(),
295
            $builder->getBindings(),
296
            $builder->toSql(),
297
        ]));
298
    }
299
300
    /**
301
     * Cache given callback.
302
     *
303
     * @param \Illuminate\Database\Eloquent\Builder $builder
304
     * @param array                                 $columns
305
     * @param \Closure                              $closure
306
     *
307
     * @return mixed
308
     */
309
    public function cacheQuery(Builder $builder, array $columns, Closure $closure)
310
    {
311
        $modelName = __CLASS__;
312
        $lifetime = $this->getCacheLifetime();
313
        $cacheKey = $this->generateCacheKey($builder, $columns);
314
315
        // Switch cache driver on runtime
316
        if ($driver = $this->getCacheDriver()) {
317
            app('cache')->setDefaultDriver($driver);
318
        }
319
320
        // We need cache tags, check if default driver supports it
321
        if (method_exists(app('cache')->getStore(), 'tags')) {
322
            $result = $lifetime === -1 ? app('cache')->tags($modelName)->rememberForever($cacheKey, $closure) : app('cache')->tags($modelName)->remember($cacheKey, $lifetime, $closure);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 185 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
323
324
            return $result;
325
        }
326
327
        $result = $lifetime === -1 ? app('cache')->rememberForever($cacheKey, $closure) : app('cache')->remember($cacheKey, $lifetime, $closure);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 145 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
328
329
        // Default cache driver doesn't support tags, let's do it manually
330
        static::storeCacheKey($modelName, $cacheKey);
331
332
        // We're done, let's clean up!
333
        $this->resetCacheConfig();
334
335
        return $result;
336
    }
337
338
    /**
339
     * Create a new Eloquent query builder for the model.
340
     *
341
     * @param \Illuminate\Database\Query\Builder $query
342
     *
343
     * @return \Illuminate\Database\Eloquent\Builder|static
344
     */
345
    public function newEloquentBuilder($query)
346
    {
347
        return new EloquentBuilder($query);
348
    }
349
350
    /**
351
     * Attach events to the model.
352
     *
353
     * @return void
354
     */
355
    protected static function attacheEvents()
356
    {
357
        static::updated(function (Model $cachedModel) {
358
            if ($cachedModel::isCacheClearEnabled()) {
359
                $cachedModel::forgetCache();
360
            }
361
        });
362
363
        static::created(function (Model $cachedModel) {
364
            if ($cachedModel::isCacheClearEnabled()) {
365
                $cachedModel::forgetCache();
366
            }
367
        });
368
369
        static::deleted(function (Model $cachedModel) {
370
            if ($cachedModel::isCacheClearEnabled()) {
371
                $cachedModel::forgetCache();
372
            }
373
        });
374
    }
375
}
376