Memcached::init()   F
last analyzed

Complexity

Conditions 22
Paths 1033

Size

Total Lines 98
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 22
eloc 55
c 1
b 0
f 0
nc 1033
nop 1
dl 0
loc 98
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\Cache\Handlers;
13
14
use DateInterval;
15
use InvalidArgumentException;
16
use Memcached as BaseMemcached;
17
use RuntimeException;
18
19
/**
20
 * Moteur de stockage Memcached pour le cache. Memcached a certaines limitations dans la quantité de
21
 * le contrôle que vous avez sur les délais d'expiration lointains dans le futur. Voir MemcachedEngine::write() pour
22
 * Plus d'information.
23
 *
24
 * Le moteur Memcached prend en charge le protocole binaire et igbinary
25
 * sérialisation (si l'extension memcached est compilée avec --enable-igbinary).
26
 * Les touches compressées peuvent également être incrémentées/décrémentées.
27
 */
28
class Memcached extends BaseHandler
29
{
30
    /**
31
     * Wrapper Memcached.
32
     *
33
     * @var BaseMemcached
34
     */
35
    protected $_Memcached;
36
37
    /**
38
     * La configuration par défaut utilisée sauf si elle est remplacée par la configuration d'exécution
39
     *
40
     * - `compress` Indique s'il faut compresser les données
41
     * - `duration` Spécifiez combien de temps durent les éléments de cette configuration de cache.
42
     * - `groups` Liste des groupes ou 'tags' associés à chaque clé stockée dans cette configuration.
43
     * pratique pour supprimer un groupe complet du cache.
44
     * - `nom d'utilisateur` Connectez-vous pour accéder au serveur Memcache
45
     * - `password` Mot de passe pour accéder au serveur Memcache
46
     * - `persistent` Le nom de la connexion persistante. Toutes les configurations utilisant
47
     * la même valeur persistante partagera une seule connexion sous-jacente.
48
     * - `prefix` Préfixé à toutes les entrées. Bon pour quand vous avez besoin de partager un keyspace
49
     * avec une autre configuration de cache ou une autre application.
50
     * - `serialize` Le moteur de sérialisation utilisé pour sérialiser les données. Les moteurs disponibles sont 'php',
51
     * 'igbinaire' et 'json'. A côté de 'php', l'extension memcached doit être compilée avec le
52
     * Prise en charge appropriée du sérialiseur.
53
     * - `servers` Chaîne ou tableau de serveurs memcached. Si un tableau MemcacheEngine utilisera
54
     * eux comme une piscine.
55
     * - `options` - Options supplémentaires pour le client memcached. Doit être un tableau d'option => valeur.
56
     * Utilisez les constantes \Memcached::OPT_* comme clés.
57
     */
58
    protected array $_defaultConfig = [
59
        'compress'   => false,
60
        'duration'   => 3600,
61
        'groups'     => [],
62
        'host'       => null,
63
        'username'   => null,
64
        'password'   => null,
65
        'persistent' => null,
66
        'port'       => null,
67
        'prefix'     => 'blitz_',
68
        'serialize'  => 'php',
69
        'servers'    => ['127.0.0.1'],
70
        'options'    => [],
71
    ];
72
73
    /**
74
     * Liste des moteurs de sérialisation disponibles
75
     *
76
     * Memcached doit être compilé avec JSON et le support igbinary pour utiliser ces moteurs
77
     */
78
    protected array $_serializers = [];
79
80
    /**
81
     * @var string[]
82
     */
83
    protected array $_compiledGroupNames = [];
84
85
    /**
86
     * {@inheritDoc}
87
     */
88
    public function init(array $config = []): bool
89
    {
90
        if (! extension_loaded('memcached')) {
91
            throw new RuntimeException('L\'extension `memcached` doit être activée pour utiliser MemcachedHandler.');
92
        }
93
94
        $this->_serializers = [
95
            'igbinary' => BaseMemcached::SERIALIZER_IGBINARY,
96
            'json'     => BaseMemcached::SERIALIZER_JSON,
97
            'php'      => BaseMemcached::SERIALIZER_PHP,
98
        ];
99
        if (defined('Memcached::HAVE_MSGPACK')) {
100
            $this->_serializers['msgpack'] = BaseMemcached::SERIALIZER_MSGPACK;
101
        }
102
103
        parent::init($config);
104
105
        if (! empty($config['host'])) {
106
            if (empty($config['port'])) {
107
                $config['servers'] = [$config['host']];
108
            } else {
109
                $config['servers'] = [sprintf('%s:%d', $config['host'], $config['port'])];
110
            }
111
        }
112
113
        if (isset($config['servers'])) {
114
            $this->setConfig('servers', $config['servers'], false);
115
        }
116
117
        if (! is_array($this->_config['servers'])) {
118
            $this->_config['servers'] = [$this->_config['servers']];
119
        }
120
121
        /** @psalm-suppress RedundantPropertyInitializationCheck */
122
        if (isset($this->_Memcached)) {
123
            return true;
124
        }
125
126
        if ($this->_config['persistent']) {
127
            $this->_Memcached = new BaseMemcached($this->_config['persistent']);
128
        } else {
129
            $this->_Memcached = new BaseMemcached();
130
        }
131
        $this->_setOptions();
132
133
        $serverList = $this->_Memcached->getServerList();
134
        if ($serverList) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $serverList of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
135
            if ($this->_Memcached->isPersistent()) {
136
                foreach ($serverList as $server) {
137
                    if (! in_array($server['host'] . ':' . $server['port'], $this->_config['servers'], true)) {
138
                        throw new InvalidArgumentException(
139
                            'Configuration du cache invalide. Plusieurs configurations de cache persistant sont détectées' .
140
                            ' avec des valeurs `servers` différentes. `valeurs` des serveurs pour les configurations de cache persistant' .
141
                            ' doit être le même lors de l\'utilisation du même identifiant de persistance.'
142
                        );
143
                    }
144
                }
145
            }
146
147
            return true;
148
        }
149
150
        $servers = [];
151
152
        foreach ($this->_config['servers'] as $server) {
153
            $servers[] = $this->parseServerString($server);
154
        }
155
156
        if (! $this->_Memcached->addServers($servers)) {
157
            return false;
158
        }
159
160
        if (is_array($this->_config['options'])) {
161
            foreach ($this->_config['options'] as $opt => $value) {
162
                $this->_Memcached->setOption($opt, $value);
163
            }
164
        }
165
166
        if (empty($this->_config['username']) && ! empty($this->_config['login'])) {
167
            throw new InvalidArgumentException(
168
                'Veuillez passer "nom d\'utilisateur" au lieu de "login" pour vous connecter à Memcached'
169
            );
170
        }
171
172
        if ($this->_config['username'] !== null && $this->_config['password'] !== null) {
173
            if (! method_exists($this->_Memcached, 'setSaslAuthData')) {
174
                throw new InvalidArgumentException(
175
                    "L'extension Memcached n'est pas construite avec le support SASL"
176
                );
177
            }
178
            $this->_Memcached->setOption(BaseMemcached::OPT_BINARY_PROTOCOL, true);
179
            $this->_Memcached->setSaslAuthData(
180
                $this->_config['username'],
181
                $this->_config['password']
182
            );
183
        }
184
185
        return true;
186
    }
187
188
    /**
189
     * Paramétrage de l'instance memcached
190
     *
191
     * @throws InvalidArgumentException Lorsque l'extension Memcached n'est pas construite avec le moteur de sérialisation souhaité.
192
     */
193
    protected function _setOptions(): void
194
    {
195
        $this->_Memcached->setOption(BaseMemcached::OPT_LIBKETAMA_COMPATIBLE, true);
196
197
        $serializer = strtolower($this->_config['serialize']);
198
        if (! isset($this->_serializers[$serializer])) {
199
            throw new InvalidArgumentException(
200
                sprintf('%s n\'est pas un moteur de sérialisation valide pour Memcached', $serializer)
201
            );
202
        }
203
204
        if (
205
            $serializer !== 'php'
206
            && ! constant('Memcached::HAVE_' . strtoupper($serializer))
207
        ) {
208
            throw new InvalidArgumentException(
209
                sprintf('L\'extension Memcached n\'est pas compilée avec la prise en charge de %s', $serializer)
210
            );
211
        }
212
213
        $this->_Memcached->setOption(
214
            BaseMemcached::OPT_SERIALIZER,
215
            $this->_serializers[$serializer]
216
        );
217
218
        // Check for Amazon ElastiCache instance
219
        if (
220
            defined('Memcached::OPT_CLIENT_MODE')
221
            && defined('Memcached::DYNAMIC_CLIENT_MODE')
222
        ) {
223
            $this->_Memcached->setOption(
224
                BaseMemcached::OPT_CLIENT_MODE,
0 ignored issues
show
Bug introduced by
The constant Memcached::OPT_CLIENT_MODE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
225
                BaseMemcached::DYNAMIC_CLIENT_MODE
0 ignored issues
show
Bug introduced by
The constant Memcached::DYNAMIC_CLIENT_MODE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
226
            );
227
        }
228
229
        $this->_Memcached->setOption(
230
            BaseMemcached::OPT_COMPRESSION,
231
            (bool) $this->_config['compress']
232
        );
233
    }
234
235
    /**
236
     * Analyse l'adresse du serveur dans l'hôte/port. Gère à la fois les adresses IPv6 et IPv4 et sockets Unix
237
     *
238
     * @param string $server La chaîne d'adresse du serveur.
239
     *
240
     * @return array Tableau contenant l'hôte, le port
241
     */
242
    public function parseServerString(string $server): array
243
    {
244
        $socketTransport = 'unix://';
245
        if (str_starts_with($server, $socketTransport)) {
246
            return [substr($server, strlen($socketTransport)), 0];
247
        }
248
        if (substr($server, 0, 1) === '[') {
249
            $position = strpos($server, ']:');
250
            if ($position !== false) {
251
                $position++;
252
            }
253
        } else {
254
            $position = strpos($server, ':');
255
        }
256
        $port = 11211;
257
        $host = $server;
258
        if ($position !== false) {
259
            $host = substr($server, 0, $position);
260
            $port = substr($server, $position + 1);
261
        }
262
263
        return [$host, (int) $port];
264
    }
265
266
    /**
267
     * Lire une valeur d'option à partir de la connexion memcached.
268
     *
269
     * @return bool|int|string|null
270
     *
271
     * @see https://secure.php.net/manual/en/memcached.getoption.php
272
     */
273
    public function getOption(int $name)
274
    {
275
        return $this->_Memcached->getOption($name);
276
    }
277
278
    /**
279
     * {@inheritDoc}
280
     *
281
     * @see https://www.php.net/manual/en/memcached.set.php
282
     */
283
    public function set(string $key, mixed $value, null|DateInterval|int $ttl = null): bool
284
    {
285
        $duration = $this->duration($ttl);
286
287
        return $this->_Memcached->set($this->_key($key), $value, $duration);
288
    }
289
290
    /**
291
     * {@inheritDoc}
292
     */
293
    public function setMultiple(iterable $values, null|DateInterval|int $ttl = null): bool
294
    {
295
        $cacheData = [];
296
297
        foreach ($values as $key => $value) {
298
            $cacheData[$this->_key($key)] = $value;
299
        }
300
        $duration = $this->duration($ttl);
301
302
        return $this->_Memcached->setMulti($cacheData, $duration);
303
    }
304
305
    /**
306
     * {@inheritDoc}
307
     */
308
    public function get(string $key, mixed $default = null): mixed
309
    {
310
        $key   = $this->_key($key);
311
        $value = $this->_Memcached->get($key);
312
        if ($this->_Memcached->getResultCode() === BaseMemcached::RES_NOTFOUND) {
313
            return $default;
314
        }
315
316
        return $value;
317
    }
318
319
    /**
320
     * {@inheritDoc}
321
     *
322
     * @return array Un tableau contenant, pour chacune des $keys données, les données mises en cache ou false si les données mises en cache n'ont pas pu être récupérées.
323
     */
324
    public function getMultiple(iterable $keys, mixed $default = null): iterable
325
    {
326
        $cacheKeys = [];
327
328
        foreach ($keys as $key) {
329
            $cacheKeys[$key] = $this->_key($key);
330
        }
331
332
        $values = $this->_Memcached->getMulti($cacheKeys);
333
        $return = [];
334
335
        foreach ($cacheKeys as $original => $prefixed) {
336
            $return[$original] = $values[$prefixed] ?? $default;
337
        }
338
339
        return $return;
340
    }
341
342
    /**
343
     * {@inheritDoc}
344
     */
345
    public function increment(string $key, int $offset = 1)
346
    {
347
        return $this->_Memcached->increment($this->_key($key), $offset);
348
    }
349
350
    /**
351
     * {@inheritDoc}
352
     */
353
    public function decrement(string $key, int $offset = 1)
354
    {
355
        return $this->_Memcached->decrement($this->_key($key), $offset);
356
    }
357
358
    /**
359
     * {@inheritDoc}
360
     */
361
    public function delete(string $key): bool
362
    {
363
        return $this->_Memcached->delete($this->_key($key));
364
    }
365
366
    /**
367
     * {@inheritDoc}
368
     */
369
    public function deleteMultiple(iterable $keys): bool
370
    {
371
        $cacheKeys = [];
372
373
        foreach ($keys as $key) {
374
            $cacheKeys[] = $this->_key($key);
375
        }
376
377
        return (bool) $this->_Memcached->deleteMulti($cacheKeys);
378
    }
379
380
    /**
381
     * {@inheritDoc}
382
     */
383
    public function clear(): bool
384
    {
385
        $keys = $this->_Memcached->getAllKeys();
386
        if ($keys === false) {
387
            return false;
388
        }
389
390
        foreach ($keys as $key) {
391
            if (str_starts_with($key, $this->_config['prefix'])) {
392
                $this->_Memcached->delete($key);
393
            }
394
        }
395
396
        return true;
397
    }
398
399
    /**
400
     * {@inheritDoc}
401
     */
402
    public function add(string $key, mixed $value): bool
403
    {
404
        $duration = $this->_config['duration'];
405
        $key      = $this->_key($key);
406
407
        return $this->_Memcached->add($key, $value, $duration);
408
    }
409
410
    /**
411
     * {@inheritDoc}
412
     */
413
    public function info()
414
    {
415
        return $this->_Memcached->getStats();
416
    }
417
418
    /**
419
     * {@inheritDoc}
420
     */
421
    public function groups(): array
422
    {
423
        if (empty($this->_compiledGroupNames)) {
424
            foreach ($this->_config['groups'] as $group) {
425
                $this->_compiledGroupNames[] = $this->_config['prefix'] . $group;
426
            }
427
        }
428
429
        $groups = $this->_Memcached->getMulti($this->_compiledGroupNames) ?: [];
430
        if (count($groups) !== count($this->_config['groups'])) {
431
            foreach ($this->_compiledGroupNames as $group) {
432
                if (! isset($groups[$group])) {
433
                    $this->_Memcached->set($group, 1, 0);
434
                    $groups[$group] = 1;
435
                }
436
            }
437
            ksort($groups);
438
        }
439
440
        $result = [];
441
        $groups = array_values($groups);
442
443
        foreach ($this->_config['groups'] as $i => $group) {
444
            $result[] = $group . $groups[$i];
445
        }
446
447
        return $result;
448
    }
449
450
    /**
451
     * {@inheritDoc}
452
     */
453
    public function clearGroup(string $group): bool
454
    {
455
        return (bool) $this->_Memcached->increment($this->_config['prefix'] . $group);
456
    }
457
}
458