Issues (3)

src/Memcached.php (1 issue)

Labels
Severity
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Cache\Memcached;
6
7
use DateInterval;
8
use DateTime;
9
use Psr\SimpleCache\CacheInterface;
0 ignored issues
show
The type Psr\SimpleCache\CacheInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Traversable;
11
12
use function array_fill_keys;
13
use function array_key_exists;
14
use function array_keys;
15
use function array_map;
16
use function gettype;
17
use function is_array;
18
use function is_iterable;
19
use function is_string;
20
use function iterator_to_array;
21
use function strpbrk;
22
use function time;
23
24
/**
25
 * Memcached implements a cache application component based on
26
 * [memcached](http://pecl.php.net/package/memcached) PECL extension.
27
 *
28
 * Memcached can be configured with a list of memcached servers passed to the constructor.
29
 * By default, Memcached assumes there is a memcached server running on localhost at port 11211.
30
 *
31
 * See {@see \Psr\SimpleCache\CacheInterface} for common cache operations that MemCached supports.
32
 *
33
 * Note, there is no security measure to protected data in memcached.
34
 * All data in memcached can be accessed by any process running in the system.
35
 */
36
final class Memcached implements CacheInterface
37
{
38
    public const DEFAULT_SERVER_HOST = '127.0.0.1';
39
    public const DEFAULT_SERVER_PORT = 11211;
40
    public const DEFAULT_SERVER_WEIGHT = 1;
41
42
    private const TTL_INFINITY = 0;
43
    private const TTL_EXPIRED = -1;
44
45
    /**
46
     * @var \Memcached The Memcached instance.
47
     */
48
    private \Memcached $cache;
49
50
    /**
51
     * @param string $persistentId The ID that identifies the Memcached instance.
52
     * By default, the Memcached instances are destroyed at the end of the request.
53
     * To create an instance that persists between requests, use `persistentId` to specify a unique ID for the instance.
54
     * All instances created with the same persistent_id will share the same connection.
55
     * @param array $servers List of memcached servers that will be added to the server pool.
56
     *
57
     * @see https://www.php.net/manual/en/memcached.construct.php
58
     * @see https://www.php.net/manual/en/memcached.addservers.php
59
     */
60 176
    public function __construct(string $persistentId = '', array $servers = [])
61
    {
62 176
        $this->cache = new \Memcached($persistentId);
63 176
        $this->initServers($servers, $persistentId);
64 170
    }
65
66 72
    public function get($key, $default = null)
67
    {
68 72
        $this->validateKey($key);
69 64
        $value = $this->cache->get($key);
70
71 64
        if ($this->cache->getResultCode() === \Memcached::RES_SUCCESS) {
72 51
            return $value;
73
        }
74
75 25
        return $default;
76
    }
77
78 83
    public function set($key, $value, $ttl = null): bool
79
    {
80 83
        $this->validateKey($key);
81 75
        $ttl = $this->normalizeTtl($ttl);
82
83 75
        if ($ttl <= self::TTL_EXPIRED) {
84 1
            return $this->delete($key);
85
        }
86
87 74
        return $this->cache->set($key, $value, $ttl);
88
    }
89
90 21
    public function delete($key): bool
91
    {
92 21
        $this->validateKey($key);
93 13
        return $this->cache->delete($key);
94
    }
95
96 12
    public function clear(): bool
97
    {
98 12
        return $this->cache->flush();
99
    }
100
101 23
    public function getMultiple($keys, $default = null): iterable
102
    {
103 23
        $keys = $this->iterableToArray($keys);
104 15
        $this->validateKeys($keys);
105 7
        $values = array_fill_keys($keys, $default);
106 7
        $valuesFromCache = $this->cache->getMulti($keys);
107
108 7
        foreach ($values as $key => $value) {
109 7
            $values[$key] = $valuesFromCache[$key] ?? $value;
110
        }
111
112 7
        return $values;
113
    }
114
115 18
    public function setMultiple($values, $ttl = null): bool
116
    {
117 18
        $values = $this->iterableToArray($values);
118 10
        $this->validateKeysOfValues($values);
119 10
        return $this->cache->setMulti($values, $this->normalizeTtl($ttl));
120
    }
121
122 17
    public function deleteMultiple($keys): bool
123
    {
124 17
        $keys = $this->iterableToArray($keys);
125 9
        $this->validateKeys($keys);
126
127 1
        foreach ($this->cache->deleteMulti($keys) as $result) {
128 1
            if ($result === false) {
129
                return false;
130
            }
131
        }
132
133 1
        return true;
134
    }
135
136 21
    public function has($key): bool
137
    {
138 21
        $this->validateKey($key);
139 13
        $this->cache->get($key);
140 13
        return $this->cache->getResultCode() === \Memcached::RES_SUCCESS;
141
    }
142
143
    /**
144
     * Normalizes cache TTL handling `null` value, strings and {@see DateInterval} objects.
145
     *
146
     * @param DateInterval|int|string|null $ttl The raw TTL.
147
     *
148
     * @return int TTL value as UNIX timestamp.
149
     *
150
     * @see https://secure.php.net/manual/en/memcached.expiration.php
151
     */
152 92
    private function normalizeTtl($ttl): int
153
    {
154 92
        if ($ttl === null) {
155 82
            return self::TTL_INFINITY;
156
        }
157
158 12
        if ($ttl instanceof DateInterval) {
159 3
            $ttl = (new DateTime('@0'))->add($ttl)->getTimestamp();
160
        }
161
162 12
        $ttl = (int) $ttl;
163
164 12
        if ($ttl > 2592000) {
165 1
            return $ttl + time();
166
        }
167
168 11
        return $ttl > 0 ? $ttl : self::TTL_EXPIRED;
169
    }
170
171
    /**
172
     * Converts iterable to array. If provided value is not iterable it throws an InvalidArgumentException.
173
     *
174
     * @param mixed $iterable
175
     *
176
     * @return array
177
     */
178 50
    private function iterableToArray($iterable): array
179
    {
180 50
        if (!is_iterable($iterable)) {
181 24
            throw new InvalidArgumentException('Iterable is expected, got ' . gettype($iterable));
182
        }
183
184
        /** @psalm-suppress RedundantCast */
185 26
        return $iterable instanceof Traversable ? iterator_to_array($iterable) : (array) $iterable;
186
    }
187
188
    /**
189
     * @param array $servers
190
     * @param string $persistentId
191
     *
192
     * @throws CacheException If an error occurred when adding servers to the server pool.
193
     * @throws InvalidArgumentException If the servers format is incorrect.
194
     */
195 176
    private function initServers(array $servers, string $persistentId): void
196
    {
197 176
        $servers = $this->normalizeServers($servers);
198
199 170
        if ($persistentId !== '') {
200 1
            $servers = $this->getNewServers($servers);
201
        }
202
203 170
        if (!$this->cache->addServers($servers)) {
204 1
            throw new CacheException('An error occurred while adding servers to the server pool.');
205
        }
206 170
    }
207
208
    /**
209
     * Returns the list of the servers that are not in the pool.
210
     *
211
     * @param array $servers
212
     *
213
     * @return array
214
     */
215 2
    private function getNewServers(array $servers): array
216
    {
217 2
        $existingServers = [];
218 2
        $newServers = [];
219
220 2
        foreach ($this->cache->getServerList() as $existingServer) {
221 1
            $existingServers["{$existingServer['host']}:{$existingServer['port']}"] = true;
222
        }
223
224 2
        foreach ($servers as $server) {
225 2
            if (!array_key_exists("{$server[0]}:{$server[1]}", $existingServers)) {
226 2
                $newServers[] = $server;
227
            }
228
        }
229
230 2
        return $newServers;
231
    }
232
233
    /**
234
     * Validates and normalizes the format of the servers.
235
     *
236
     * @param array $servers The raw servers.
237
     *
238
     * @throws InvalidArgumentException If the servers format is incorrect.
239
     *
240
     * @return array The normalized servers.
241
     */
242 176
    private function normalizeServers(array $servers): array
243
    {
244 176
        $normalized = [];
245
246 176
        foreach ($servers as $server) {
247 175
            if (!is_array($server) || !isset($server['host'], $server['port'])) {
248 6
                throw new InvalidArgumentException(
249
                    'Each entry in servers parameter is supposed to be an array'
250 6
                    . ' containing hostname, port, and, optionally, weight of the server.',
251
                );
252
            }
253
254 169
            $normalized[] = [$server['host'], $server['port'], $server['weight'] ?? self::DEFAULT_SERVER_WEIGHT];
255
        }
256
257 170
        return $normalized ?: [[self::DEFAULT_SERVER_HOST, self::DEFAULT_SERVER_PORT, self::DEFAULT_SERVER_WEIGHT]];
258
    }
259
260
    /**
261
     * @param mixed $key
262
     */
263 132
    private function validateKey($key): void
264
    {
265 132
        if (!is_string($key) || $key === '' || strpbrk($key, '{}()/\@:')) {
266 48
            throw new InvalidArgumentException('Invalid key value.');
267
        }
268 84
    }
269
270
    /**
271
     * @param array $keys
272
     */
273 26
    private function validateKeys(array $keys): void
274
    {
275 26
        foreach ($keys as $key) {
276 26
            $this->validateKey($key);
277
        }
278 10
    }
279
280
    /**
281
     * @param array $values
282
     */
283 10
    private function validateKeysOfValues(array $values): void
284
    {
285 10
        $keys = array_map('\strval', array_keys($values));
286 10
        $this->validateKeys($keys);
287 10
    }
288
}
289