Completed
Push — develop ( ca663c...04b539 )
by Abdelrahman
01:37
created

CacheableEloquent::bootCacheableEloquent()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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