Test Setup Failed
Branch master (3f89d7)
by Mike
20:24
created

Caching::checkCooldownAndRemoveIfExpired()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
c 0
b 0
f 0
rs 9.536
cc 3
nc 2
nop 1
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])) {
0 ignored issues
show
Bug introduced by
The property localMacros does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
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()
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) {
0 ignored issues
show
Bug introduced by
The property scopes does not seem to exist. Did you mean scopesAreApplied?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
53
            if (! isset($this->scopes[$identifier])) {
0 ignored issues
show
Bug introduced by
The property scopes does not seem to exist. Did you mean scopesAreApplied?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
54
                continue;
55
            }
56
57
            $this->callScope(function () use ($scope) {
0 ignored issues
show
Documentation Bug introduced by
The method callScope does not exist on object<GeneaLabs\Laravel...Caching\Traits\Caching>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
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)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Illuminate\Cache\TaggableStore::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable $cacheCooldown does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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;
0 ignored issues
show
Bug introduced by
The property model does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
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 ?? [];
0 ignored issues
show
Bug introduced by
The property eagerLoad does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
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();
0 ignored issues
show
Bug introduced by
The method getModel() does not exist on GeneaLabs\LaravelModelCaching\Traits\Caching. Did you maybe mean getModelCacheCooldown()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
156
        }
157
158
        $query = $this->query
0 ignored issues
show
Bug introduced by
The property query does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
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
0 ignored issues
show
Bug introduced by
The method getModel() does not exist on GeneaLabs\LaravelModelCaching\Traits\Caching. Did you maybe mean getModelCacheCooldown()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
177
            ? $this->getModel()
0 ignored issues
show
Bug introduced by
The method getModel() does not exist on GeneaLabs\LaravelModelCaching\Traits\Caching. Did you maybe mean getModelCacheCooldown()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
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
0 ignored issues
show
Bug introduced by
The variable $cacheCooldown does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $invalidatedAt does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $savedAt does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable $cacheCooldown does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $invalidatedAt does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable $cacheCooldown does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $invalidatedAt does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
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