Completed
Push — develop ( cb0c08...873420 )
by Abdelrahman
04:15
created

CacheableEloquent::attacheEvents()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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