Completed
Push — master ( 955b04...f00653 )
by André
38:12 queued 16:07
created

InMemoryCache::setMulti()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 17
nop 3
dl 0
loc 34
rs 8.1315
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File containing MetadataCachePool class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
declare(strict_types=1);
10
11
namespace eZ\Publish\Core\Persistence\Cache\InMemory;
12
13
/**
14
 * Simple In-Memory Cache Pool.
15
 *
16
 * @internal Only for use in eZ\Publish\Core\Persistence\Cache\AbstractInMemoryHandler, may change depending on needs there.
17
 */
18
class InMemoryCache
19
{
20
    /**
21
     * @var float Cache Time to Live, in seconds. This is only for how long we keep cache object around in-memory.
22
     */
23
    private $ttl;
24
25
    /**
26
     * @var int The limit of objects in cache pool at a given time
27
     */
28
    private $limit;
29
30
    /**
31
     * @var bool Switch for enabeling/disabling in-memory cache
32
     */
33
    private $enabled;
34
35
    /**
36
     * Cache objects by primary key.
37
     *
38
     * @var object[]
39
     */
40
    private $cache = [];
41
42
    /**
43
     * @var float[] Timestamp (float microtime) for individual cache by primary key.
44
     */
45
    private $cacheTime = [];
46
47
    /**
48
     * Mapping of secondary index to primary key.
49
     *
50
     * @var string[]
51
     */
52
    private $cacheIndex = [];
53
54
    /**
55
     * In Memory Cache constructor.
56
     *
57
     * @param int $ttl Seconds for the cache to live, by default 300 milliseconds
58
     * @param int $limit Limit for values to keep in cache, by default 100 cache values (per pool instance).
59
     * @param bool $enabled For use by configuration to be able to disable or enable depending on needs.
60
     */
61
    public function __construct(int $ttl = 300, int $limit = 100, bool $enabled = true)
62
    {
63
        $this->ttl = $ttl / 1000;
0 ignored issues
show
Documentation Bug introduced by
The property $ttl was declared of type double, but $ttl / 1000 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
64
        $this->limit = $limit;
65
        $this->enabled = $enabled;
66
    }
67
68
    /**
69
     * @param bool $enabled
70
     *
71
     * @return bool Prior value
72
     */
73
    public function setEnabled(bool $enabled = true): bool
74
    {
75
        $was = $this->enabled;
76
        $this->enabled = $enabled;
77
78
        return $was;
79
    }
80
81
    /**
82
     * Returns a cache objects.
83
     *
84
     * @param string $key Primary or secondary index to look for cache on.
85
     *
86
     * @return object|null Object if found, null if not.
87
     */
88
    public function get(string $key)
89
    {
90
        if ($this->enabled === false) {
91
            return null;
92
        }
93
94
        $index = $this->cacheIndex[$key] ?? $key;
95
        if (!isset($this->cache[$index]) || $this->cacheTime[$index] + $this->ttl < microtime(true)) {
96
            return null;
97
        }
98
99
        return $this->cache[$index];
100
    }
101
102
    /**
103
     * Set object in in-memory cache.
104
     *
105
     * Should only set Cache hits here!
106
     *
107
     * @param object[] $objects
108
     * @param callable $objectIndexes Return array of indexes per object (first argument), must return at least 1 primary index
109
     * @param string|null $listIndex Optional index for list of items
110
     */
111
    public function setMulti(array $objects, callable $objectIndexes, string $listIndex = null): void
112
    {
113
        // If objects accounts for more then 20% of our limit, assume it's bulk content load and skip saving in-memory
114
        if ($this->enabled === false || \count($objects) >= $this->limit / 5) {
115
            return;
116
        }
117
118
        // check if we will reach limit by adding these objects, if so remove old cache
119
        if (\count($this->cache) + \count($objects) >= $this->limit) {
120
            $this->vacuum();
121
        }
122
123
        $time = microtime(true);
124
        // if set add objects to cache on list index (typically a "all" key)
125
        if ($listIndex) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $listIndex of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
126
            $this->cache[$listIndex] = $objects;
127
            $this->cacheTime[$listIndex] = $time;
128
        }
129
130
        foreach ($objects as $object) {
131
            // Skip if there are no indexes
132
            if (!$indexes = $objectIndexes($object)) {
133
                continue;
134
            }
135
136
            $key = \array_shift($indexes);
137
            $this->cache[$key] = $object;
138
            $this->cacheTime[$key] = $time;
139
140
            foreach ($indexes as $index) {
141
                $this->cacheIndex[$index] = $key;
142
            }
143
        }
144
    }
145
146
    /**
147
     * Removes multiple in-memory cache from the pool.
148
     *
149
     * @param string[] $keys An array of keys that should be removed from the pool.
150
     */
151
    public function deleteMulti(array $keys): void
152
    {
153
        if ($this->enabled === false) {
154
            return;
155
        }
156
157
        foreach ($keys as $key) {
158
            if ($index = $this->cacheIndex[$key] ?? null) {
159
                unset($this->cacheIndex[$key], $this->cache[$index], $this->cacheTime[$index]);
160
            } else {
161
                unset($this->cache[$key], $this->cacheTime[$key]);
162
            }
163
        }
164
    }
165
166
    /**
167
     * Deletes all cache in the in-memory pool.
168
     */
169
    public function clear(): void
170
    {
171
        // On purpose does not check if enabled, in case of several instances we allow clearing cache
172
        $this->cache = $this->cacheIndex = $this->cacheTime = [];
173
    }
174
175
    /**
176
     * Call to reduce cache items when $limit has been reached.
177
     *
178
     * Deletes expired first, then oldest(or least used?).
179
     */
180
    private function vacuum(): void
181
    {
182
        // Vacuuming cache in bulk, clearing the 33% oldest cache values
183
        $this->cache = \array_slice($this->cache, (int) ($this->limit / 3));
184
185
        // Cleanup secondary index and cache time for missing primary keys
186
        foreach ($this->cacheIndex as $index => $key) {
187
            if (!isset($this->cache[$key])) {
188
                unset($this->cacheIndex[$index], $this->cacheTime[$key]);
189
            }
190
        }
191
    }
192
}
193