Passed
Push — main ( e7b3b2...eecacd )
by Dimitri
02:12
created

RedisHandler::_connect()   B

Complexity

Conditions 10
Paths 30

Size

Total Lines 39
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 27
c 1
b 0
f 0
nc 30
nop 0
dl 0
loc 39
rs 7.6666

How to fix   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 Redis;
16
use RedisException;
17
use RuntimeException;
18
19
/**
20
 * Moteur de stockage Redis pour le cache.
21
 */
22
class RedisHandler extends BaseHandler
23
{
24
    /**
25
     * Wrapper Redis.
26
     *
27
     * @var Redis
28
     */
29
    protected $_Redis;
30
31
    /**
32
     * La configuration par défaut utilisée sauf si elle est remplacée par la configuration d'exécution
33
     *
34
     * - Numéro de base de données `database` à utiliser pour la connexion.
35
     * - `duration` Spécifiez combien de temps durent les éléments de cette configuration de cache.
36
     * - `groups` Liste des groupes ou 'tags' associés à chaque clé stockée dans cette configuration.
37
     * pratique pour supprimer un groupe complet du cache.
38
     * - `password` Mot de passe du serveur Redis.
39
     * - `persistent` Connectez-vous au serveur Redis avec une connexion persistante
40
     * - `port` numéro de port vers le serveur Redis.
41
     * - `prefix` Préfixe ajouté à toutes les entrées. Bon pour quand vous avez besoin de partager un keyspace
42
     * avec une autre configuration de cache ou une autre application.
43
     * - URL ou IP `server` vers l'hôte du serveur Redis.
44
     * - Délai d'expiration de `timeout` en secondes (flottant).
45
     * - `unix_socket` Chemin vers le fichier socket unix (par défaut : false)
46
     */
47
    protected array $_defaultConfig = [
48
        'database'    => 0,
49
        'duration'    => 3600,
50
        'groups'      => [],
51
        'password'    => false,
52
        'persistent'  => true,
53
        'port'        => 6379,
54
        'prefix'      => 'blitz_',
55
        'host'        => null,
56
        'server'      => '127.0.0.1',
57
        'timeout'     => 0,
58
        'unix_socket' => false,
59
    ];
60
61
    /**
62
     * {@inheritDoc}
63
     */
64
    public function init(array $config = []): bool
65
    {
66
        if (! extension_loaded('redis')) {
67
            throw new RuntimeException('L\'extension `redis` doit être activée pour utiliser RedisHandler.');
68
        }
69
70
        if (! empty($config['host'])) {
71
            $config['server'] = $config['host'];
72
        }
73
74
        parent::init($config);
75
76
        return $this->_connect();
77
    }
78
79
    /**
80
     * Connection au serveur Redis
81
     *
82
     * @return bool Vrai si le serveur Redis était connecté
83
     */
84
    protected function _connect(): bool
85
    {
86
        try {
87
            $this->_Redis = new Redis();
88
            if (! empty($this->_config['unix_socket'])) {
89
                $return = $this->_Redis->connect($this->_config['unix_socket']);
90
            } elseif (empty($this->_config['persistent'])) {
91
                $return = $this->_Redis->connect(
92
                    $this->_config['server'],
93
                    (int) $this->_config['port'],
94
                    (int) $this->_config['timeout']
95
                );
96
            } else {
97
                $persistentId = $this->_config['port'] . $this->_config['timeout'] . $this->_config['database'];
98
                $return       = $this->_Redis->pconnect(
99
                    $this->_config['server'],
100
                    (int) $this->_config['port'],
101
                    (int) $this->_config['timeout'],
102
                    $persistentId
103
                );
104
            }
105
        } catch (RedisException $e) {
106
            if (function_exists('logger')) {
107
                $logger = logger();
108
                if (is_object($logger) && method_exists($logger, 'error')) {
109
                    $logger->error('RedisEngine n\'a pas pu se connecter. Erreur: ' . $e->getMessage());
110
                }
111
            }
112
113
            return false;
114
        }
115
        if ($return && $this->_config['password']) {
116
            $return = $this->_Redis->auth($this->_config['password']);
117
        }
118
        if ($return) {
119
            $return = $this->_Redis->select((int) $this->_config['database']);
120
        }
121
122
        return $return;
123
    }
124
125
    /**
126
     * {@inheritDoc}
127
     */
128
    public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool
129
    {
130
        $key   = $this->_key($key);
131
        $value = $this->serialize($value);
132
133
        $duration = $this->duration($ttl);
134
        if ($duration === 0) {
135
            return $this->_Redis->set($key, $value);
136
        }
137
138
        return $this->_Redis->setEx($key, $duration, $value);
139
    }
140
141
    /**
142
     * {@inheritDoc}
143
     */
144
    public function get(string $key, mixed $default = null): mixed
145
    {
146
        $value = $this->_Redis->get($this->_key($key));
147
        if ($value === false) {
148
            return $default;
149
        }
150
151
        return $this->unserialize($value);
152
    }
153
154
    /**
155
     * {@inheritDoc}
156
     */
157
    public function increment(string $key, int $offset = 1)
158
    {
159
        $duration = $this->_config['duration'];
160
        $key      = $this->_key($key);
161
162
        $value = $this->_Redis->incrBy($key, $offset);
163
        if ($duration > 0) {
164
            $this->_Redis->expire($key, $duration);
165
        }
166
167
        return $value;
168
    }
169
170
    /**
171
     * {@inheritDoc}
172
     */
173
    public function decrement(string $key, int $offset = 1)
174
    {
175
        $duration = $this->_config['duration'];
176
        $key      = $this->_key($key);
177
178
        $value = $this->_Redis->decrBy($key, $offset);
179
        if ($duration > 0) {
180
            $this->_Redis->expire($key, $duration);
181
        }
182
183
        return $value;
184
    }
185
186
    /**
187
     * {@inheritDoc}
188
     */
189
    public function delete(string $key): bool
190
    {
191
        $key = $this->_key($key);
192
193
        return $this->_Redis->del($key) > 0;
194
    }
195
196
    /**
197
     * {@inheritDoc}
198
     */
199
    public function clear(): bool
200
    {
201
        $this->_Redis->setOption(Redis::OPT_SCAN, (string) Redis::SCAN_RETRY);
202
203
        $isAllDeleted = true;
204
        $iterator     = null;
205
        $pattern      = $this->_config['prefix'] . '*';
206
207
        while (true) {
208
            $keys = $this->_Redis->scan($iterator, $pattern);
209
210
            if ($keys === false) {
211
                break;
212
            }
213
214
            foreach ($keys as $key) {
215
                $isDeleted    = ($this->_Redis->del($key) > 0);
216
                $isAllDeleted = $isAllDeleted && $isDeleted;
217
            }
218
        }
219
220
        return $isAllDeleted;
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     *
226
     * @see https://github.com/phpredis/phpredis#set
227
     */
228
    public function add(string $key, mixed $value): bool
229
    {
230
        $duration = $this->_config['duration'];
231
        $key      = $this->_key($key);
232
        $value    = $this->serialize($value);
233
234
        return (bool) ($this->_Redis->set($key, $value, ['nx', 'ex' => $duration]));
235
    }
236
237
    /**
238
     * {@inheritDoc}
239
     */
240
    public function groups(): array
241
    {
242
        $result = [];
243
244
        foreach ($this->_config['groups'] as $group) {
245
            $value = $this->_Redis->get($this->_config['prefix'] . $group);
246
            if (! $value) {
247
                $value = $this->serialize(1);
248
                $this->_Redis->set($this->_config['prefix'] . $group, $value);
249
            }
250
            $result[] = $group . $value;
251
        }
252
253
        return $result;
254
    }
255
256
    /**
257
     * {@inheritDoc}
258
     */
259
    public function clearGroup(string $group): bool
260
    {
261
        return (bool) $this->_Redis->incr($this->_config['prefix'] . $group);
262
    }
263
264
    /**
265
     * Sérialisez la valeur pour l'enregistrer dans Redis.
266
     *
267
     * Ceci est nécessaire au lieu d'utiliser la fonction de sérialisation intégrée de Redis
268
     * car cela crée des problèmes d'incrémentation/décrémentation de la valeur entière initialement définie.
269
     *
270
     * @see https://github.com/phpredis/phpredis/issues/81
271
     */
272
    protected function serialize(mixed $value): string
273
    {
274
        if (is_int($value)) {
275
            return (string) $value;
276
        }
277
278
        return serialize($value);
279
    }
280
281
    /**
282
     * Désérialiser la valeur de chaîne extraite de Redis.
283
     */
284
    protected function unserialize(string $value): mixed
285
    {
286
        if (preg_match('/^[-]?\d+$/', $value)) {
287
            return (int) $value;
288
        }
289
290
        return unserialize($value);
291
    }
292
293
    /**
294
     * Se déconnecte du serveur redis
295
     */
296
    public function __destruct()
297
    {
298
        if (empty($this->_config['persistent']) && $this->_Redis instanceof Redis) {
299
            $this->_Redis->close();
300
        }
301
    }
302
}
303