Completed
Push — master ( c0b066...2cb525 )
by Abdelrahman
03:51 queued 01:59
created

BaseRepository::isCacheEnabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/*
4
 * NOTICE OF LICENSE
5
 *
6
 * Part of the Rinvex Repository 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 Repository Package
12
 * License: The MIT License (MIT)
13
 * Link:    https://rinvex.com
14
 */
15
16
namespace Rinvex\Repository\Repositories;
17
18
use Closure;
19
use Illuminate\Contracts\Container\Container;
20
use Rinvex\Repository\Contracts\RepositoryContract;
21
22
abstract class BaseRepository implements RepositoryContract
23
{
24
    /**
25
     * The IoC container instance.
26
     *
27
     * @var \Illuminate\Contracts\Container\Container
28
     */
29
    protected $container;
30
31
    /**
32
     * The repository identifier.
33
     *
34
     * @var string
35
     */
36
    protected $repositoryId;
37
38
    /**
39
     * The repository model.
40
     *
41
     * @var string
42
     */
43
    protected $model;
44
45
    /**
46
     * The repository cache lifetime.
47
     *
48
     * @var float|int
49
     */
50
    protected $cacheLifetime;
51
52
    /**
53
     * The repository cache driver.
54
     *
55
     * @var string
56
     */
57
    protected $cacheDriver;
58
59
    /**
60
     * Indicate if the repository cache clear is enabled.
61
     *
62
     * @var bool
63
     */
64
    protected $cacheClearEnabled = true;
65
66
    /**
67
     * The relations to eager load on query execution.
68
     *
69
     * @var array
70
     */
71
    protected $relations = [];
72
73
    /**
74
     * The query where clauses.
75
     *
76
     * @var array
77
     */
78
    protected $where = [];
79
80
    /**
81
     * The query whereIn clauses.
82
     *
83
     * @var array
84
     */
85
    protected $whereIn = [];
86
87
    /**
88
     * The query whereNotIn clauses.
89
     *
90
     * @var array
91
     */
92
    protected $whereNotIn = [];
93
94
    /**
95
     * The "offset" value of the query.
96
     *
97
     * @var int
98
     */
99
    protected $offset;
100
101
    /**
102
     * The "limit" value of the query.
103
     *
104
     * @var int
105
     */
106
    protected $limit;
107
108
    /**
109
     * The column to order results by.
110
     *
111
     * @var array
112
     */
113
    protected $orderBy = [];
114
115
    /**
116
     * Execute given callback and cache the result.
117
     *
118
     * @param string   $class
119
     * @param string   $method
120
     * @param array    $args
121
     * @param \Closure $closure
122
     *
123
     * @return mixed
124
     */
125
    protected function executeCallback($class, $method, $args, Closure $closure)
126
    {
127
        $skipUri = $this->getContainer('config')->get('rinvex.repository.cache.skip_uri');
128
129
        // Check if cache is enabled
130
        if ($this->getCacheLifetime() && ! $this->getContainer('request')->has($skipUri)) {
131
            $repositoryId = $this->getRepositoryId();
132
            $lifetime     = $this->getCacheLifetime();
133
            $hash         = $this->generateHash($args);
134
            $cacheKey     = $class.'@'.$method.'.'.$hash;
135
136
            // Switch cache driver on runtime
137
            if ($driver = $this->getCacheDriver()) {
138
                $this->getContainer('cache')->setDefaultDriver($driver);
139
            }
140
141
            // We need cache tags, check if default driver supports it
142
            if (method_exists($this->getContainer('cache')->getStore(), 'tags')) {
143
                $result = $lifetime === -1
144
                    ? $this->getContainer('cache')->tags($repositoryId)->rememberForever($cacheKey, $closure)
145
                    : $this->getContainer('cache')->tags($repositoryId)->remember($cacheKey, $lifetime, $closure);
146
147
                // We're done, let's clean up!
148
                $this->resetRepository();
149
150
                return $result;
151
            }
152
153
            // Default cache driver doesn't support tags, let's do it manually
154
            $this->storeCacheKeys($class, $method, $hash);
155
156
            $result = $lifetime === -1
157
                ? $this->getContainer('cache')->rememberForever($cacheKey, $closure)
158
                : $this->getContainer('cache')->remember($cacheKey, $lifetime, $closure);
159
160
            // We're done, let's clean up!
161
            $this->resetRepository();
162
163
            return $result;
164
        }
165
166
        // Cache disabled, just execute qurey & return result
167
        $result = call_user_func($closure);
168
169
        // We're done, let's clean up!
170
        $this->resetRepository();
171
172
        return $result;
173
    }
174
175
    /**
176
     * Generate unique query hash.
177
     *
178
     * @param $args
179
     *
180
     * @return string
181
     */
182
    protected function generateHash($args)
183
    {
184
        return md5(json_encode($args + [
185
                $this->getRepositoryId(),
186
                $this->getModel(),
187
                $this->getCacheDriver(),
188
                $this->getCacheLifetime(),
189
                $this->relations,
190
                $this->where,
191
                $this->whereIn,
192
                $this->whereNotIn,
193
                $this->offset,
194
                $this->limit,
195
                $this->orderBy,
196
            ]));
197
    }
198
199
    /**
200
     * Reset repository to it's defaults.
201
     *
202
     * @return $this
203
     */
204
    protected function resetRepository()
205
    {
206
        $this->cacheLifetime = null;
207
        $this->cacheDriver   = null;
208
        $this->relations     = [];
209
        $this->where         = [];
210
        $this->whereIn       = [];
211
        $this->whereNotIn    = [];
212
        $this->offset        = null;
213
        $this->limit         = null;
214
        $this->orderBy       = [];
215
216
        return $this;
217
    }
218
219
    /**
220
     * Prepare query.
221
     *
222
     * @param object $model
223
     *
224
     * @return object
225
     */
226
    protected function prepareQuery($model)
227
    {
228
        // Set the relationships that should be eager loaded
229
        if (! empty($this->relations)) {
230
            $model = $model->with($this->relations);
231
        }
232
233
        // Add a basic where clause to the query
234
        foreach ($this->where as $where) {
235
            list($attribute, $operator, $value, $boolean) = array_pad($where, 4, null);
236
237
            $model = $model->where($attribute, $operator, $value, $boolean);
238
        }
239
240
        // Add a "where in" clause to the query
241
        foreach ($this->whereIn as $whereIn) {
242
            list($attribute, $values, $boolean, $not) = array_pad($whereIn, 4, null);
243
244
            $model = $model->whereIn($attribute, $values, $boolean, $not);
245
        }
246
247
        // Add a "where not in" clause to the query
248
        foreach ($this->whereNotIn as $whereNotIn) {
249
            list($attribute, $values, $boolean) = array_pad($whereNotIn, 3, null);
250
251
            $model = $model->whereNotIn($attribute, $values, $boolean);
252
        }
253
254
        // Set the "offset" value of the query
255
        if ($this->offset > 0) {
256
            $model = $model->offset($this->offset);
257
        }
258
259
        // Set the "limit" value of the query
260
        if ($this->limit > 0) {
261
            $model = $model->limit($this->limit);
262
        }
263
264
        // Add an "order by" clause to the query.
265
        if (! empty($this->orderBy)) {
266
            list($attribute, $direction) = $this->orderBy;
267
268
            $model = $model->orderBy($attribute, $direction);
269
        }
270
271
        return $model;
272
    }
273
274
    /**
275
     * Store cache keys by mimicking cache tags.
276
     *
277
     * @param string $class
278
     * @param string $method
279
     * @param string $hash
280
     *
281
     * @return void
282
     */
283
    protected function storeCacheKeys($class, $method, $hash)
284
    {
285
        $keysFile  = $this->getContainer('config')->get('rinvex.repository.cache.keys_file');
286
        $cacheKeys = $this->getCacheKeys($keysFile);
287
288
        if (! isset($cacheKeys[$class]) || ! in_array($method.'.'.$hash, $cacheKeys[$class])) {
289
            $cacheKeys[$class][] = $method.'.'.$hash;
290
            file_put_contents($keysFile, json_encode($cacheKeys));
291
        }
292
    }
293
294
    /**
295
     * Get cache keys.
296
     *
297
     * @param string $file
298
     *
299
     * @return array
300
     */
301
    protected function getCacheKeys($file)
302
    {
303
        if (! file_exists($file)) {
304
            file_put_contents($file, null);
305
        }
306
307
        return json_decode(file_get_contents($file), true) ?: [];
308
    }
309
310
    /**
311
     * Flush cache keys by mimicking cache tags.
312
     *
313
     * @return array
314
     */
315
    protected function flushCacheKeys()
316
    {
317
        $flushedKeys  = [];
318
        $calledClasss = get_called_class();
319
        $config       = $this->getContainer('config')->get('rinvex.repository.cache');
320
        $cacheKeys    = $this->getCacheKeys($config['keys_file']);
321
322
        if (isset($cacheKeys[$calledClasss]) && is_array($cacheKeys[$calledClasss])) {
323
            foreach ($cacheKeys[$calledClasss] as $cacheKey) {
324
                $flushedKeys[] = $calledClasss.'@'.$cacheKey;
325
            }
326
327
            unset($cacheKeys[$calledClasss]);
328
            file_put_contents($config['keys_file'], json_encode($cacheKeys));
329
        }
330
331
        return $flushedKeys;
332
    }
333
334
    /**
335
     * Set the IoC container instance.
336
     *
337
     * @param \Illuminate\Contracts\Container\Container $container
338
     *
339
     * @return $this
340
     */
341
    public function setContainer(Container $container)
342
    {
343
        $this->container = $container;
344
345
        return $this;
346
    }
347
348
    /**
349
     * Get the IoC container instance or any of it's services.
350
     *
351
     * @param string|null $service
352
     *
353
     * @return object
354
     */
355
    public function getContainer($service = null)
356
    {
357
        return is_null($service) ? ($this->container ?: app()) : ($this->container[$service] ?: app($service));
358
    }
359
360
    /**
361
     * Set the repository identifier.
362
     *
363
     * @param string $repositoryId
364
     *
365
     * @return $this
366
     */
367
    public function setRepositoryId($repositoryId)
368
    {
369
        $this->repositoryId = $repositoryId;
370
371
        return $this;
372
    }
373
374
    /**
375
     * Get the repository identifier.
376
     *
377
     * @return string
378
     */
379
    public function getRepositoryId()
380
    {
381
        return $this->repositoryId ?: get_called_class();
382
    }
383
384
    /**
385
     * Set the repository model.
386
     *
387
     * @param string $model
388
     *
389
     * @return $this
390
     */
391
    public function setModel($model)
392
    {
393
        $this->model = $model;
394
395
        return $this;
396
    }
397
398
    /**
399
     * Get the repository model.
400
     *
401
     * @return string
402
     */
403
    public function getModel()
404
    {
405
        return $this->model ?: str_replace(['Repositories', 'Repository'], ['Models', ''], get_called_class());
406
    }
407
408
    /**
409
     * Set the repository cache lifetime.
410
     *
411
     * @param float|int $cacheLifetime
412
     *
413
     * @return $this
414
     */
415
    public function setCacheLifetime($cacheLifetime)
416
    {
417
        $this->cacheLifetime = $cacheLifetime;
418
419
        return $this;
420
    }
421
422
    /**
423
     * Get the repository cache lifetime.
424
     *
425
     * @return float|int
426
     */
427
    public function getCacheLifetime()
428
    {
429
        // Return value even if it's zero "0" (which means cache is disabled)
430
        return ! is_null($this->cacheLifetime)
431
            ? $this->cacheLifetime
432
            : $this->getContainer('config')->get('rinvex.repository.cache.lifetime');
433
    }
434
435
    /**
436
     * Set the repository cache driver.
437
     *
438
     * @param string $cacheDriver
439
     *
440
     * @return $this
441
     */
442
    public function setCacheDriver($cacheDriver)
443
    {
444
        $this->cacheDriver = $cacheDriver;
445
446
        return $this;
447
    }
448
449
    /**
450
     * Get the repository cache driver.
451
     *
452
     * @return string
453
     */
454
    public function getCacheDriver()
455
    {
456
        return $this->cacheDriver;
457
    }
458
459
    /**
460
     * Enable repository cache clear.
461
     *
462
     * @param bool $status
463
     *
464
     * @return $this
465
     */
466
    public function enableCacheClear($status = true)
467
    {
468
        $this->cacheClearEnabled = $status;
469
470
        return $this;
471
    }
472
473
    /**
474
     * Determine if repository cache clear is enabled.
475
     *
476
     * @return bool
477
     */
478
    public function isCacheClearEnabled()
479
    {
480
        return $this->cacheClearEnabled;
481
    }
482
483
    /**
484
     * Forget the repository cache.
485
     *
486
     * @return $this
487
     */
488
    public function forgetCache()
489
    {
490
        if ($this->getCacheLifetime()) {
491
            // Flush cache tags
492
            if (method_exists($this->getContainer('cache')->getStore(), 'tags')) {
493
                $this->getContainer('cache')->tags($this->getRepositoryId())->flush();
494
            } else {
495
                // Flush cache keys, then forget actual cache
496
                foreach ($this->flushCacheKeys() as $cacheKey) {
497
                    $this->getContainer('cache')->forget($cacheKey);
498
                }
499
            }
500
501
            $this->getContainer('events')->fire($this->getRepositoryId().'.entity.cache.flushed', [$this]);
502
        }
503
504
        return $this;
505
    }
506
507
    /**
508
     * Set the relationships that should be eager loaded.
509
     *
510
     * @param array $relations
511
     *
512
     * @return $this
513
     */
514
    public function with(array $relations)
515
    {
516
        $this->relations = $relations;
517
518
        return $this;
519
    }
520
521
    /**
522
     * Add a basic where clause to the query.
523
     *
524
     * @param string $attribute
525
     * @param string $operator
526
     * @param mixed  $value
527
     * @param string $boolean
528
     *
529
     * @return $this
530
     */
531
    public function where($attribute, $operator = null, $value = null, $boolean = 'and')
532
    {
533
        // The last `$boolean` expression is intentional to fix list() & array_pad() results
534
        $this->where[] = [$attribute, $operator, $value, $boolean ?: 'and'];
535
536
        return $this;
537
    }
538
539
    /**
540
     * Add a "where in" clause to the query.
541
     *
542
     * @param string $attribute
543
     * @param mixed  $values
544
     * @param string $boolean
545
     * @param bool   $not
546
     *
547
     * @return $this
548
     */
549
    public function whereIn($attribute, $values, $boolean = 'and', $not = false)
550
    {
551
        // The last `$boolean` & `$not` expressions are intentional to fix list() & array_pad() results
552
        $this->whereIn[] = [$attribute, $values, $boolean ?: 'and', (bool) $not];
553
554
        return $this;
555
    }
556
557
    /**
558
     * Add a "where not in" clause to the query.
559
     *
560
     * @param string $attribute
561
     * @param mixed  $values
562
     * @param string $boolean
563
     *
564
     * @return $this
565
     */
566
    public function whereNotIn($attribute, $values, $boolean = 'and')
567
    {
568
        // The last `$boolean` expression is intentional to fix list() & array_pad() results
569
        $this->whereNotIn[] = [$attribute, $values, $boolean ?: 'and'];
570
571
        return $this;
572
    }
573
574
    /**
575
     * Set the "offset" value of the query.
576
     *
577
     * @param int $offset
578
     *
579
     * @return $this
580
     */
581
    public function offset($offset)
582
    {
583
        $this->offset = $offset;
584
585
        return $this;
586
    }
587
588
    /**
589
     * Set the "limit" value of the query.
590
     *
591
     * @param int $limit
592
     *
593
     * @return $this
594
     */
595
    public function limit($limit)
596
    {
597
        $this->limit = $limit;
598
599
        return $this;
600
    }
601
602
    /**
603
     * Add an "order by" clause to the query.
604
     *
605
     * @param string $attribute
606
     * @param string $direction
607
     *
608
     * @return $this
609
     */
610
    public function orderBy($attribute, $direction = 'asc')
611
    {
612
        $this->orderBy = [$attribute, $direction];
613
614
        return $this;
615
    }
616
617
    /**
618
     * Dynamically pass missing static methods to the model.
619
     *
620
     * @param $method
621
     * @param $parameters
622
     *
623
     * @return mixed
624
     */
625
    public static function __callStatic($method, $parameters)
626
    {
627
        return call_user_func_array([new static(), $method], $parameters);
628
    }
629
630
    /**
631
     * Dynamically pass missing methods to the model.
632
     *
633
     * @param string $method
634
     * @param array  $parameters
635
     *
636
     * @return mixed
637
     */
638
    public function __call($method, $parameters)
639
    {
640
        $model = $this->model;
641
642
        return call_user_func_array([$model, $method], $parameters);
643
    }
644
}
645