Passed
Push — main ( 4fdfef...ddb583 )
by Dimitri
11:51
created

Cache::pull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 4
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;
13
14
use BlitzPHP\Cache\Handlers\BaseHandler;
15
use BlitzPHP\Cache\Handlers\Dummy;
16
use BlitzPHP\Utilities\Helpers;
17
use DateInterval;
18
use RuntimeException;
19
20
/**
21
 * Cache fournit une interface cohérente à la mise en cache dans votre application. Il vous permet
22
 * d'utiliser plusieurs moteurs de Cache différents, sans coupler votre application à un moteur spécifique
23
 * la mise en oeuvre. Il vous permet également de modifier le stockage ou la configuration du cache sans affecter
24
 * le reste de votre candidature.
25
 *
26
 * Cela configurerait un moteur de cache APCu sur l'alias "shared". Vous pourrez alors lire et écrire
27
 * à cet alias de cache en l'utilisant pour le paramètre `$config` dans les différentes méthodes Cache.
28
 *
29
 * En général, toutes les opérations de cache sont prises en charge par tous les moteurs de cache.
30
 * Cependant, Cache::increment() et Cache::decrement() ne sont pas pris en charge par la mise en cache des fichiers.
31
 *
32
 * Il existe 7 moteurs de mise en cache intégrés :
33
 *
34
 * - `Apcu` - Utilise le cache d'objets APCu, l'un des moteurs de mise en cache les plus rapides.
35
 * - `Array` - Utilise uniquement la mémoire pour stocker toutes les données, pas réellement un moteur persistant.
36
 * 			Peut être utile dans un environnement de test ou CLI.
37
 * - `File` - Utilise des fichiers simples pour stocker le contenu. Mauvaises performances, mais bonnes pour
38
 * 			stocker de gros objets ou des choses qui ne sont pas sensibles aux E/S. Bien adapté au développement
39
 * 			car il s'agit d'un cache facile à inspecter et à vider manuellement.
40
 * - `Memcache` - Utilise l'extension PECL::Memcache et Memcached pour le stockage.
41
 * 			Lectures/écritures rapides et avantages de la distribution de Memcache.
42
 * - `Redis` - Utilise l'extension redis et php-redis pour stocker les données de cache.
43
 * - `Wincache` - Utilise l'extension de cache Windows pour PHP. Prend en charge Wincache 1.1.0 et supérieur.
44
 * 			Ce moteur est recommandé aux personnes déployant sur Windows avec IIS.
45
 * - `Xcache` - Utilise l'extension Xcache, une alternative à APCu.
46
 *
47
 * Voir la documentation du moteur de cache pour les clés de configuration attendues.
48
 */
49
class Cache implements CacheInterface
50
{
51
    /**
52
     * Un tableau mappant les schémas d'URL aux noms de classe de moteur de mise en cache complets.
53
     *
54
     * @var array<string, string>
55
     * @psalm-var array<string, class-string>
56
     */
57
    protected static array $validHandlers = [
58
        'apcu'      => Handlers\Apcu::class,
59
        'array'     => Handlers\ArrayHandler::class,
60
        'dummy'     => Handlers\Dummy::class,
61
        'file'      => Handlers\File::class,
62
        'memcached' => Handlers\Memcached::class,
63
        'redis'     => Handlers\RedisHandler::class,
64
        'wincache'  => Handlers\Wincache::class,
65
    ];
66
67
    /**
68
     * Drapeau pour verifier si la mise en cache est activr ou pas.
69
     */
70
    protected static bool $_enabled = true;
71
72
    /**
73
     * Configuration des caches
74
     */
75
    protected array $config = [];
76
77
    /**
78
     * Adapter a utiliser pour la mise en cache
79
     */
80
    private ?CacheInterface $adapter;
81
82
    /**
83
     * Constructeur
84
     */
85
    public function __construct(array $config = [])
86
    {
87
        $this->setConfig($config);
88
    }
89
90
    /**
91
     * Modifie les configuration du cache pour la fabrique actuelle
92
     */
93
    public function setConfig(array $config): self
94
    {
95
        $this->config  = $config;
96
        $this->adapter = null;
97
98
        return $this;
99
    }
100
101
    /**
102
     * Tente de créer le gestionnaire de cache souhaité
103
	 *
104
	 * @return BaseHandler
105
     */
106
    protected function factory(): CacheInterface
107
    {
108
        if (! static::$_enabled) {
109
            return new Dummy();
110
        }
111
        if (! empty($this->adapter)) {
112
            return $this->adapter;
113
        }
114
115
        $validHandlers = $this->config['valid_handlers'] ?? self::$validHandlers;
116
117
        if (empty($validHandlers) || ! is_array($validHandlers)) {
118
            throw new InvalidArgumentException('La configuration du cache doit avoir un tableau de $valid_handlers.');
119
        }
120
121
        $handler  = $this->config['handler'] ?? null;
122
        $fallback = $this->config['fallback_handler'] ?? null;
123
124
        if (empty($handler)) {
125
            throw new InvalidArgumentException('La configuration du cache doit avoir un ensemble de gestionnaires.');
126
        }
127
128
        if (! array_key_exists($handler, $validHandlers)) {
129
            throw new InvalidArgumentException('La configuration du cache a un gestionnaire non valide spécifié.');
130
        }
131
132
        $adapter = new $validHandlers[$handler]();
133
        if (! ($adapter instanceof BaseHandler)) {
134
            if (empty($fallback)) {
135
                $adapter = new Dummy();
136
            } elseif (! array_key_exists($fallback, $validHandlers)) {
137
                throw new InvalidArgumentException('La configuration du cache a un gestionnaire de secours non valide spécifié.');
138
            } else {
139
                $adapter = new $validHandlers[$fallback]();
140
            }
141
        }
142
143
        if (! ($adapter instanceof BaseHandler)) {
144
            throw new InvalidArgumentException('Le gestionnaire de cache doit utiliser BlitzPHP\Cache\Handlers\BaseHandler comme classe de base.');
145
        }
146
147
        if (isset($this->config[$handler]) && is_array($this->config[$handler])) {
148
            $this->config = array_merge($this->config, $this->config[$handler]);
149
            unset($this->config[$handler]);
150
        }
151
152
        if (! $adapter->init($this->config)) {
153
            throw new RuntimeException(
154
                sprintf(
155
                    'Le moteur de cache %s n\'est pas correctement configuré. Consultez le journal des erreurs pour plus d\'informations.',
156
                    get_class($adapter)
157
                )
158
            );
159
        }
160
161
        return $this->adapter = $adapter;
162
    }
163
164
    /**
165
     * Persiste les données dans le cache, référencées de manière unique par une clé avec un temps d'expiration TTL optionnel.
166
     *
167
     * ### Utilisation :
168
     *
169
     * Écriture dans la configuration de cache active :
170
     *
171
     * ```
172
     * $cache->write('cached_data', $data);
173
     * ```
174
     *
175
     * @param mixed                 $value Données à mettre en cache - tout sauf une ressource
176
     * @param DateInterval|int|null $ttl   Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
177
     *                                     le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
178
     *                                     pour cela ou laissez le conducteur s'en occuper.
179
     *
180
     * @return bool Vrai si les données ont été mises en cache avec succès, faux en cas d'échec
181
     */
182
    public function write(string $key, mixed $value, null|DateInterval|int $ttl = null): bool
183
    {
184
        if (is_resource($value)) {
185
            return false;
186
        }
187
188
        $backend = $this->factory();
189
        $success = $backend->set($key, $value, $ttl);
190
        if ($success === false && $value !== '') {
191
            trigger_error(
192
                sprintf(
193
                    "Unable to write '%s' to %s cache",
194
                    $key,
195
                    get_class($backend)
196
                ),
197
                E_USER_WARNING
198
            );
199
        }
200
201
        return $success;
202
    }
203
204
    /**
205
     * {@inheritDoc}
206
     */
207
    public function set(string $key, mixed $value, null|DateInterval|int $ttl = null): bool
208
    {
209
        return $this->write($key, $value, $ttl);
210
    }
211
212
    /**
213
     * Écrire des données pour de nombreuses clés dans le cache.
214
     *
215
     * ### Utilisation :
216
     *
217
     * Écriture dans la configuration de cache active :
218
     *
219
     * ```
220
     * $cache->writeMany(['cached_data_1' => 'data 1', 'cached_data_2' => 'data 2']);
221
     * ```
222
     *
223
     * @param iterable              $data Un tableau ou Traversable de données à stocker dans le cache
224
     * @param DateInterval|int|null $ttl  Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
225
     *                                    le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
226
     *                                    pour cela ou laissez le conducteur s'en occuper.
227
     *
228
     * @return bool Vrai en cas de succès, faux en cas d'échec
229
     *
230
     * @throws InvalidArgumentException
231
     */
232
    public function writeMany(iterable $data, null|DateInterval|int $ttl = null): bool
233
    {
234
        return $this->factory()->setMultiple($data, $ttl);
235
    }
236
237
    /**
238
     * {@inheritDoc}
239
     */
240
    public function setMultiple(iterable $values, null|DateInterval|int $ttl = null): bool
241
    {
242
        return $this->writeMany($values, $ttl);
243
    }
244
245
    /**
246
     * Récupère une valeur dans le cache.
247
     *
248
     * ### Utilisation :
249
     *
250
     * Lecture à partir de la configuration du cache actif.
251
     *
252
     * ```
253
     * $cache->read('my_data');
254
	 * ```
255
     */
256
    public function read(string $key, mixed $default = null): mixed
257
    {
258
        if (is_callable($default)) {
259
            $_default = $default;
260
            $default  = null;
261
        }
262
263
        $result = $this->factory()->get($key, $default);
264
265
        if (empty($result) && isset($_default)) {
266
            if (function_exists('service')) {
267
                $result = service('container')->call($_default);
268
            } else {
269
                $result = call_user_func($_default);
270
            }
271
        }
272
273
        return $result;
274
    }
275
276
    /**
277
     * {@inheritDoc}
278
     */
279
    public function get(string $key, mixed $default = null): mixed
280
    {
281
        return $this->read($key, $default);
282
    }
283
284
    /**
285
     * Permet d'obtenir plusieurs éléments de cache à partir de leurs clés uniques.
286
     *
287
     * ### Utilisation :
288
     *
289
     * Lecture de plusieurs clés à partir de la configuration de cache active.
290
     *
291
     * ```
292
     * $cache->readMany(['my_data_1', 'my_data_2']);
293
	 * ```
294
     */
295
    public function readMany(iterable $keys, mixed $default = null): iterable
296
    {
297
        if (is_callable($default)) {
298
            $_default = $default;
299
            $default  = null;
300
        }
301
302
        $result = $this->factory()->getMultiple($keys, $default);
303
304
        if (empty($result) && isset($_default)) {
305
            if (function_exists('service')) {
306
                $result = service('container')->call($_default);
307
            } else {
308
                $result = call_user_func($_default);
309
            }
310
        }
311
312
        return $result;
313
    }
314
315
    /**
316
     * {@inheritDoc}
317
     */
318
    public function getMultiple(iterable $keys, mixed $default = null): iterable
319
    {
320
        return $this->readMany($keys, $default);
321
    }
322
323
    /**
324
     * Incrémente un nombre sous la clé et renvoie la valeur incrémentée.
325
     *
326
     * @param int $offset Combien ajouter
327
     *
328
     * @return false|int Nouvelle valeur, ou false si la donnée n'existe pas, n'est pas un entier,
329
     *                   ou si une erreur s'est produite lors de sa récupération.
330
     *
331
     * @throws InvalidArgumentException Lorsque décalage < 0
332
     */
333
    public function increment(string $key, int $offset = 1)
334
    {
335
        if ($offset < 0) {
336
            throw new InvalidArgumentException('Le décalage ne peut pas être inférieur à 0.');
337
        }
338
339
        return $this->factory()->increment($key, $offset);
340
    }
341
342
    /**
343
     * Décrémenter un nombre sous la clé et renvoyer la valeur décrémentée.
344
     *
345
     * @param int $offset Combien soustraire
346
     *
347
     * @return false|int Nouvelle valeur, ou false si la donnée n'existe pas, n'est pas un entier,
348
     *                   ou s'il y a eu une erreur lors de sa récupération
349
     *
350
     * @throws InvalidArgumentException lorsque décalage < 0
351
     */
352
    public function decrement(string $key, int $offset = 1)
353
    {
354
        if ($offset < 0) {
355
            throw new InvalidArgumentException('Le décalage ne peut pas être inférieur à 0.');
356
        }
357
358
        return $this->factory()->decrement($key, $offset);
359
    }
360
361
    /**
362
     * Supprimer une clé du cache.
363
     *
364
     * ### Utilisation :
365
     *
366
     * Suppression de la configuration du cache actif.
367
     *
368
     * ```
369
     * $cache->delete('my_data');
370
     * ```
371
     */
372
    public function delete(string $key): bool
373
    {
374
        return $this->factory()->delete($key);
375
    }
376
377
    /**
378
     * Supprime plusieurs éléments du cache en une seule opération.
379
     *
380
     * ### Utilisation :
381
     *
382
     * Suppression de plusieurs clés de la configuration du cache actif.
383
     *
384
     * ```
385
     * $cache->deleteMany(['my_data_1', 'my_data_2']);
386
     * ```
387
     *
388
     * @param iterable $keys Array ou Traversable de clés de cache à supprimer
389
     *
390
     * @throws InvalidArgumentException
391
     */
392
    public function deleteMany(iterable $keys): bool
393
    {
394
        return $this->factory()->deleteMultiple($keys);
395
    }
396
397
    /**
398
     * {@inheritDoc}
399
     */
400
    public function deleteMultiple(iterable $keys): bool
401
    {
402
        return $this->deleteMany($keys);
403
    }
404
405
    /**
406
     * Supprime toutes les clés du cache.
407
     */
408
    public function clear(): bool
409
    {
410
        return $this->factory()->clear();
411
    }
412
413
    /**
414
     * Supprime toutes les clés du cache appartenant au même groupe.
415
     */
416
    public function clearGroup(string $group): bool
417
    {
418
        return $this->factory()->clearGroup($group);
419
    }
420
421
    /**
422
     * Renvoie des informations sur l'ensemble du cache.
423
     *
424
     * Les informations retournées et la structure des données
425
     * varie selon le gestionnaire.
426
     *
427
     * @return array|false|object|null
428
     */
429
    public function info()
430
    {
431
        return $this->factory()->info();
432
    }
433
434
    /**
435
     * Réactivez la mise en cache.
436
     *
437
     * Si la mise en cache a été désactivée avec Cache::disable() cette méthode inversera cet effet.
438
     */
439
    public static function enable(): void
440
    {
441
        static::$_enabled = true;
442
    }
443
444
    /**
445
     * Désactivez la mise en cache.
446
     *
447
     * Lorsqu'il est désactivé, toutes les opérations de cache renverront null.
448
     */
449
    public static function disable(): void
450
    {
451
        static::$_enabled = false;
452
    }
453
454
    /**
455
     * Vérifiez si la mise en cache est activée.
456
     */
457
    public static function enabled(): bool
458
    {
459
        return static::$_enabled;
460
    }
461
462
    /**
463
     * Fournit la possibilité de faire facilement la mise en cache de lecture.
464
     *
465
     * Lorsqu'elle est appelée si la clé $ n'est pas définie dans $config, la fonction $callable
466
     * sera invoqué. Les résultats seront ensuite stockés dans la configuration du cache
467
     * à la clé.
468
     *
469
     * Exemples:
470
     *
471
     * En utilisant une Closure pour fournir des données, supposez que `$this` est un objet Table :
472
     *
473
     * ```
474
     * $resultats = $cache->remember('all_articles', function() {
475
     * 		return $this->find('all')->toArray();
476
     * });
477
     * ```
478
     *
479
     * @param string   $key      La clé de cache sur laquelle lire/stocker les données.
480
     * @param callable|DateInterval|int|null $ttl   Facultatif. La valeur TTL de cet élément. Si aucune valeur n'est envoyée et
481
     *                                     le pilote prend en charge TTL, la bibliothèque peut définir une valeur par défaut
482
     *                                     pour cela ou laissez le conducteur s'en occuper.
483
     *
484
     * @param callable $callable Le callback qui fournit des données dans le cas où
485
     *                           la clé de cache est vide. Peut être n'importe quel type appelable pris en charge par votre PHP.
486
     *
487
     * @return mixed Si la clé est trouvée : les données en cache.
488
     *               Si la clé n'est pas trouvée, la valeur renvoyée par le callable.
489
     */
490
    public function remember(string $key, callable|DateInterval|int|null $ttl, callable $callable): mixed
491
    {
492
        return $this->factory()->remember($key, $callable, $ttl);
0 ignored issues
show
Bug introduced by
The method remember() does not exist on BlitzPHP\Cache\CacheInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to BlitzPHP\Cache\CacheInterface. ( Ignorable by Annotation )

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

492
        return $this->factory()->/** @scrutinizer ignore-call */ remember($key, $callable, $ttl);
Loading history...
493
    }
494
495
    /**
496
     * Écrivez les données de la clé dans un moteur de cache si elles n'existent pas déjà.
497
     *
498
     * ### Utilisation :
499
     *
500
     * Écriture dans la configuration de cache active :
501
     *
502
     * ```
503
     * $cache->add('cached_data', $data);
504
     * ```
505
     *
506
     * @param mixed $value Données à mettre en cache - tout sauf une ressource.
507
     */
508
    public function add(string $key, mixed $value): bool
509
    {
510
        if (is_resource($value)) {
511
            return false;
512
        }
513
514
        return $this->factory()->add($key, $value);
515
    }
516
517
    /**
518
     * Détermine si un élément est présent dans le cache.
519
     *
520
     * REMARQUE : Il est recommandé que has() ne soit utilisé qu'à des fins de type réchauffement du cache
521
     * et à ne pas utiliser dans vos opérations d'applications en direct pour get/set, car cette méthode
522
     * est soumis à une condition de concurrence où votre has() renverra vrai et immédiatement après,
523
     * un autre script peut le supprimer, rendant l'état de votre application obsolète.
524
     *
525
     * @throws InvalidArgumentException DOIT être lancé si la chaîne $key n'est pas une valeur légale.
526
     */
527
    public function has(string $key): bool
528
    {
529
        return $this->factory()->has($key);
530
    }
531
532
	/**
533
     * Récupérez un élément du cache et supprimez-le.
534
     *
535
     * @template TCacheValue
536
     *
537
     * @param  TCacheValue|(\Closure(): TCacheValue)  $default
0 ignored issues
show
Documentation Bug introduced by
The doc comment TCacheValue|(\Closure(): TCacheValue) at position 3 could not be parsed: Expected ')' at position 3, but found '\Closure'.
Loading history...
538
     * @return (TCacheValue is null ? mixed : TCacheValue)
0 ignored issues
show
Documentation Bug introduced by
The doc comment (TCacheValue at position 1 could not be parsed: Expected ')' at position 1, but found 'TCacheValue'.
Loading history...
539
     */
540
    public function pull(string $key, $default = null)
541
    {
542
        return Helpers::tap($this->read($key, $default), function () use ($key) {
543
            $this->delete($key);
544
        });
545
    }
546
547
	/**
548
     * Récupérez un élément du cache et supprimez-le.
549
     *
550
     * @template TCacheValue
551
     *
552
     * @param  TCacheValue|(\Closure(): TCacheValue)  $default
0 ignored issues
show
Documentation Bug introduced by
The doc comment TCacheValue|(\Closure(): TCacheValue) at position 3 could not be parsed: Expected ')' at position 3, but found '\Closure'.
Loading history...
553
     * @return (TCacheValue is null ? mixed : TCacheValue)
0 ignored issues
show
Documentation Bug introduced by
The doc comment (TCacheValue at position 1 could not be parsed: Expected ')' at position 1, but found 'TCacheValue'.
Loading history...
554
     */
555
    public function pullMany(iterable $keys, $default = null)
556
    {
557
        return Helpers::tap($this->readMany($keys, $default), function () use ($keys) {
558
            $this->deleteMany($keys);
559
        });
560
    }
561
}
562