Completed
Push — develop ( 301c4e...f79c61 )
by Abdelrahman
05:11
created

BaseRepository::getModel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 2
eloc 2
nc 2
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 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
        return call_user_func($closure);
168
    }
169
170
    /**
171
     * Generate unique query hash.
172
     *
173
     * @param $args
174
     *
175
     * @return string
176
     */
177
    protected function generateHash($args)
178
    {
179
        return md5(json_encode($args + [
180
                $this->getRepositoryId(),
181
                $this->getModel(),
182
                $this->getCacheDriver(),
183
                $this->getCacheLifetime(),
184
                $this->relations,
185
                $this->where,
186
                $this->whereIn,
187
                $this->whereNotIn,
188
                $this->offset,
189
                $this->limit,
190
                $this->orderBy,
191
            ]));
192
    }
193
194
    /**
195
     * Reset repository to it's defaults.
196
     *
197
     * @return $this
198
     */
199
    protected function resetRepository()
200
    {
201
        $this->cacheLifetime = null;
202
        $this->cacheDriver   = null;
203
        $this->relations     = [];
204
        $this->where         = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $where.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
205
        $this->whereIn       = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $whereIn.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
206
        $this->whereNotIn    = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $whereNotIn.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
207
        $this->offset        = null;
208
        $this->limit         = null;
209
        $this->orderBy       = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $orderBy.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
210
211
        return $this;
212
    }
213
214
    /**
215
     * Prepare query.
216
     *
217
     * @param object $model
218
     *
219
     * @return object
220
     */
221
    protected function prepareQuery($model)
222
    {
223
        // Set the relationships that should be eager loaded
224
        if ($this->relations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->relations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
225
            $model = $model->with($this->relations);
226
        }
227
228
        // Add a basic where clause to the query
229
        if (is_array($this->where) && ! empty($this->where)) {
230
            foreach ($this->where as $where) {
231
                list($attribute, $operator, $value, $boolean) = $where;
232
                $model = $model->where($attribute, $operator, $value, $boolean);
233
            }
234
        }
235
236
        // Add a "where in" clause to the query
237
        if (is_array($this->whereIn) && ! empty($this->whereIn)) {
238
            foreach ($this->whereIn as $whereIn) {
239
                list($attribute, $values, $boolean, $not) = $whereIn;
240
                $model = $model->whereIn($attribute, $values, $boolean, $not);
241
            }
242
        }
243
244
        // Add a "where not in" clause to the query
245
        if (is_array($this->whereNotIn) && ! empty($this->whereNotIn)) {
246
            foreach ($this->whereNotIn as $whereNotIn) {
247
                list($attribute, $values, $boolean) = $whereNotIn;
248
                $model = $model->whereNotIn($attribute, $values, $boolean);
249
            }
250
        }
251
252
        // Set the "offset" value of the query
253
        if ($this->offset) {
254
            $model = $model->offset($this->offset);
255
        }
256
257
        // Set the "limit" value of the query
258
        if ($this->limit) {
259
            $model = $model->limit($this->limit);
260
        }
261
262
        // Add an "order by" clause to the query.
263
        if ($this->orderBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orderBy of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
264
            list($attribute, $direction) = $this->orderBy;
265
266
            $model = $model->orderBy($attribute, $direction);
267
        }
268
269
        return $model;
270
    }
271
272
    /**
273
     * Store cache keys by mimicking cache tags.
274
     *
275
     * @param string $class
276
     * @param string $method
277
     * @param string $hash
278
     *
279
     * @return void
280
     */
281
    protected function storeCacheKeys($class, $method, $hash)
282
    {
283
        $keysFile  = $this->getContainer('config')->get('rinvex.repository.cache.keys_file');
284
        $cacheKeys = $this->getCacheKeys($keysFile);
285
286
        if (! isset($cacheKeys[$class]) || ! in_array($method.'.'.$hash, $cacheKeys[$class])) {
287
            $cacheKeys[$class][] = $method.'.'.$hash;
288
            file_put_contents($keysFile, json_encode($cacheKeys));
289
        }
290
    }
291
292
    /**
293
     * Get cache keys.
294
     *
295
     * @param string $file
296
     *
297
     * @return array
298
     */
299
    protected function getCacheKeys($file)
300
    {
301
        return json_decode(file_get_contents(file_exists($file) ? $file : file_put_contents($file, null)), true) ?: [];
302
    }
303
304
    /**
305
     * Flush cache keys by mimicking cache tags.
306
     *
307
     * @return array
308
     */
309
    protected function flushCacheKeys()
310
    {
311
        $flushedKeys  = [];
312
        $calledClasss = get_called_class();
313
        $config       = $this->getContainer('config')->get('rinvex.repository.cache');
314
        $cacheKeys    = $this->getCacheKeys($config['keys_file']);
315
316
        if (isset($cacheKeys[$calledClasss]) && is_array($cacheKeys[$calledClasss])) {
317
            foreach ($cacheKeys[$calledClasss] as $cacheKey) {
318
                $flushedKeys[] = $calledClasss.'@'.$cacheKey;
319
            }
320
321
            unset($cacheKeys[$calledClasss]);
322
            file_put_contents($config['keys_file'], json_encode($cacheKeys));
323
        }
324
325
        return $flushedKeys;
326
    }
327
328
    /**
329
     * Set the IoC container instance.
330
     *
331
     * @param \Illuminate\Contracts\Container\Container $container
332
     *
333
     * @return $this
334
     */
335
    public function setContainer(Container $container)
336
    {
337
        $this->container = $container;
338
339
        return $this;
340
    }
341
342
    /**
343
     * Get the IoC container instance or any of it's services.
344
     *
345
     * @param string|null $service
346
     *
347
     * @return object
348
     */
349
    public function getContainer($service = null)
350
    {
351
        return is_null($service) ? ($this->container ?: app()) : ($this->container[$service] ?: app($service));
352
    }
353
354
    /**
355
     * Set the repository identifier.
356
     *
357
     * @param string $repositoryId
358
     *
359
     * @return $this
360
     */
361
    public function setRepositoryId($repositoryId)
362
    {
363
        $this->repositoryId = $repositoryId;
364
365
        return $this;
366
    }
367
368
    /**
369
     * Get the repository identifier.
370
     *
371
     * @return string
372
     */
373
    public function getRepositoryId()
374
    {
375
        return $this->repositoryId ?: get_called_class();
376
    }
377
378
    /**
379
     * Set the repository model.
380
     *
381
     * @param string $model
382
     *
383
     * @return $this
384
     */
385
    public function setModel($model)
386
    {
387
        $this->model = $model;
388
389
        return $this;
390
    }
391
392
    /**
393
     * Get the repository model.
394
     *
395
     * @return string
396
     */
397
    public function getModel()
398
    {
399
        return $this->model ?: str_replace(['Repositories', 'Repository'], ['Models', ''], get_called_class());
400
    }
401
402
    /**
403
     * Set the repository cache lifetime.
404
     *
405
     * @param int $cacheLifetime
406
     *
407
     * @return $this
408
     */
409
    public function setCacheLifetime($cacheLifetime)
410
    {
411
        $this->cacheLifetime = $cacheLifetime;
412
413
        return $this;
414
    }
415
416
    /**
417
     * Get the repository cache lifetime.
418
     *
419
     * @return int
420
     */
421
    public function getCacheLifetime()
422
    {
423
        // Return value even if it's zero "0" (which means cache is disabled)
424
        return ! is_null($this->cacheLifetime)
425
            ? $this->cacheLifetime
426
            : $this->getContainer('config')->get('rinvex.repository.cache.lifetime');
427
    }
428
429
    /**
430
     * Set the repository cache driver.
431
     *
432
     * @param string $cacheDriver
433
     *
434
     * @return $this
435
     */
436
    public function setCacheDriver($cacheDriver)
437
    {
438
        $this->cacheDriver = $cacheDriver;
439
440
        return $this;
441
    }
442
443
    /**
444
     * Get the repository cache driver.
445
     *
446
     * @return string
447
     */
448
    public function getCacheDriver()
449
    {
450
        return $this->cacheDriver;
451
    }
452
453
    /**
454
     * Enable repository cache clear.
455
     *
456
     * @param bool $status
457
     *
458
     * @return $this
459
     */
460
    public function enableCacheClear($status = true)
461
    {
462
        $this->cacheClearEnabled = $status;
463
464
        return $this;
465
    }
466
467
    /**
468
     * Determine if repository cache clear is enabled.
469
     *
470
     * @return bool
471
     */
472
    public function isCacheClearEnabled()
473
    {
474
        return $this->cacheClearEnabled;
475
    }
476
477
    /**
478
     * Forget the repository cache.
479
     *
480
     * @return $this
481
     */
482
    public function forgetCache()
483
    {
484
        if ($this->getCacheLifetime()) {
485
            // Flush cache tags
486
            if (method_exists($this->getContainer('cache')->getStore(), 'tags')) {
487
                $this->getContainer('cache')->tags($this->getRepositoryId())->flush();
488
            } else {
489
                // Flush cache keys, then forget actual cache
490
                foreach ($this->flushCacheKeys() as $cacheKey) {
491
                    $this->getContainer('cache')->forget($cacheKey);
492
                }
493
            }
494
495
            $this->getContainer('events')->fire($this->getRepositoryId().'.entity.cache.flushed', [$this]);
496
        }
497
498
        return $this;
499
    }
500
501
    /**
502
     * Set the relationships that should be eager loaded.
503
     *
504
     * @param array $relations
505
     *
506
     * @return $this
507
     */
508
    public function with(array $relations)
509
    {
510
        $this->relations = $relations;
511
512
        return $this;
513
    }
514
515
    /**
516
     * Add a basic where clause to the query.
517
     *
518
     * @param  string  $attribute
519
     * @param  string  $operator
1 ignored issue
show
Documentation introduced by
Should the type for parameter $operator not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
520
     * @param  mixed   $value
521
     * @param  string  $boolean
522
     *
523
     * @return $this
524
     */
525
    public function where($attribute, $operator = null, $value = null, $boolean = 'and')
526
    {
527
        $this->where[] = [$attribute, $operator, $value, $boolean];
528
529
        return $this;
530
    }
531
532
    /**
533
     * Add a "where in" clause to the query.
534
     *
535
     * @param  string  $attribute
536
     * @param  mixed   $values
537
     * @param  string  $boolean
538
     * @param  bool    $not
539
     *
540
     * @return $this
541
     */
542
    public function whereIn($attribute, $values, $boolean = 'and', $not = false)
543
    {
544
        $this->whereIn[] = [$attribute, $values, $boolean, $not];
545
546
        return $this;
547
    }
548
549
    /**
550
     * Add a "where not in" clause to the query.
551
     *
552
     * @param  string  $attribute
553
     * @param  mixed   $values
554
     * @param  string  $boolean
555
     *
556
     * @return $this
557
     */
558
    public function whereNotIn($attribute, $values, $boolean = 'and')
559
    {
560
        $this->whereNotIn[] = [$attribute, $values, $boolean];
561
562
        return $this;
563
    }
564
565
    /**
566
     * Set the "offset" value of the query.
567
     *
568
     * @param int $offset
569
     *
570
     * @return $this
571
     */
572
    public function offset($offset)
573
    {
574
        $this->offset = $offset;
575
576
        return $this;
577
    }
578
579
    /**
580
     * Set the "limit" value of the query.
581
     *
582
     * @param int $limit
583
     *
584
     * @return $this
585
     */
586
    public function limit($limit)
587
    {
588
        $this->limit = $limit;
589
590
        return $this;
591
    }
592
593
    /**
594
     * Add an "order by" clause to the query.
595
     *
596
     * @param string $attribute
597
     * @param string $direction
598
     *
599
     * @return $this
600
     */
601
    public function orderBy($attribute, $direction = 'asc')
602
    {
603
        $this->orderBy = [$attribute, $direction];
604
605
        return $this;
606
    }
607
608
    /**
609
     * Dynamically pass missing static methods to the model.
610
     *
611
     * @param $method
612
     * @param $parameters
613
     *
614
     * @return mixed
615
     */
616
    public static function __callStatic($method, $parameters)
617
    {
618
        return call_user_func_array([new static(), $method], $parameters);
619
    }
620
621
    /**
622
     * Dynamically pass missing methods to the model.
623
     *
624
     * @param string $method
625
     * @param array  $parameters
626
     *
627
     * @return mixed
628
     */
629
    public function __call($method, $parameters)
630
    {
631
        $model = $this->model;
632
633
        return call_user_func_array([$model, $method], $parameters);
634
    }
635
}
636