Completed
Push — master ( ca663c...af64de )
by Abdelrahman
40:03 queued 32:43
created

CacheableEloquent::enableCacheClear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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