Passed
Branch master (9d5f0f)
by Mike
05:15
created

Caching   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
eloc 164
c 6
b 1
f 0
dl 0
loc 313
rs 5.5199
wmc 56

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __call() 0 13 3
A getCacheCooldownDetails() 0 15 1
A setCacheCooldownSavedAtTimestamp() 0 9 1
A cache() 0 17 3
A getModelCacheCooldown() 0 16 4
A makeCacheKey() 0 30 5
A flushCache() 0 18 3
A disableModelCaching() 0 5 1
A checkCooldownAndFlushAfterPersisting() 0 25 6
A getCachePrefix() 0 18 4
A makeCacheTags() 0 15 3
A checkCooldownAndRemoveIfExpired() 0 23 3
A applyScopes() 0 7 2
B isCachable() 0 36 9
B applyScopesToInstance() 0 27 8

How to fix   Complexity   

Complex Class

Complex classes like Caching often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Caching, and based on these observations, apply Extract Interface, too.

1
<?php namespace GeneaLabs\LaravelModelCaching\Traits;
2
3
use Closure;
4
use GeneaLabs\LaravelModelCaching\CachedBuilder;
5
use GeneaLabs\LaravelModelCaching\CacheKey;
6
use GeneaLabs\LaravelModelCaching\CacheTags;
7
use Illuminate\Cache\TaggableStore;
8
use Illuminate\Container\Container;
9
use Illuminate\Database\Eloquent\Model;
10
use Illuminate\Database\Eloquent\Scope;
11
use Illuminate\Database\Query\Builder;
12
use Illuminate\Support\Carbon;
13
14
trait Caching
15
{
16
    protected $isCachable = true;
17
    protected $scopesAreApplied = false;
18
    protected $macroKey = "";
19
20
    public function __call($method, $parameters)
21
    {
22
        $result = parent::__call($method, $parameters);
23
24
        if (isset($this->localMacros[$method])) {
25
            $this->macroKey .= "-{$method}";
26
27
            if ($parameters) {
28
                $this->macroKey .= implode("_", $parameters);
29
            }
30
        }
31
32
        return $result;
33
    }
34
35
    public function applyScopes() : self
36
    {
37
        if ($this->scopesAreApplied) {
38
            return $this;
39
        }
40
        
41
        return parent::applyScopes();
42
    }
43
44
    protected function applyScopesToInstance()
45
    {
46
        if (! property_exists($this, "scopes")
47
            || $this->scopesAreApplied
48
        ) {
49
            return;
50
        }
51
52
        foreach ($this->scopes as $identifier => $scope) {
53
            if (! isset($this->scopes[$identifier])) {
54
                continue;
55
            }
56
57
            $this->callScope(function () use ($scope) {
58
                if ($scope instanceof Closure) {
59
                    $scope($this);
60
                }
61
62
                if ($scope instanceof Scope
63
                    && $this instanceof CachedBuilder
64
                ) {
65
                    $scope->apply($this, $this->getModel());
66
                }
67
            });
68
        }
69
70
        $this->scopesAreApplied = true;
71
    }
72
73
    public function cache(array $tags = [])
74
    {
75
        $cache = Container::getInstance()
76
            ->make("cache");
77
        $config = Container::getInstance()
78
            ->make("config")
79
            ->get("laravel-model-caching.store");
80
81
        if ($config) {
82
            $cache = $cache->store($config);
83
        }
84
85
        if (is_subclass_of($cache->getStore(), TaggableStore::class)) {
86
            $cache = $cache->tags($tags);
87
        }
88
89
        return $cache;
90
    }
91
92
    public function disableModelCaching()
93
    {
94
        $this->isCachable = false;
95
96
        return $this;
97
    }
98
99
    public function flushCache(array $tags = [])
100
    {
101
        if (count($tags) === 0) {
102
            $tags = $this->makeCacheTags();
103
        }
104
105
        $this->cache($tags)->flush();
106
107
        [$cacheCooldown] = $this->getModelCacheCooldown($this);
108
109
        if ($cacheCooldown) {
110
            $cachePrefix = $this->getCachePrefix();
111
            $modelClassName = get_class($this);
112
            $cacheKey = "{$cachePrefix}:{$modelClassName}-cooldown:saved-at";
113
114
            $this->cache()
115
                ->rememberForever($cacheKey, function () {
116
                    return (new Carbon)->now();
117
                });
118
        }
119
    }
120
121
    protected function getCachePrefix() : string
122
    {
123
        $cachePrefix = Container::getInstance()
124
            ->make("config")
125
            ->get("laravel-model-caching.cache-prefix", "");
126
127
        if ($this->model
128
            && property_exists($this->model, "cachePrefix")
129
        ) {
130
            $cachePrefix = $this->model->cachePrefix;
131
        }
132
133
        $cachePrefix = $cachePrefix
134
            ? "{$cachePrefix}:"
135
            : "";
136
137
        return "genealabs:laravel-model-caching:"
138
            . $cachePrefix;
139
    }
140
141
    protected function makeCacheKey(
142
        array $columns = ['*'],
143
        $idColumn = null,
144
        string $keyDifferentiator = ''
145
    ) : string {
146
        $this->applyScopesToInstance();
147
        $eagerLoad = $this->eagerLoad ?? [];
148
        $model = $this;
149
150
        if (property_exists($this, "model")) {
151
            $model = $this->model;
152
        }
153
154
        if (method_exists($this, "getModel")) {
155
            $model = $this->getModel();
156
        }
157
158
        $query = $this->query
159
            ?? Container::getInstance()
160
                ->make("db")
161
                ->query();
162
        
163
        if ($this->query
164
            && method_exists($this->query, "getQuery")
165
        ) {
166
            $query = $this->query->getQuery();
167
        }
168
169
        return (new CacheKey($eagerLoad, $model, $query, $this->macroKey))
170
            ->make($columns, $idColumn, $keyDifferentiator);
171
    }
172
173
    protected function makeCacheTags() : array
174
    {
175
        $eagerLoad = $this->eagerLoad ?? [];
176
        $model = $this->getModel() instanceof Model
177
            ? $this->getModel()
178
            : $this;
179
        $query = $this->query instanceof Builder
180
            ? $this->query
181
            : Container::getInstance()
182
                ->make("db")
183
                ->query();
184
        $tags = (new CacheTags($eagerLoad, $model, $query))
185
            ->make();
186
187
        return $tags;
188
    }
189
190
    public function getModelCacheCooldown(Model $instance) : array
191
    {
192
        if (! $instance->cacheCooldownSeconds) {
193
            return [null, null, null];
194
        }
195
196
        $cachePrefix = $this->getCachePrefix();
197
        $modelClassName = get_class($instance);
198
        [$cacheCooldown, $invalidatedAt, $savedAt] = $this
199
            ->getCacheCooldownDetails($instance, $cachePrefix, $modelClassName);
200
201
        if (! $cacheCooldown || $cacheCooldown === 0) {
202
            return [null, null, null];
203
        }
204
205
        return [$cacheCooldown, $invalidatedAt, $savedAt];
206
    }
207
208
    protected function getCacheCooldownDetails(
209
        Model $instance,
210
        string $cachePrefix,
211
        string $modelClassName
212
    ) : array {
213
        return [
214
            $instance
215
                ->cache()
216
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:seconds"),
217
            $instance
218
                ->cache()
219
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:invalidated-at"),
220
            $instance
221
                ->cache()
222
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:saved-at"),
223
        ];
224
    }
225
226
    protected function checkCooldownAndRemoveIfExpired(Model $instance)
227
    {
228
        [$cacheCooldown, $invalidatedAt] = $this->getModelCacheCooldown($instance);
229
230
        if (! $cacheCooldown
231
            || (new Carbon)->now()->diffInSeconds($invalidatedAt) < $cacheCooldown
232
        ) {
233
            return;
234
        }
235
236
        $cachePrefix = $this->getCachePrefix();
237
        $modelClassName = get_class($instance);
238
239
        $instance
240
            ->cache()
241
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:seconds");
242
        $instance
243
            ->cache()
244
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:invalidated-at");
245
        $instance
246
            ->cache()
247
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:saved-at");
248
        $instance->flushCache();
249
    }
250
251
    protected function checkCooldownAndFlushAfterPersisting(Model $instance, string $relationship = "")
252
    {
253
        [$cacheCooldown, $invalidatedAt] = $instance->getModelCacheCooldown($instance);
254
255
        if (! $cacheCooldown) {
256
            $instance->flushCache();
257
258
            if ($relationship) {
259
                $relationshipInstance = $instance->$relationship()->getModel();
260
261
                if (method_exists($relationshipInstance, "flushCache")) {
262
                    $relationshipInstance->flushCache();
263
                }
264
            }
265
266
            return;
267
        }
268
269
        $this->setCacheCooldownSavedAtTimestamp($instance);
270
271
        if ((new Carbon)->now()->diffInSeconds($invalidatedAt) >= $cacheCooldown) {
272
            $instance->flushCache();
273
274
            if ($relationship) {
275
                $instance->$relationship()->getModel()->flushCache();
276
            }
277
        }
278
    }
279
280
    public function isCachable() : bool
281
    {
282
        $isCacheDisabled = ! Container::getInstance()
283
            ->make("config")
284
            ->get("laravel-model-caching.enabled");
285
        $allRelationshipsAreCachable = true;
286
287
        if (property_exists($this, "eagerLoad")
288
            && $this->eagerLoad
289
        ) {
290
            $allRelationshipsAreCachable = collect($this
291
                ->eagerLoad)
292
                ->keys()
293
                ->reduce(function ($carry, $related) {
294
                    if (! method_exists($this, $related)
295
                        || $carry === false
296
                    ) {
297
                        return $carry;
298
                    }
299
        
300
                    $relatedModel = $this->model->$related()->getRelated();
301
302
                    if (! method_exists($relatedModel, "isCachable")
303
                        || ! $relatedModel->isCachable()
304
                    ) {
305
                        return false;
306
                    }
307
308
                    return true;
309
                })
310
                ?? true;
311
        }
312
313
        return $this->isCachable
314
            && ! $isCacheDisabled
315
            && $allRelationshipsAreCachable;
316
    }
317
318
    protected function setCacheCooldownSavedAtTimestamp(Model $instance)
319
    {
320
        $cachePrefix = $this->getCachePrefix();
321
        $modelClassName = get_class($instance);
322
        $cacheKey = "{$cachePrefix}:{$modelClassName}-cooldown:saved-at";
323
324
        $instance->cache()
325
            ->rememberForever($cacheKey, function () {
326
                return (new Carbon)->now();
327
            });
328
    }
329
}
330