Issues (29)

Handlers/BaseHandler.php (1 issue)

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 DateTime;
21
use Exception;
22
23
abstract class BaseHandler implements CacheInterface
24
{
25
    use InstanceConfigTrait;
26
27
    /**
28
     * @var string
29
     */
30
    protected const CHECK_KEY = 'key';
31
32
    /**
33
     * @var string
34
     */
35
    protected const CHECK_VALUE = 'value';
36
37
    /**
38
     * 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.
39
     * From https://github.com/symfony/cache-contracts/blob/c0446463729b89dd4fa62e9aeecc80287323615d/ItemInterface.php#L43
40
     */
41
    protected static string $reservedCharacters = '{}()/\@:';
42
43
    /**
44
     * Préfixe à appliquer aux clés de cache.
45
     * Ne peut pas être utilisé par tous les gestionnaires.
46
     */
47
    protected string $prefix;
48
49
    /**
50
     * La configuration de cache par défaut est remplacée dans la plupart des adaptateurs de cache. Ceux-ci sont
51
     * les clés communes à tous les adaptateurs. Si elle est remplacée, cette propriété n'est pas utilisée.
52
     *
53
     * - `duration` Spécifiez combien de temps durent les éléments de cette configuration de cache.
54
     * - `groups` Liste des groupes ou 'tags' associés à chaque clé stockée dans cette configuration.
55
     * 			pratique pour supprimer un groupe complet du cache.
56
     * - `prefix` Préfixe ajouté à toutes les entrées. Bon pour quand vous avez besoin de partager un keyspace
57
     * 			avec une autre configuration de cache ou une autre application.
58
     * - `warnOnWriteFailures` Certains moteurs, tels que ApcuEngine, peuvent déclencher des avertissements en cas d'échecs d'écriture.
59
     *
60
     * @var array<string, mixed>
61
     */
62
    protected array $_defaultConfig = [
63
        'duration'            => 3600,
64
        'groups'              => [],
65
        'prefix'              => 'blitz_',
66
        'warnOnWriteFailures' => true,
67
    ];
68
69
    /**
70
     * Contient la chaîne compilée avec tous les groupes
71
     * préfixes à ajouter à chaque clé dans ce moteur de cache
72
     */
73
    protected string $_groupPrefix = '';
74
75
    /**
76
     * Initialiser le moteur de cache
77
     *
78
     * Appelé automatiquement par le frontal du cache. Fusionner la configuration d'exécution avec les valeurs par défaut
79
     * Avant utilisation.
80
     *
81
     * @param array<string, mixed> $config Tableau associatif de paramètres pour le moteur
82
     *
83
     * @return bool Vrai si le moteur a été initialisé avec succès, faux sinon
84
     */
85
    public function init(array $config = []): bool
86
    {
87
        if (isset($config['prefix'])) {
88
            $config['prefix'] = str_replace(' ', '-', strtolower($config['prefix']));
89
        }
90
91
        $this->setConfig($config);
92
93
        if (! empty($this->_config['groups'])) {
94
            sort($this->_config['groups']);
95
            $this->_groupPrefix = str_repeat('%s_', count($this->_config['groups']));
96
        }
97
        if (! is_numeric($this->_config['duration'])) {
98
            $this->_config['duration'] = strtotime($this->_config['duration']) - time();
99
        }
100
101
        return true;
102
    }
103
104
    /**
105
     * Modifie les caractères reservés
106
     */
107
    public function setReservedCharacters(string $reservedCharacters)
108
    {
109
        self::$reservedCharacters = $reservedCharacters;
110
    }
111
112
    /**
113
     * Assurez-vous de la validité de la clé de cache donnée.
114
     *
115
     * @throws InvalidArgumentException Quand la clé n'est pas valide
116
     */
117
    public function ensureValidKey(string $key): void
118
    {
119
        if (! is_string($key) || $key === '') {
0 ignored issues
show
The condition is_string($key) is always true.
Loading history...
120
            throw new InvalidArgumentException('Une clé de cache doit être une chaîne non vide.');
121
        }
122
123
        $reserved = self::$reservedCharacters;
124
        if ($reserved && strpbrk($key, $reserved) !== false) {
125
            throw new InvalidArgumentException('La clé de cache contient des caractères réservés ' . $reserved);
126
        }
127
    }
128
129
    /**
130
     * Assurez-vous de la validité du type d'argument et des clés de cache.
131
     *
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 $iterable, string $check = self::CHECK_VALUE): void
137
    {
138
        foreach ($iterable as $key => $value) {
139
            if ($check === self::CHECK_VALUE) {
140
                $this->ensureValidKey($value);
141
            } else {
142
                $this->ensureValidKey($key);
143
            }
144
        }
145
    }
146
147
    /**
148
     * Fournit la possibilité de faire facilement la mise en cache de lecture.
149
     *
150
     * Lorsqu'elle est appelée si la clé $ n'est pas définie dans $config, la fonction $callable
151
     * sera invoqué. Les résultats seront ensuite stockés dans la configuration du cache
152
     * à la clé.
153
     *
154
     * Exemples:
155
     *
156
     * En utilisant une Closure pour fournir des données, supposez que `$this` est un objet Table :
157
     *
158
     * ```
159
     * $resultats = $cache->remember('all_articles', function() {
160
     * 		return $this->find('all')->toArray();
161
     * });
162
     * ```
163
     *
164
     * @param string                         $key      La clé de cache sur laquelle lire/stocker les données.
165
     * @param callable|DateInterval|int|null $ttl      Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
166
     *                                                 le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
167
     *                                                 pour cela ou laissez le conducteur s'en occuper.
168
     * @param callable                       $callable Le callback qui fournit des données dans le cas où
169
     *                                                 la clé de cache est vide. Peut être n'importe quel type appelable pris en charge par votre PHP.
170
     *
171
     * @return mixed Si la clé est trouvée : les données en cache.
172
     *               Si la clé n'est pas trouvée, la valeur renvoyée par le callable.
173
     */
174
    public function remember(string $key, callable|DateInterval|int|null $ttl, ?callable $callable = null): mixed
175
    {
176
        if (is_callable($ttl)) {
177
            $callable = $ttl;
178
            $ttl      = null;
179
        }
180
181
        if (null !== $value = $this->get($key)) {
182
            return $value;
183
        }
184
185
        $this->set($key, $value = $callable(), $ttl);
186
187
        return $value;
188
    }
189
190
    /**
191
     * Supprime les éléments du magasin de cache correspondant à un modèle donné.
192
     *
193
     * @param string $pattern Modèle de style global des éléments du cache
194
     *
195
     * @throws Exception
196
     */
197
    public function deleteMatching(string $pattern)
198
    {
199
        throw new Exception('La méthode deleteMatching n\'est pas implémentée.');
200
    }
201
202
    /**
203
     * Obtient plusieurs éléments de cache par leurs clés uniques.
204
     *
205
     * @param iterable $keys    Une liste de clés pouvant être obtenues en une seule opération.
206
     * @param mixed    $default Valeur par défaut à renvoyer pour les clés qui n'existent pas.
207
     *
208
     * @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.
209
     *
210
     * @throws InvalidArgumentException Si $keys n'est ni un tableau ni un Traversable,
211
     *                                  ou si l'une des clés n'a pas de valeur légale.
212
     */
213
    public function getMultiple(iterable $keys, mixed $default = null): iterable
214
    {
215
        $this->ensureValidType($keys);
216
217
        $results = [];
218
219
        foreach ($keys as $key) {
220
            $results[$key] = $this->get($key, $default);
221
        }
222
223
        return $results;
224
    }
225
226
    /**
227
     * Persiste un ensemble de paires clé => valeur dans le cache, avec un TTL facultatif.
228
     *
229
     * @param iterable              $values Une liste de paires clé => valeur pour une opération sur plusieurs ensembles.
230
     * @param DateInterval|int|null $ttl    Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
231
     *                                      le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
232
     *                                      pour cela ou laissez le conducteur s'en occuper.
233
     *
234
     * @return bool Vrai en cas de succès et faux en cas d'échec.
235
     *
236
     * @throws InvalidArgumentException Si $values n'est ni un tableau ni un Traversable,
237
     *                                  ou si l'une des valeurs $ n'est pas une valeur légale.
238
     */
239
    public function setMultiple(iterable $values, DateInterval|int|null $ttl = null): bool
240
    {
241
        $this->ensureValidType($values, self::CHECK_KEY);
242
243
		$restore = null;
244
        if ($ttl !== null) {
245
            $restore = $this->getConfig('duration');
246
            $this->setConfig('duration', $ttl);
247
        }
248
249
        try {
250
            foreach ($values as $key => $value) {
251
                $success = $this->set($key, $value);
252
                if ($success === false) {
253
                    return false;
254
                }
255
            }
256
257
            return true;
258
        } finally {
259
            if($restore !== null) {
260
                $this->setConfig('duration', $restore);
261
            }
262
        }
263
    }
264
265
    /**
266
     * Supprime plusieurs éléments du cache sous forme de liste
267
     *
268
     * Il s'agit d'une tentative de meilleur effort. Si la suppression d'un élément
269
     * créer une erreur, elle sera ignorée et tous les éléments seront
270
     * être tenté.
271
     *
272
     * @param iterable $keys Une liste de clés basées sur des chaînes à supprimer.
273
     *
274
     * @return bool Vrai si les éléments ont été supprimés avec succès. Faux s'il y a eu une erreur.
275
     *
276
     * @throws InvalidArgumentException Si $keys n'est ni un tableau ni un Traversable,
277
     *                                  ou si l'une des clés $ n'a pas de valeur légale.
278
     */
279
    public function deleteMultiple(iterable $keys): bool
280
    {
281
        $this->ensureValidType($keys);
282
283
        $result = true;
284
285
        foreach ($keys as $key) {
286
            if (! $this->delete($key)) {
287
                $result = false;
288
            }
289
        }
290
291
        return $result;
292
    }
293
294
    /**
295
     * Détermine si un élément est présent dans le cache.
296
     *
297
     * REMARQUE : Il est recommandé que has() ne soit utilisé qu'à des fins de type réchauffement du cache
298
     * et à ne pas utiliser dans vos opérations d'applications en direct pour get/set, car cette méthode
299
     * est soumis à une condition de concurrence où votre has() renverra vrai et immédiatement après,
300
     * un autre script peut le supprimer, rendant l'état de votre application obsolète.
301
     *
302
     * @param mixed $key
303
     *
304
     * @throws InvalidArgumentException Si la chaîne $key n'est pas une valeur légale.
305
     */
306
    public function has(string $key): bool
307
    {
308
        return $this->get($key) !== null;
309
    }
310
311
    /**
312
     * Récupère la valeur d'une clé donnée dans le cache.
313
     */
314
    abstract public function get(string $key, mixed $default = null): mixed;
315
316
    /**
317
     * 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.
318
     *
319
     * @param DateInterval|int|null $ttl Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
320
     *                                   le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
321
     *                                   pour cela ou laissez le conducteur s'en occuper.
322
     *
323
     * @return bool Vrai en cas de succès et faux en cas d'échec.
324
     */
325
    abstract public function set(string $key, mixed $value, DateInterval|int|null $ttl = null): bool;
326
327
    /**
328
     * {@inheritDoc}
329
     */
330
    abstract public function increment(string $key, int $offset = 1);
331
332
    /**
333
     * {@inheritDoc}
334
     */
335
    abstract public function decrement(string $key, int $offset = 1);
336
337
    /**
338
     * {@inheritDoc}
339
     */
340
    abstract public function delete(string $key): bool;
341
342
    /**
343
     * {@inheritDoc}
344
     */
345
    abstract public function clear(): bool;
346
347
    /**
348
     * {@inheritDoc}
349
     */
350
    public function add(string $key, mixed $value): bool
351
    {
352
        $cachedValue = $this->get($key);
353
        if ($cachedValue === null) {
354
            return $this->set($key, $value);
355
        }
356
357
        return false;
358
    }
359
360
    /**
361
     * {@inheritDoc}
362
     */
363
    abstract public function clearGroup(string $group): bool;
364
365
    /**
366
     * {@inheritDoc}
367
     */
368
    public function info()
369
    {
370
        return null;
371
    }
372
373
    /**
374
     * Effectue toute initialisation pour chaque groupe est nécessaire
375
     * et renvoie la "valeur du groupe" pour chacun d'eux, c'est
376
     * le jeton représentant chaque groupe dans la clé de cache
377
     *
378
     * @return list<string>
379
     */
380
    public function groups(): array
381
    {
382
        return $this->_config['groups'];
383
    }
384
385
    /**
386
     * Génère une clé pour l'utilisation du backend du cache.
387
     *
388
     * Si la clé demandée est valide, la valeur du préfixe de groupe et le préfixe du moteur sont appliqués.
389
     * Les espaces blancs dans les clés seront remplacés.
390
     *
391
     * @param string $key la clé transmise
392
     *
393
     * @return string Clé préfixée avec des caractères potentiellement dangereux remplacés.
394
     *
395
     * @throws InvalidArgumentException Si la valeur de la clé n'est pas valide.
396
     */
397
    protected function _key($key): string
398
    {
399
        $this->ensureValidKey($key);
400
401
        $prefix = '';
402
        if ($this->_groupPrefix) {
403
            $prefix = hash('xxh128', implode('_', $this->groups()));
404
        }
405
        $key = preg_replace('/[\s]+/', '_', $key);
406
407
        return $this->_config['prefix'] . $prefix . $key;
408
    }
409
410
    /**
411
     * Les moteurs de cache peuvent déclencher des avertissements s'ils rencontrent des pannes pendant le fonctionnement,
412
     * si l'option warnOnWriteFailures est définie sur true.
413
     */
414
    protected function warning(string $message): void
415
    {
416
        if ($this->getConfig('warnOnWriteFailures') !== true) {
417
            return;
418
        }
419
420
        Helpers::triggerWarning($message);
421
    }
422
423
    /**
424
     * Convertir les différentes expressions d'une valeur TTL en durée en secondes
425
     *
426
     * @param DateInterval|int|null $ttl La valeur TTL de cet élément. Si null est envoyé,
427
     *                                   La durée par défaut du conducteur sera utilisée.
428
     */
429
    protected function duration(DateInterval|int|null $ttl): int
430
    {
431
        if ($ttl === null) {
432
            return $this->_config['duration'];
433
        }
434
435
        if (is_int($ttl)) {
436
            return max(0, $ttl);
437
        }
438
439
        /** @var DateTime $datetime */
440
        $datetime = DateTime::createFromFormat('U', '0');
441
442
        return (int) $datetime->add($ttl)->format('U');
443
    }
444
}
445