Passed
Push — master ( ece9a9...712088 )
by Mike
02:50
created

Caching::isCachable()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 22
c 1
b 0
f 0
nc 6
nop 0
dl 0
loc 36
rs 8.0555
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
    protected function applyScopesToInstance()
36
    {
37
        if (! property_exists($this, "scopes")
38
            || $this->scopesAreApplied
39
        ) {
40
            return;
41
        }
42
43
        foreach ($this->scopes as $identifier => $scope) {
44
            if (! isset($this->scopes[$identifier])) {
45
                continue;
46
            }
47
48
            $this->callScope(function () use ($scope) {
1 ignored issue
show
Bug introduced by
The method callScope() does not exist on GeneaLabs\LaravelModelCaching\Traits\Caching. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

48
            $this->/** @scrutinizer ignore-call */ 
49
                   callScope(function () use ($scope) {
Loading history...
49
                if ($scope instanceof Closure) {
50
                    $scope($this);
51
                }
52
53
                if ($scope instanceof Scope
54
                    && $this instanceof CachedBuilder
55
                ) {
56
                    $scope->apply($this, $this->getModel());
57
                }
58
            });
59
        }
60
61
        $this->scopesAreApplied = true;
62
    }
63
64
    public function cache(array $tags = [])
65
    {
66
        $cache = Container::getInstance()
67
            ->make("cache");
68
        $config = Container::getInstance()
69
            ->make("config")
70
            ->get("laravel-model-caching.store");
71
72
        if ($config) {
73
            $cache = $cache->store($config);
74
        }
75
76
        if (is_subclass_of($cache->getStore(), TaggableStore::class)) {
77
            $cache = $cache->tags($tags);
78
        }
79
80
        return $cache;
81
    }
82
83
    public function disableModelCaching()
84
    {
85
        $this->isCachable = false;
86
87
        return $this;
88
    }
89
90
    public function flushCache(array $tags = [])
91
    {
92
        if (count($tags) === 0) {
93
            $tags = $this->makeCacheTags();
94
        }
95
96
        $this->cache($tags)->flush();
97
98
        [$cacheCooldown] = $this->getModelCacheCooldown($this);
99
100
        if ($cacheCooldown) {
101
            $cachePrefix = $this->getCachePrefix();
102
            $modelClassName = get_class($this);
103
            $cacheKey = "{$cachePrefix}:{$modelClassName}-cooldown:saved-at";
104
105
            $this->cache()
106
                ->rememberForever($cacheKey, function () {
107
                    return (new Carbon)->now();
108
                });
109
        }
110
    }
111
112
    protected function getCachePrefix() : string
113
    {
114
        $cachePrefix = Container::getInstance()
115
            ->make("config")
116
            ->get("laravel-model-caching.cache-prefix", "");
117
118
        if ($this->model
119
            && property_exists($this->model, "cachePrefix")
120
        ) {
121
            $cachePrefix = $this->model->cachePrefix;
122
        }
123
124
        $cachePrefix = $cachePrefix
125
            ? "{$cachePrefix}:"
126
            : "";
127
128
        return "genealabs:laravel-model-caching:"
129
            . $cachePrefix;
130
    }
131
132
    protected function makeCacheKey(
133
        array $columns = ['*'],
134
        $idColumn = null,
135
        string $keyDifferentiator = ''
136
    ) : string {
137
        $this->applyScopesToInstance();
138
        $eagerLoad = $this->eagerLoad ?? [];
139
        $model = $this;
140
141
        if (property_exists($this, "model")) {
142
            $model = $this->model;
143
        }
144
145
        if (method_exists($this, "getModel")) {
146
            $model = $this->getModel();
1 ignored issue
show
Bug introduced by
The method getModel() does not exist on GeneaLabs\LaravelModelCaching\Traits\Caching. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

146
            /** @scrutinizer ignore-call */ 
147
            $model = $this->getModel();
Loading history...
147
        }
148
149
        $query = $this->query
150
            ?? Container::getInstance()
151
                ->make("db")
152
                ->query();
153
        
154
        if ($this->query
155
            && method_exists($this->query, "getQuery")
156
        ) {
157
            $query = $this->query->getQuery();
158
        }
159
160
        return (new CacheKey($eagerLoad, $model, $query, $this->macroKey))
161
            ->make($columns, $idColumn, $keyDifferentiator);
162
    }
163
164
    protected function makeCacheTags() : array
165
    {
166
        $eagerLoad = $this->eagerLoad ?? [];
167
        $model = $this->getModel() instanceof Model
168
            ? $this->getModel()
169
            : $this;
170
        $query = $this->query instanceof Builder
171
            ? $this->query
172
            : Container::getInstance()
173
                ->make("db")
174
                ->query();
175
        $tags = (new CacheTags($eagerLoad, $model, $query))
176
            ->make();
177
178
        return $tags;
179
    }
180
181
    public function getModelCacheCooldown(Model $instance) : array
182
    {
183
        if (! $instance->cacheCooldownSeconds) {
184
            return [null, null, null];
185
        }
186
187
        $cachePrefix = $this->getCachePrefix();
188
        $modelClassName = get_class($instance);
189
        [$cacheCooldown, $invalidatedAt, $savedAt] = $this
190
            ->getCacheCooldownDetails($instance, $cachePrefix, $modelClassName);
191
192
        if (! $cacheCooldown || $cacheCooldown === 0) {
193
            return [null, null, null];
194
        }
195
196
        return [$cacheCooldown, $invalidatedAt, $savedAt];
197
    }
198
199
    protected function getCacheCooldownDetails(
200
        Model $instance,
201
        string $cachePrefix,
202
        string $modelClassName
203
    ) : array {
204
        return [
205
            $instance
206
                ->cache()
207
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:seconds"),
208
            $instance
209
                ->cache()
210
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:invalidated-at"),
211
            $instance
212
                ->cache()
213
                ->get("{$cachePrefix}:{$modelClassName}-cooldown:saved-at"),
214
        ];
215
    }
216
217
    protected function checkCooldownAndRemoveIfExpired(Model $instance)
218
    {
219
        [$cacheCooldown, $invalidatedAt] = $this->getModelCacheCooldown($instance);
220
221
        if (! $cacheCooldown
222
            || (new Carbon)->now()->diffInSeconds($invalidatedAt) < $cacheCooldown
223
        ) {
224
            return;
225
        }
226
227
        $cachePrefix = $this->getCachePrefix();
228
        $modelClassName = get_class($instance);
229
230
        $instance
231
            ->cache()
232
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:seconds");
233
        $instance
234
            ->cache()
235
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:invalidated-at");
236
        $instance
237
            ->cache()
238
            ->forget("{$cachePrefix}:{$modelClassName}-cooldown:saved-at");
239
        $instance->flushCache();
240
    }
241
242
    protected function checkCooldownAndFlushAfterPersisting(Model $instance, string $relationship = "")
243
    {
244
        [$cacheCooldown, $invalidatedAt] = $instance->getModelCacheCooldown($instance);
245
246
        if (! $cacheCooldown) {
247
            $instance->flushCache();
248
249
            if ($relationship) {
250
                $relationshipInstance = $instance->$relationship()->getModel();
251
252
                if (method_exists($relationshipInstance, "flushCache")) {
253
                    $relationshipInstance->flushCache();
254
                }
255
            }
256
257
            return;
258
        }
259
260
        $this->setCacheCooldownSavedAtTimestamp($instance);
261
262
        if ((new Carbon)->now()->diffInSeconds($invalidatedAt) >= $cacheCooldown) {
263
            $instance->flushCache();
264
265
            if ($relationship) {
266
                $instance->$relationship()->getModel()->flushCache();
267
            }
268
        }
269
    }
270
271
    public function isCachable() : bool
272
    {
273
        $isCacheDisabled = ! Container::getInstance()
274
            ->make("config")
275
            ->get("laravel-model-caching.enabled");
276
        $allRelationshipsAreCachable = true;
277
278
        if (property_exists($this, "eagerLoad")
279
            && $this->eagerLoad
280
        ) {
281
            $allRelationshipsAreCachable = collect($this
282
                ->eagerLoad)
283
                ->keys()
284
                ->reduce(function ($carry, $related) {
285
                    if (! method_exists($this, $related)
286
                        || $carry === false
287
                    ) {
288
                        return $carry;
289
                    }
290
        
291
                    $relatedModel = $this->model->$related()->getRelated();
292
293
                    if (! method_exists($relatedModel, "isCachable")
294
                        || ! $relatedModel->isCachable()
295
                    ) {
296
                        return false;
297
                    }
298
299
                    return true;
300
                })
301
                ?? true;
302
        }
303
304
        return $this->isCachable
305
            && ! $isCacheDisabled
306
            && $allRelationshipsAreCachable;
307
    }
308
309
    protected function setCacheCooldownSavedAtTimestamp(Model $instance)
310
    {
311
        $cachePrefix = $this->getCachePrefix();
312
        $modelClassName = get_class($instance);
313
        $cacheKey = "{$cachePrefix}:{$modelClassName}-cooldown:saved-at";
314
315
        $instance->cache()
316
            ->rememberForever($cacheKey, function () {
317
                return (new Carbon)->now();
318
            });
319
    }
320
}
321