CacheableRepository::skipCache()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
dl 0
loc 5
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Salah3id\Domains\Repository\Traits;
4
5
use Illuminate\Contracts\Cache\Repository as CacheRepository;
6
use Salah3id\Domains\Repository\Contracts\CriteriaInterface;
7
use Salah3id\Domains\Repository\Helpers\CacheKeys;
8
use ReflectionObject;
9
use Exception;
10
11
/**
12
 * Class CacheableRepository
13
 * @package Salah3id\Domains\Repository\Traits
14
 * @author Anderson Andrade <[email protected]>
15
 */
16
trait CacheableRepository
17
{
18
19
    /**
20
     * @var CacheRepository
21
     */
22
    protected $cacheRepository = null;
23
24
    /**
25
     * Set Cache Repository
26
     *
27
     * @param CacheRepository $repository
28
     *
29
     * @return $this
30
     */
31
    public function setCacheRepository(CacheRepository $repository)
32
    {
33
        $this->cacheRepository = $repository;
34
35
        return $this;
36
    }
37
38
    /**
39
     * Return instance of Cache Repository
40
     *
41
     * @return CacheRepository
42
     */
43
    public function getCacheRepository()
44
    {
45
        if (is_null($this->cacheRepository)) {
46
            $this->cacheRepository = app(config('domains.repo-cache.repository', 'cache'));
0 ignored issues
show
Documentation Bug introduced by
It seems like app(config('domains.repo....repository', 'cache')) can also be of type Illuminate\Contracts\Foundation\Application. However, the property $cacheRepository is declared as type Illuminate\Contracts\Cache\Repository. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
47
        }
48
49
        return $this->cacheRepository;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->cacheRepository also could return the type Illuminate\Contracts\Foundation\Application which is incompatible with the documented return type Illuminate\Contracts\Cache\Repository.
Loading history...
50
    }
51
52
    /**
53
     * Skip Cache
54
     *
55
     * @param bool $status
56
     *
57
     * @return $this
58
     */
59
    public function skipCache($status = true)
60
    {
61
        $this->cacheSkip = $status;
0 ignored issues
show
Bug Best Practice introduced by
The property cacheSkip does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
62
63
        return $this;
64
    }
65
66
    /**
67
     * @return bool
68
     */
69
    public function isSkippedCache()
70
    {
71
        $skipped = isset($this->cacheSkip) ? $this->cacheSkip : false;
72
        $request = app('Illuminate\Http\Request');
73
        $skipCacheParam = config('domains.repo-cache.params.skipCache', 'skipCache');
74
75
        if ($request->has($skipCacheParam) && $request->get($skipCacheParam)) {
76
            $skipped = true;
77
        }
78
79
        return $skipped;
80
    }
81
82
    /**
83
     * @param $method
84
     *
85
     * @return bool
86
     */
87
    protected function allowedCache($method)
88
    {
89
        $cacheEnabled = config('domains.repo-cache.enabled', true);
90
91
        if (!$cacheEnabled) {
92
            return false;
93
        }
94
95
        $cacheOnly = isset($this->cacheOnly) ? $this->cacheOnly : config('domains.repo-cache.allowed.only', null);
96
        $cacheExcept = isset($this->cacheExcept) ? $this->cacheExcept : config('domains.repo-cache.allowed.except', null);
97
98
        if (is_array($cacheOnly)) {
99
            return in_array($method, $cacheOnly);
100
        }
101
102
        if (is_array($cacheExcept)) {
103
            return !in_array($method, $cacheExcept);
104
        }
105
106
        if (is_null($cacheOnly) && is_null($cacheExcept)) {
107
            return true;
108
        }
109
110
        return false;
111
    }
112
113
    /**
114
     * Get Cache key for the method
115
     *
116
     * @param $method
117
     * @param $args
118
     *
119
     * @return string
120
     */
121
    public function getCacheKey($method, $args = null)
122
    {
123
124
        $request = app('Illuminate\Http\Request');
125
        $args = serialize($args);
126
        $criteria = $this->serializeCriteria();
127
        $key = sprintf('%s@%s-%s', get_called_class(), $method, md5($args . $criteria . $request->fullUrl()));
128
129
        CacheKeys::putKey(get_called_class(), $key);
130
131
        return $key;
132
133
    }
134
135
    /**
136
     * Serialize the criteria making sure the Closures are taken care of.
137
     *
138
     * @return string
139
     */
140
    protected function serializeCriteria()
141
    {
142
        try {
143
            return serialize($this->getCriteria());
0 ignored issues
show
Bug introduced by
The method getCriteria() does not exist on Salah3id\Domains\Reposit...its\CacheableRepository. Did you maybe mean getByCriteria()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

143
            return serialize($this->/** @scrutinizer ignore-call */ getCriteria());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
144
        } catch (Exception $e) {
145
            return serialize($this->getCriteria()->map(function ($criterion) {
146
                return $this->serializeCriterion($criterion);
147
            }));
148
        }
149
    }
150
151
    /**
152
     * Serialize single criterion with customized serialization of Closures.
153
     *
154
     * @param  \Salah3id\Domains\Repository\Contracts\CriteriaInterface $criterion
155
     * @return \Salah3id\Domains\Repository\Contracts\CriteriaInterface|array
156
     *
157
     * @throws \Exception
158
     */
159
    protected function serializeCriterion($criterion)
160
    {
161
        try {
162
            serialize($criterion);
163
164
            return $criterion;
165
        } catch (Exception $e) {
166
            // We want to take care of the closure serialization errors,
167
            // other than that we will simply re-throw the exception.
168
            if ($e->getMessage() !== "Serialization of 'Closure' is not allowed") {
169
                throw $e;
170
            }
171
172
            $r = new ReflectionObject($criterion);
173
174
            return [
175
                'hash' => md5((string) $r),
176
            ];
177
        }
178
    }
179
180
    /**
181
     * Get cache time
182
     * 
183
     * Return minutes: version < 5.8
184
     * Return seconds: version >= 5.8
185
     *
186
     * @return int
187
     */
188
    public function getCacheTime()
189
    {
190
        $cacheMinutes = isset($this->cacheMinutes) ? $this->cacheMinutes : config('domains.repo-cache.minutes', 30);
191
192
        /**
193
         * https://laravel.com/docs/5.8/upgrade#cache-ttl-in-seconds
194
         */
195
        if ($this->versionCompare($this->app->version(), "5.7.*", ">")) {
0 ignored issues
show
Bug introduced by
It seems like versionCompare() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

195
        if ($this->/** @scrutinizer ignore-call */ versionCompare($this->app->version(), "5.7.*", ">")) {
Loading history...
196
            return $cacheMinutes * 60;
197
        }
198
199
        return $cacheMinutes;
200
    }
201
202
    /**
203
     * Retrieve all data of repository
204
     *
205
     * @param array $columns
206
     *
207
     * @return mixed
208
     */
209
    public function all($columns = ['*'])
210
    {
211
        if (!$this->allowedCache('all') || $this->isSkippedCache()) {
212
            return parent::all($columns);
213
        }
214
215
        $key = $this->getCacheKey('all', func_get_args());
216
        $time = $this->getCacheTime();
217
        $value = $this->getCacheRepository()->remember($key, $time, function () use ($columns) {
218
            return parent::all($columns);
219
        });
220
221
        $this->resetModel();
0 ignored issues
show
Bug introduced by
It seems like resetModel() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
        $this->/** @scrutinizer ignore-call */ 
222
               resetModel();
Loading history...
222
        $this->resetScope();
0 ignored issues
show
Bug introduced by
It seems like resetScope() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

222
        $this->/** @scrutinizer ignore-call */ 
223
               resetScope();
Loading history...
223
        return $value;
224
    }
225
226
    /**
227
     * Retrieve all data of repository, paginated
228
     *
229
     * @param null  $limit
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $limit is correct as it would always require null to be passed?
Loading history...
230
     * @param array $columns
231
     * @param string $method
232
     *
233
     * @return mixed
234
     */
235
    public function paginate($limit = null, $columns = ['*'], $method = 'paginate')
236
    {
237
        if (!$this->allowedCache('paginate') || $this->isSkippedCache()) {
238
            return parent::paginate($limit, $columns, $method);
239
        }
240
241
        $key = $this->getCacheKey('paginate', func_get_args());
242
243
        $time = $this->getCacheTime();
244
        $value = $this->getCacheRepository()->remember($key, $time, function () use ($limit, $columns, $method) {
245
            return parent::paginate($limit, $columns, $method);
246
        });
247
248
        $this->resetModel();
249
        $this->resetScope();
250
        return $value;
251
    }
252
253
    /**
254
     * Find data by id
255
     *
256
     * @param       $id
257
     * @param array $columns
258
     *
259
     * @return mixed
260
     */
261
    public function find($id, $columns = ['*'])
262
    {
263
        if (!$this->allowedCache('find') || $this->isSkippedCache()) {
264
            return parent::find($id, $columns);
265
        }
266
267
        $key = $this->getCacheKey('find', func_get_args());
268
        $time = $this->getCacheTime();
269
        $value = $this->getCacheRepository()->remember($key, $time, function () use ($id, $columns) {
270
            return parent::find($id, $columns);
271
        });
272
273
        $this->resetModel();
274
        $this->resetScope();
275
        return $value;
276
    }
277
278
    /**
279
     * Find data by field and value
280
     *
281
     * @param       $field
282
     * @param       $value
283
     * @param array $columns
284
     *
285
     * @return mixed
286
     */
287
    public function findByField($field, $value = null, $columns = ['*'])
288
    {
289
        if (!$this->allowedCache('findByField') || $this->isSkippedCache()) {
290
            return parent::findByField($field, $value, $columns);
291
        }
292
293
        $key = $this->getCacheKey('findByField', func_get_args());
294
        $time = $this->getCacheTime();
295
        $value = $this->getCacheRepository()->remember($key, $time, function () use ($field, $value, $columns) {
296
            return parent::findByField($field, $value, $columns);
297
        });
298
299
        $this->resetModel();
300
        $this->resetScope();
301
        return $value;
302
    }
303
304
    /**
305
     * Find data by multiple fields
306
     *
307
     * @param array $where
308
     * @param array $columns
309
     *
310
     * @return mixed
311
     */
312
    public function findWhere(array $where, $columns = ['*'])
313
    {
314
        if (!$this->allowedCache('findWhere') || $this->isSkippedCache()) {
315
            return parent::findWhere($where, $columns);
316
        }
317
318
        $key = $this->getCacheKey('findWhere', func_get_args());
319
        $time = $this->getCacheTime();
320
        $value = $this->getCacheRepository()->remember($key, $time, function () use ($where, $columns) {
321
            return parent::findWhere($where, $columns);
322
        });
323
324
        $this->resetModel();
325
        $this->resetScope();
326
        return $value;
327
    }
328
329
    /**
330
     * Find data by Criteria
331
     *
332
     * @param CriteriaInterface $criteria
333
     *
334
     * @return mixed
335
     */
336
    public function getByCriteria(CriteriaInterface $criteria)
337
    {
338
        if (!$this->allowedCache('getByCriteria') || $this->isSkippedCache()) {
339
            return parent::getByCriteria($criteria);
340
        }
341
342
        $key = $this->getCacheKey('getByCriteria', func_get_args());
343
        $time = $this->getCacheTime();
344
        $value = $this->getCacheRepository()->remember($key, $time, function () use ($criteria) {
345
            return parent::getByCriteria($criteria);
346
        });
347
348
        $this->resetModel();
349
        $this->resetScope();
350
        return $value;
351
    }
352
}
353