Passed
Push — master ( 5d243c...e200e0 )
by Mike
03:01 queued 11s
created

Caching   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 124
dl 0
loc 246
rs 8.8798
c 0
b 0
f 0
wmc 44

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getCacheCooldownDetails() 0 15 1
A setCacheCooldownSavedAtTimestamp() 0 9 1
A cache() 0 13 3
A getModelCacheCooldown() 0 16 4
A makeCacheKey() 0 28 5
A flushCache() 0 18 3
A disableModelCaching() 0 5 1
A checkCooldownAndFlushAfterPersisting() 0 25 6
A getCachePrefix() 0 16 4
A makeCacheTags() 0 13 3
A checkCooldownAndRemoveIfExpired() 0 23 3
A isCachable() 0 4 2
B applyScopesToInstance() 0 24 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 Carbon\Carbon;
4
use Closure;
5
use GeneaLabs\LaravelModelCaching\CachedBuilder;
6
use GeneaLabs\LaravelModelCaching\CacheKey;
7
use GeneaLabs\LaravelModelCaching\CacheTags;
8
use Illuminate\Cache\TaggableStore;
9
use Illuminate\Database\Eloquent\Model;
10
use Illuminate\Database\Eloquent\Scope;
11
use Illuminate\Database\Query\Builder;
12
13
trait Caching
14
{
15
    protected $isCachable = true;
16
    protected $scopesAreApplied = false;
17
18
    protected function applyScopesToInstance()
19
    {
20
        if (! property_exists($this, "scopes")
21
            || $this->scopesAreApplied
22
        ) {
23
            return;
24
        }
25
26
        foreach ($this->scopes as $identifier => $scope) {
27
            if (! isset($this->scopes[$identifier])) {
28
                continue;
29
            }
30
31
            $this->scopesAreApplied = true;
32
33
            $this->callScope(function () use ($scope) {
0 ignored issues
show
Bug introduced by
It seems like callScope() 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

33
            $this->/** @scrutinizer ignore-call */ 
34
                   callScope(function () use ($scope) {
Loading history...
34
                if ($scope instanceof Closure) {
35
                    $scope($this);
36
                }
37
38
                if ($scope instanceof Scope
39
                    && $this instanceof CachedBuilder
40
                ) {
41
                    $scope->apply($this, $this->getModel());
42
                }
43
            });
44
        }
45
    }
46
47
    public function cache(array $tags = [])
48
    {
49
        $cache = app('cache');
50
51
        if (config('laravel-model-caching.store')) {
52
            $cache = $cache->store(config('laravel-model-caching.store'));
53
        }
54
55
        if (is_subclass_of($cache->getStore(), TaggableStore::class)) {
56
            $cache = $cache->tags($tags);
57
        }
58
59
        return $cache;
60
    }
61
62
    public function disableModelCaching()
63
    {
64
        $this->isCachable = false;
65
66
        return $this;
67
    }
68
69
    public function flushCache(array $tags = [])
70
    {
71
        if (count($tags) === 0) {
72
            $tags = $this->makeCacheTags();
73
        }
74
75
        $this->cache($tags)->flush();
76
77
        [$cacheCooldown] = $this->getModelCacheCooldown($this);
78
79
        if ($cacheCooldown) {
80
            $cachePrefix = $this->getCachePrefix();
81
            $modelClassName = get_class($this);
82
            $cacheKey = "{$cachePrefix}:{$modelClassName}-cooldown:saved-at";
83
84
            $this->cache()
85
                ->rememberForever($cacheKey, function () {
86
                    return (new Carbon)->now();
87
                });
88
        }
89
    }
90
91
    protected function getCachePrefix() : string
92
    {
93
        $cachePrefix = config("laravel-model-caching.cache-prefix", "");
94
95
        if ($this->model
96
            && property_exists($this->model, "cachePrefix")
97
        ) {
98
            $cachePrefix = $this->model->cachePrefix;
99
        }
100
101
        $cachePrefix = $cachePrefix
102
            ? "{$cachePrefix}:"
103
            : "";
104
105
        return "genealabs:laravel-model-caching:"
106
            . $cachePrefix;
107
    }
108
109
    protected function makeCacheKey(
110
        array $columns = ['*'],
111
        $idColumn = null,
112
        string $keyDifferentiator = ''
113
    ) : string {
114
        $this->applyScopesToInstance();
115
        $eagerLoad = $this->eagerLoad ?? [];
116
        $model = $this;
117
118
        if (property_exists($this, "model")) {
119
            $model = $this->model;
120
        }
121
122
        if (method_exists($this, "getModel")) {
123
            $model = $this->getModel();
124
        }
125
126
        $query = $this->query
127
            ?? app('db')->query();
128
        
129
        if ($this->query
130
            && method_exists($this->query, "getQuery")
131
        ) {
132
            $query = $this->query->getQuery();
133
        }
134
135
        return (new CacheKey($eagerLoad, $model, $query))
136
            ->make($columns, $idColumn, $keyDifferentiator);
137
    }
138
139
    protected function makeCacheTags() : array
140
    {
141
        $eagerLoad = $this->eagerLoad ?? [];
142
        $model = $this->getModel() instanceof Model
143
            ? $this->getModel()
144
            : $this;
145
        $query = $this->query instanceof Builder
146
            ? $this->query
147
            : app('db')->query();
148
        $tags = (new CacheTags($eagerLoad, $model, $query))
149
            ->make();
150
151
        return $tags;
152
    }
153
154
    public function getModelCacheCooldown(Model $instance) : array
155
    {
156
        if (! $instance->cacheCooldownSeconds) {
157
            return [null, null, null];
158
        }
159
160
        $cachePrefix = $this->getCachePrefix();
161
        $modelClassName = get_class($instance);
162
        [$cacheCooldown, $invalidatedAt, $savedAt] = $this
163
            ->getCacheCooldownDetails($instance, $cachePrefix, $modelClassName);
164
165
        if (! $cacheCooldown || $cacheCooldown === 0) {
166
            return [null, null, null];
167
        }
168
169
        return [$cacheCooldown, $invalidatedAt, $savedAt];
170
    }
171
172
    protected function getCacheCooldownDetails(
173
        Model $instance,
174
        string $cachePrefix,
175
        string $modelClassName
176
    ) : array {
177
        return [
178
            $instance
179
                ->cache()
180
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:seconds"),
181
            $instance
182
                ->cache()
183
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:invalidated-at"),
184
            $instance
185
                ->cache()
186
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:saved-at"),
187
        ];
188
    }
189
190
    protected function checkCooldownAndRemoveIfExpired(Model $instance)
191
    {
192
        [$cacheCooldown, $invalidatedAt] = $this->getModelCacheCooldown($instance);
193
194
        if (! $cacheCooldown
195
            || (new Carbon)->now()->diffInSeconds($invalidatedAt) < $cacheCooldown
196
        ) {
197
            return;
198
        }
199
200
        $cachePrefix = $this->getCachePrefix();
201
        $modelClassName = get_class($instance);
202
203
        $instance
204
            ->cache()
205
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:seconds");
206
        $instance
207
            ->cache()
208
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:invalidated-at");
209
        $instance
210
            ->cache()
211
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:saved-at");
212
        $instance->flushCache();
213
    }
214
215
    protected function checkCooldownAndFlushAfterPersisting(Model $instance, string $relationship = "")
216
    {
217
        [$cacheCooldown, $invalidatedAt] = $instance->getModelCacheCooldown($instance);
218
219
        if (! $cacheCooldown) {
220
            $instance->flushCache();
221
222
            if ($relationship) {
223
                $relationshipInstance = $instance->$relationship()->getModel();
224
225
                if (method_exists($instance, "flushCache")) {
226
                    $relationshipInstance->flushCache();
227
                }
228
            }
229
230
            return;
231
        }
232
233
        $this->setCacheCooldownSavedAtTimestamp($instance);
234
235
        if ((new Carbon)->now()->diffInSeconds($invalidatedAt) >= $cacheCooldown) {
236
            $instance->flushCache();
237
238
            if ($relationship) {
239
                $instance->$relationship()->getModel()->flushCache();
240
            }
241
        }
242
    }
243
244
    public function isCachable() : bool
245
    {
246
        return $this->isCachable
247
            && ! config('laravel-model-caching.disabled');
248
    }
249
250
    protected function setCacheCooldownSavedAtTimestamp(Model $instance)
251
    {
252
        $cachePrefix = $this->getCachePrefix();
253
        $modelClassName = get_class($instance);
254
        $cacheKey = "{$cachePrefix}:{$modelClassName}-cooldown:saved-at";
255
256
        $instance->cache()
257
            ->rememberForever($cacheKey, function () {
258
                return (new Carbon)->now();
259
            });
260
    }
261
}
262