Passed
Push — main ( 8f6c6e...4fdfef )
by Dimitri
11:17
created

Handlers/BaseHandler.php (1 issue)

Labels
Severity
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\CacheInterface;
15
use BlitzPHP\Cache\InvalidArgumentException;
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
        $this->setConfig($config);
87
88
        if (! empty($this->_config['groups'])) {
89
            sort($this->_config['groups']);
90
            $this->_groupPrefix = str_repeat('%s_', count($this->_config['groups']));
91
        }
92
        if (! is_numeric($this->_config['duration'])) {
93
            $this->_config['duration'] = strtotime($this->_config['duration']) - time();
94
        }
95
96
        return true;
97
    }
98
99
    /**
100
     * Modifie les caractères reservés
101
     */
102
    public function setReservedCharacters(string $reservedCharacters)
103
    {
104
        self::$reservedCharacters = $reservedCharacters;
105
    }
106
107
    /**
108
     * Assurez-vous de la validité de la clé de cache donnée.
109
     *
110
     * @throws InvalidArgumentException Quand la clé n'est pas valide
111
     */
112
    public function ensureValidKey(string $key): void
113
    {
114
        if (! is_string($key) || $key === '') {
115
            throw new InvalidArgumentException('Une clé de cache doit être une chaîne non vide.');
116
        }
117
118
        $reserved = self::$reservedCharacters;
119
        if ($reserved && strpbrk($key, $reserved) !== false) {
120
            throw new InvalidArgumentException('La clé de cache contient des caractères réservés ' . $reserved);
121
        }
122
    }
123
124
    /**
125
     * Assurez-vous de la validité du type d'argument et des clés de cache.
126
     *
127
     * @param iterable $iterable L'itérable à vérifier.
128
     * @param string   $check    Indique s'il faut vérifier les clés ou les valeurs.
129
     *
130
     * @throws InvalidArgumentException
131
     */
132
    protected function ensureValidType($iterable, string $check = self::CHECK_VALUE): void
133
    {
134
        if (! is_iterable($iterable)) {
135
            throw new InvalidArgumentException(sprintf(
136
                'Un cache %s doit être soit un tableau soit un Traversable.',
137
                $check === self::CHECK_VALUE ? 'key set' : 'set'
138
            ));
139
        }
140
141
        foreach ($iterable as $key => $value) {
142
            if ($check === self::CHECK_VALUE) {
143
                $this->ensureValidKey($value);
144
            } else {
145
                $this->ensureValidKey($key);
146
            }
147
        }
148
    }
149
150
    /**
151
     * Fournit la possibilité de faire facilement la mise en cache de lecture.
152
     *
153
     * Lorsqu'elle est appelée si la clé $ n'est pas définie dans $config, la fonction $callable
154
     * sera invoqué. Les résultats seront ensuite stockés dans la configuration du cache
155
     * à la clé.
156
     *
157
     * Exemples:
158
     *
159
     * En utilisant une Closure pour fournir des données, supposez que `$this` est un objet Table :
160
     *
161
     * ```
162
     * $resultats = $cache->remember('all_articles', function() {
163
     * 		return $this->find('all')->toArray();
164
     * });
165
     * ```
166
     *
167
     * @param string   $key      La clé de cache sur laquelle lire/stocker les données.
168
     * @param callable|DateInterval|int|null $ttl   Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
169
     *                                     le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
170
     *                                     pour cela ou laissez le conducteur s'en occuper.
171
     *
172
     * @param callable $callable Le callback qui fournit des données dans le cas où
173
     *                           la clé de cache est vide. Peut être n'importe quel type appelable pris en charge par votre PHP.
174
     *
175
     * @return mixed Si la clé est trouvée : les données en cache.
176
     *               Si la clé n'est pas trouvée, la valeur renvoyée par le callable.
177
     */
178
    public function remember(string $key, callable|DateInterval|int|null $ttl, callable $callable): mixed
179
    {
180
        if (is_callable($ttl)) {
181
            $callable = $ttl;
182
            $ttl      = null;
183
        }
184
185
		if (null !== $value = $this->get($key)) {
186
			return $value;
187
		}
188
189
        $this->set($key, $value = $callable(), $ttl);
0 ignored issues
show
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

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