BaseHandler::setReservedCharacters()   A
last analyzed

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
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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 BlitzPHP\Cache\InvalidArgumentException;
15
use BlitzPHP\Contracts\Cache\CacheInterface;
16
use BlitzPHP\Traits\InstanceConfigTrait;
17
use BlitzPHP\Utilities\Helpers;
18
use Closure;
19
use DateInterval;
20
use Exception;
21
22
abstract class BaseHandler implements CacheInterface
23
{
24
    use InstanceConfigTrait;
25
26
    /**
27
     * @var string
28
     */
29
    protected const CHECK_KEY = 'key';
30
31
    /**
32
     * @var string
33
     */
34
    protected const CHECK_VALUE = 'value';
35
36
    /**
37
     * Caractères réservés qui ne peuvent pas être utilisés dans une clé ou une étiquette. Peut être remplacé par le fichier config.
38
     * From https://github.com/symfony/cache-contracts/blob/c0446463729b89dd4fa62e9aeecc80287323615d/ItemInterface.php#L43
39
     */
40
    protected static string $reservedCharacters = '{}()/\@:';
41
42
    /**
43
     * Préfixe à appliquer aux clés de cache.
44
     * Ne peut pas être utilisé par tous les gestionnaires.
45
     */
46
    protected string $prefix;
47
48
    /**
49
     * La configuration de cache par défaut est remplacée dans la plupart des adaptateurs de cache. Ceux-ci sont
50
     * les clés communes à tous les adaptateurs. Si elle est remplacée, cette propriété n'est pas utilisée.
51
     *
52
     * - `duration` Spécifiez combien de temps durent les éléments de cette configuration de cache.
53
     * - `groups` Liste des groupes ou 'tags' associés à chaque clé stockée dans cette configuration.
54
     * 			pratique pour supprimer un groupe complet du cache.
55
     * - `prefix` Préfixe ajouté à toutes les entrées. Bon pour quand vous avez besoin de partager un keyspace
56
     * 			avec une autre configuration de cache ou une autre application.
57
     * - `warnOnWriteFailures` Certains moteurs, tels que ApcuEngine, peuvent déclencher des avertissements en cas d'échecs d'écriture.
58
     *
59
     * @var array<string, mixed>
60
     */
61
    protected array $_defaultConfig = [
62
        'duration'            => 3600,
63
        'groups'              => [],
64
        'prefix'              => 'blitz_',
65
        'warnOnWriteFailures' => true,
66
    ];
67
68
    /**
69
     * Contient la chaîne compilée avec tous les groupes
70
     * préfixes à ajouter à chaque clé dans ce moteur de cache
71
     */
72
    protected string $_groupPrefix = '';
73
74
    /**
75
     * Initialiser le moteur de cache
76
     *
77
     * Appelé automatiquement par le frontal du cache. Fusionner la configuration d'exécution avec les valeurs par défaut
78
     * Avant utilisation.
79
     *
80
     * @param array<string, mixed> $config Tableau associatif de paramètres pour le moteur
81
     *
82
     * @return bool Vrai si le moteur a été initialisé avec succès, faux sinon
83
     */
84
    public function init(array $config = []): bool
85
    {
86
        if (isset($config['prefix'])) {
87
           $config['prefix'] = str_replace(' ', '-', strtolower($config['prefix'])); 
88
        }
89
90
        $this->setConfig($config);
91
92
        if (! empty($this->_config['groups'])) {
93
            sort($this->_config['groups']);
94
            $this->_groupPrefix = str_repeat('%s_', count($this->_config['groups']));
95
        }
96
        if (! is_numeric($this->_config['duration'])) {
97
            $this->_config['duration'] = strtotime($this->_config['duration']) - time();
98
        }
99
100
        return true;
101
    }
102
103
    /**
104
     * Modifie les caractères reservés
105
     */
106
    public function setReservedCharacters(string $reservedCharacters)
107
    {
108
        self::$reservedCharacters = $reservedCharacters;
109
    }
110
111
    /**
112
     * Assurez-vous de la validité de la clé de cache donnée.
113
     *
114
     * @throws InvalidArgumentException Quand la clé n'est pas valide
115
     */
116
    public function ensureValidKey(string $key): void
117
    {
118
        if (! is_string($key) || $key === '') {
0 ignored issues
show
introduced by
The condition is_string($key) is always true.
Loading history...
119
            throw new InvalidArgumentException('Une clé de cache doit être une chaîne non vide.');
120
        }
121
122
        $reserved = self::$reservedCharacters;
123
        if ($reserved && strpbrk($key, $reserved) !== false) {
124
            throw new InvalidArgumentException('La clé de cache contient des caractères réservés ' . $reserved);
125
        }
126
    }
127
128
    /**
129
     * Assurez-vous de la validité du type d'argument et des clés de cache.
130
     *
131
     * @param iterable $iterable L'itérable à vérifier.
132
     * @param string   $check    Indique s'il faut vérifier les clés ou les valeurs.
133
     *
134
     * @throws InvalidArgumentException
135
     */
136
    protected function ensureValidType($iterable, string $check = self::CHECK_VALUE): void
137
    {
138
        if (! is_iterable($iterable)) {
139
            throw new InvalidArgumentException(sprintf(
140
                'Un cache %s doit être soit un tableau soit un Traversable.',
141
                $check === self::CHECK_VALUE ? 'key set' : 'set'
142
            ));
143
        }
144
145
        foreach ($iterable as $key => $value) {
146
            if ($check === self::CHECK_VALUE) {
147
                $this->ensureValidKey($value);
148
            } else {
149
                $this->ensureValidKey($key);
150
            }
151
        }
152
    }
153
154
    /**
155
     * Fournit la possibilité de faire facilement la mise en cache de lecture.
156
     *
157
     * Lorsqu'elle est appelée si la clé $ n'est pas définie dans $config, la fonction $callable
158
     * sera invoqué. Les résultats seront ensuite stockés dans la configuration du cache
159
     * à la clé.
160
     *
161
     * Exemples:
162
     *
163
     * En utilisant une Closure pour fournir des données, supposez que `$this` est un objet Table :
164
     *
165
     * ```
166
     * $resultats = $cache->remember('all_articles', function() {
167
     * 		return $this->find('all')->toArray();
168
     * });
169
     * ```
170
     *
171
     * @param string   $key      La clé de cache sur laquelle lire/stocker les données.
172
     * @param callable|DateInterval|int|null $ttl   Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
173
     *                                     le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
174
     *                                     pour cela ou laissez le conducteur s'en occuper.
175
     *
176
     * @param callable $callable Le callback qui fournit des données dans le cas où
177
     *                           la clé de cache est vide. Peut être n'importe quel type appelable pris en charge par votre PHP.
178
     *
179
     * @return mixed Si la clé est trouvée : les données en cache.
180
     *               Si la clé n'est pas trouvée, la valeur renvoyée par le callable.
181
     */
182
    public function remember(string $key, callable|DateInterval|int|null $ttl, callable $callable = null): mixed
183
    {
184
        if (is_callable($ttl)) {
185
            $callable = $ttl;
186
            $ttl      = null;
187
        }
188
189
		if (null !== $value = $this->get($key)) {
190
			return $value;
191
		}
192
193
        $this->set($key, $value = $callable(), $ttl);
0 ignored issues
show
Bug introduced by
It seems like $ttl can also be of type callable; however, parameter $ttl of BlitzPHP\Cache\Handlers\BaseHandler::set() does only seem to accept DateInterval|integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

193
        $this->set($key, $value = $callable(), /** @scrutinizer ignore-type */ $ttl);
Loading history...
194
195
        return $value;
196
    }
197
198
    /**
199
     * Supprime les éléments du magasin de cache correspondant à un modèle donné.
200
     *
201
     * @param string $pattern Modèle de style global des éléments du cache
202
     *
203
     * @throws Exception
204
     */
205
    public function deleteMatching(string $pattern)
0 ignored issues
show
Unused Code introduced by
The parameter $pattern is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

205
    public function deleteMatching(/** @scrutinizer ignore-unused */ string $pattern)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
206
    {
207
        throw new Exception('La méthode deleteMatching n\'est pas implémentée.');
208
    }
209
210
    /**
211
     * Obtient plusieurs éléments de cache par leurs clés uniques.
212
     *
213
     * @param iterable $keys    Une liste de clés pouvant être obtenues en une seule opération.
214
     * @param mixed    $default Valeur par défaut à renvoyer pour les clés qui n'existent pas.
215
     *
216
     * @return iterable Une liste de paires clé-valeur. Les clés de cache qui n'existent pas ou qui sont obsolètes auront $default comme valeur.
217
     *
218
     * @throws InvalidArgumentException Si $keys n'est ni un tableau ni un Traversable,
219
     *                                  ou si l'une des clés n'a pas de valeur légale.
220
     */
221
    public function getMultiple(iterable $keys, mixed $default = null): iterable
222
    {
223
        $this->ensureValidType($keys);
224
225
        $results = [];
226
227
        foreach ($keys as $key) {
228
            $results[$key] = $this->get($key, $default);
229
        }
230
231
        return $results;
232
    }
233
234
    /**
235
     * Persiste un ensemble de paires clé => valeur dans le cache, avec un TTL facultatif.
236
     *
237
     * @param iterable              $values Une liste de paires clé => valeur pour une opération sur plusieurs ensembles.
238
     * @param DateInterval|int|null $ttl    Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
239
     *                                      le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
240
     *                                      pour cela ou laissez le conducteur s'en occuper.
241
     *
242
     * @return bool Vrai en cas de succès et faux en cas d'échec.
243
     *
244
     * @throws InvalidArgumentException Si $values n'est ni un tableau ni un Traversable,
245
     *                                  ou si l'une des valeurs $ n'est pas une valeur légale.
246
     */
247
    public function setMultiple(iterable $values, null|DateInterval|int $ttl = null): bool
248
    {
249
        $this->ensureValidType($values, self::CHECK_KEY);
250
251
        if ($ttl !== null) {
252
            $restore = $this->getConfig('duration');
253
            $this->setConfig('duration', $ttl);
254
        }
255
256
        try {
257
            foreach ($values as $key => $value) {
258
                $success = $this->set($key, $value);
259
                if ($success === false) {
260
                    return false;
261
                }
262
            }
263
264
            return true;
265
        } finally {
266
            if (isset($restore)) {
267
                $this->setConfig('duration', $restore);
268
            }
269
        }
270
    }
271
272
    /**
273
     * Supprime plusieurs éléments du cache sous forme de liste
274
     *
275
     * Il s'agit d'une tentative de meilleur effort. Si la suppression d'un élément
276
     * créer une erreur, elle sera ignorée et tous les éléments seront
277
     * être tenté.
278
     *
279
     * @param iterable $keys Une liste de clés basées sur des chaînes à supprimer.
280
     *
281
     * @return bool Vrai si les éléments ont été supprimés avec succès. Faux s'il y a eu une erreur.
282
     *
283
     * @throws InvalidArgumentException Si $keys n'est ni un tableau ni un Traversable,
284
     *                                  ou si l'une des clés $ n'a pas de valeur légale.
285
     */
286
    public function deleteMultiple(iterable $keys): bool
287
    {
288
        $this->ensureValidType($keys);
289
290
        $result = true;
291
292
        foreach ($keys as $key) {
293
            if (! $this->delete($key)) {
294
                $result = false;
295
            }
296
        }
297
298
        return $result;
299
    }
300
301
    /**
302
     * Détermine si un élément est présent dans le cache.
303
     *
304
     * REMARQUE : Il est recommandé que has() ne soit utilisé qu'à des fins de type réchauffement du cache
305
     * et à ne pas utiliser dans vos opérations d'applications en direct pour get/set, car cette méthode
306
     * est soumis à une condition de concurrence où votre has() renverra vrai et immédiatement après,
307
     * un autre script peut le supprimer, rendant l'état de votre application obsolète.
308
     *
309
     * @param mixed $key
310
     *
311
     * @throws InvalidArgumentException Si la chaîne $key n'est pas une valeur légale.
312
     */
313
    public function has(string $key): bool
314
    {
315
        return $this->get($key) !== null;
316
    }
317
318
    /**
319
     * Récupère la valeur d'une clé donnée dans le cache.
320
     */
321
    abstract public function get(string $key, mixed $default = null): mixed;
322
323
    /**
324
     * Persiste les données dans le cache, référencées de manière unique par la clé donnée avec un temps TTL d'expiration facultatif.
325
     *
326
     * @param DateInterval|int|null $ttl Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
327
     *                                   le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
328
     *                                   pour cela ou laissez le conducteur s'en occuper.
329
     *
330
     * @return bool Vrai en cas de succès et faux en cas d'échec.
331
     */
332
    abstract public function set(string $key, mixed $value, null|DateInterval|int $ttl = null): bool;
333
334
    /**
335
     * {@inheritDoc}
336
     */
337
    abstract public function increment(string $key, int $offset = 1);
338
339
    /**
340
     * {@inheritDoc}
341
     */
342
    abstract public function decrement(string $key, int $offset = 1);
343
344
    /**
345
     * {@inheritDoc}
346
     */
347
    abstract public function delete(string $key): bool;
348
349
    /**
350
     * {@inheritDoc}
351
     */
352
    abstract public function clear(): bool;
353
354
    /**
355
     * {@inheritDoc}
356
     */
357
    public function add(string $key, mixed $value): bool
358
    {
359
        $cachedValue = $this->get($key);
360
        if ($cachedValue === null) {
361
            return $this->set($key, $value);
362
        }
363
364
        return false;
365
    }
366
367
    /**
368
     * {@inheritDoc}
369
     */
370
    abstract public function clearGroup(string $group): bool;
371
372
    /**
373
     * {@inheritDoc}
374
     */
375
    public function info()
376
    {
377
        return null;
378
    }
379
380
    /**
381
     * Effectue toute initialisation pour chaque groupe est nécessaire
382
     * et renvoie la "valeur du groupe" pour chacun d'eux, c'est
383
     * le jeton représentant chaque groupe dans la clé de cache
384
     *
385
     * @return string[]
386
     */
387
    public function groups(): array
388
    {
389
        return $this->_config['groups'];
390
    }
391
392
    /**
393
     * Génère une clé pour l'utilisation du backend du cache.
394
     *
395
     * Si la clé demandée est valide, la valeur du préfixe de groupe et le préfixe du moteur sont appliqués.
396
     * Les espaces blancs dans les clés seront remplacés.
397
     *
398
     * @param string $key la clé transmise
399
     *
400
     * @return string Clé préfixée avec des caractères potentiellement dangereux remplacés.
401
     *
402
     * @throws InvalidArgumentException Si la valeur de la clé n'est pas valide.
403
     */
404
    protected function _key($key): string
405
    {
406
        $this->ensureValidKey($key);
407
408
        $prefix = '';
409
        if ($this->_groupPrefix) {
410
            $prefix = md5(implode('_', $this->groups()));
411
        }
412
        $key = preg_replace('/[\s]+/', '_', $key);
413
414
        return $this->_config['prefix'] . $prefix . $key;
415
    }
416
417
    /**
418
     * Les moteurs de cache peuvent déclencher des avertissements s'ils rencontrent des pannes pendant le fonctionnement,
419
     * si l'option warnOnWriteFailures est définie sur true.
420
     */
421
    protected function warning(string $message): void
422
    {
423
        if ($this->getConfig('warnOnWriteFailures') !== true) {
424
            return;
425
        }
426
427
        Helpers::triggerWarning($message);
428
    }
429
430
    /**
431
     * Convertir les différentes expressions d'une valeur TTL en durée en secondes
432
     *
433
     * @param DateInterval|int|null $ttl La valeur TTL de cet élément. Si null est envoyé,
434
     *                                   La durée par défaut du conducteur sera utilisée.
435
     */
436
    protected function duration(null|DateInterval|int $ttl): int
437
    {
438
        if ($ttl === null) {
439
            return $this->_config['duration'];
440
        }
441
442
        if (is_int($ttl)) {
443
            return $ttl;
444
        }
445
446
        return (int) $ttl->format('%s');
447
    }
448
}
449