Passed
Push — main ( eecacd...dafd8a )
by Dimitri
11:37
created

RedisHandler::info()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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 info()
241
	{
242
		return $this->_Redis->info();
243
	}
244
245
    /**
246
     * {@inheritDoc}
247
     */
248
    public function groups(): array
249
    {
250
        $result = [];
251
252
        foreach ($this->_config['groups'] as $group) {
253
            $value = $this->_Redis->get($this->_config['prefix'] . $group);
254
            if (! $value) {
255
                $value = $this->serialize(1);
256
                $this->_Redis->set($this->_config['prefix'] . $group, $value);
257
            }
258
            $result[] = $group . $value;
259
        }
260
261
        return $result;
262
    }
263
264
    /**
265
     * {@inheritDoc}
266
     */
267
    public function clearGroup(string $group): bool
268
    {
269
        return (bool) $this->_Redis->incr($this->_config['prefix'] . $group);
270
    }
271
272
    /**
273
     * Sérialisez la valeur pour l'enregistrer dans Redis.
274
     *
275
     * Ceci est nécessaire au lieu d'utiliser la fonction de sérialisation intégrée de Redis
276
     * car cela crée des problèmes d'incrémentation/décrémentation de la valeur entière initialement définie.
277
     *
278
     * @see https://github.com/phpredis/phpredis/issues/81
279
     */
280
    protected function serialize(mixed $value): string
281
    {
282
        if (is_int($value)) {
283
            return (string) $value;
284
        }
285
286
        return serialize($value);
287
    }
288
289
    /**
290
     * Désérialiser la valeur de chaîne extraite de Redis.
291
     */
292
    protected function unserialize(string $value): mixed
293
    {
294
        if (preg_match('/^[-]?\d+$/', $value)) {
295
            return (int) $value;
296
        }
297
298
        return unserialize($value);
299
    }
300
301
    /**
302
     * Se déconnecte du serveur redis
303
     */
304
    public function __destruct()
305
    {
306
        if (empty($this->_config['persistent']) && $this->_Redis instanceof Redis) {
307
            $this->_Redis->close();
308
        }
309
    }
310
}
311