├── CHANGELOG.md ├── CacheInterface.php ├── CacheTrait.php ├── CallbackInterface.php ├── ItemInterface.php ├── LICENSE ├── README.md ├── TagAwareCacheInterface.php └── composer.json /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | The changelog is maintained for all Symfony contracts at the following URL: 5 | https://github.com/symfony/contracts/blob/main/CHANGELOG.md 6 | -------------------------------------------------------------------------------- /CacheInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Contracts\Cache; 13 | 14 | use Psr\Cache\CacheItemInterface; 15 | use Psr\Cache\InvalidArgumentException; 16 | 17 | /** 18 | * Covers most simple to advanced caching needs. 19 | * 20 | * @author Nicolas Grekas 21 | */ 22 | interface CacheInterface 23 | { 24 | /** 25 | * Fetches a value from the pool or computes it if not found. 26 | * 27 | * On cache misses, a callback is called that should return the missing value. 28 | * This callback is given a PSR-6 CacheItemInterface instance corresponding to the 29 | * requested key, that could be used e.g. for expiration control. It could also 30 | * be an ItemInterface instance when its additional features are needed. 31 | * 32 | * @template T 33 | * 34 | * @param string $key The key of the item to retrieve from the cache 35 | * @param (callable(CacheItemInterface,bool):T)|(callable(ItemInterface,bool):T)|CallbackInterface $callback 36 | * @param float|null $beta A float that, as it grows, controls the likeliness of triggering 37 | * early expiration. 0 disables it, INF forces immediate expiration. 38 | * The default (or providing null) is implementation dependent but should 39 | * typically be 1.0, which should provide optimal stampede protection. 40 | * See https://en.wikipedia.org/wiki/Cache_stampede#Probabilistic_early_expiration 41 | * @param array &$metadata The metadata of the cached item {@see ItemInterface::getMetadata()} 42 | * 43 | * @return T 44 | * 45 | * @throws InvalidArgumentException When $key is not valid or when $beta is negative 46 | */ 47 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed; 48 | 49 | /** 50 | * Removes an item from the pool. 51 | * 52 | * @param string $key The key to delete 53 | * 54 | * @return bool True if the item was successfully removed, false if there was any error 55 | * 56 | * @throws InvalidArgumentException When $key is not valid 57 | */ 58 | public function delete(string $key): bool; 59 | } 60 | -------------------------------------------------------------------------------- /CacheTrait.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Contracts\Cache; 13 | 14 | use Psr\Cache\CacheItemPoolInterface; 15 | use Psr\Cache\InvalidArgumentException; 16 | use Psr\Log\LoggerInterface; 17 | 18 | // Help opcache.preload discover always-needed symbols 19 | class_exists(InvalidArgumentException::class); 20 | 21 | /** 22 | * An implementation of CacheInterface for PSR-6 CacheItemPoolInterface classes. 23 | * 24 | * @author Nicolas Grekas 25 | */ 26 | trait CacheTrait 27 | { 28 | public function get(string $key, callable $callback, ?float $beta = null, ?array &$metadata = null): mixed 29 | { 30 | return $this->doGet($this, $key, $callback, $beta, $metadata); 31 | } 32 | 33 | public function delete(string $key): bool 34 | { 35 | return $this->deleteItem($key); 36 | } 37 | 38 | private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback, ?float $beta, ?array &$metadata = null, ?LoggerInterface $logger = null): mixed 39 | { 40 | if (0 > $beta ??= 1.0) { 41 | throw new class(\sprintf('Argument "$beta" provided to "%s::get()" must be a positive number, %f given.', static::class, $beta)) extends \InvalidArgumentException implements InvalidArgumentException {}; 42 | } 43 | 44 | $item = $pool->getItem($key); 45 | $recompute = !$item->isHit() || \INF === $beta; 46 | $metadata = $item instanceof ItemInterface ? $item->getMetadata() : []; 47 | 48 | if (!$recompute && $metadata) { 49 | $expiry = $metadata[ItemInterface::METADATA_EXPIRY] ?? false; 50 | $ctime = $metadata[ItemInterface::METADATA_CTIME] ?? false; 51 | 52 | if ($recompute = $ctime && $expiry && $expiry <= ($now = microtime(true)) - $ctime / 1000 * $beta * log(random_int(1, \PHP_INT_MAX) / \PHP_INT_MAX)) { 53 | // force applying defaultLifetime to expiry 54 | $item->expiresAt(null); 55 | $logger?->info('Item "{key}" elected for early recomputation {delta}s before its expiration', [ 56 | 'key' => $key, 57 | 'delta' => \sprintf('%.1f', $expiry - $now), 58 | ]); 59 | } 60 | } 61 | 62 | if ($recompute) { 63 | $save = true; 64 | $item->set($callback($item, $save)); 65 | if ($save) { 66 | $pool->save($item); 67 | } 68 | } 69 | 70 | return $item->get(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CallbackInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Contracts\Cache; 13 | 14 | use Psr\Cache\CacheItemInterface; 15 | 16 | /** 17 | * Computes and returns the cached value of an item. 18 | * 19 | * @author Nicolas Grekas 20 | * 21 | * @template T 22 | */ 23 | interface CallbackInterface 24 | { 25 | /** 26 | * @param CacheItemInterface|ItemInterface $item The item to compute the value for 27 | * @param bool &$save Should be set to false when the value should not be saved in the pool 28 | * 29 | * @return T The computed value for the passed item 30 | */ 31 | public function __invoke(CacheItemInterface $item, bool &$save): mixed; 32 | } 33 | -------------------------------------------------------------------------------- /ItemInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Contracts\Cache; 13 | 14 | use Psr\Cache\CacheException; 15 | use Psr\Cache\CacheItemInterface; 16 | use Psr\Cache\InvalidArgumentException; 17 | 18 | /** 19 | * Augments PSR-6's CacheItemInterface with support for tags and metadata. 20 | * 21 | * @author Nicolas Grekas 22 | */ 23 | interface ItemInterface extends CacheItemInterface 24 | { 25 | /** 26 | * References the Unix timestamp stating when the item will expire. 27 | */ 28 | public const METADATA_EXPIRY = 'expiry'; 29 | 30 | /** 31 | * References the time the item took to be created, in milliseconds. 32 | */ 33 | public const METADATA_CTIME = 'ctime'; 34 | 35 | /** 36 | * References the list of tags that were assigned to the item, as string[]. 37 | */ 38 | public const METADATA_TAGS = 'tags'; 39 | 40 | /** 41 | * Reserved characters that cannot be used in a key or tag. 42 | */ 43 | public const RESERVED_CHARACTERS = '{}()/\@:'; 44 | 45 | /** 46 | * Adds a tag to a cache item. 47 | * 48 | * Tags are strings that follow the same validation rules as keys. 49 | * 50 | * @param string|string[] $tags A tag or array of tags 51 | * 52 | * @return $this 53 | * 54 | * @throws InvalidArgumentException When $tag is not valid 55 | * @throws CacheException When the item comes from a pool that is not tag-aware 56 | */ 57 | public function tag(string|iterable $tags): static; 58 | 59 | /** 60 | * Returns a list of metadata info that were saved alongside with the cached value. 61 | * 62 | * See ItemInterface::METADATA_* consts for keys potentially found in the returned array. 63 | */ 64 | public function getMetadata(): array; 65 | } 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Symfony Cache Contracts 2 | ======================= 3 | 4 | A set of abstractions extracted out of the Symfony components. 5 | 6 | Can be used to build on semantics that the Symfony components proved useful and 7 | that already have battle tested implementations. 8 | 9 | See https://github.com/symfony/contracts/blob/main/README.md for more information. 10 | -------------------------------------------------------------------------------- /TagAwareCacheInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Contracts\Cache; 13 | 14 | use Psr\Cache\InvalidArgumentException; 15 | 16 | /** 17 | * Allows invalidating cached items using tags. 18 | * 19 | * @author Nicolas Grekas 20 | */ 21 | interface TagAwareCacheInterface extends CacheInterface 22 | { 23 | /** 24 | * Invalidates cached items using tags. 25 | * 26 | * When implemented on a PSR-6 pool, invalidation should not apply 27 | * to deferred items. Instead, they should be committed as usual. 28 | * This allows replacing old tagged values by new ones without 29 | * race conditions. 30 | * 31 | * @param string[] $tags An array of tags to invalidate 32 | * 33 | * @return bool True on success 34 | * 35 | * @throws InvalidArgumentException When $tags is not valid 36 | */ 37 | public function invalidateTags(array $tags): bool; 38 | } 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/cache-contracts", 3 | "type": "library", 4 | "description": "Generic abstractions related to caching", 5 | "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Nicolas Grekas", 11 | "email": "p@tchwork.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=8.1", 20 | "psr/cache": "^3.0" 21 | }, 22 | "autoload": { 23 | "psr-4": { "Symfony\\Contracts\\Cache\\": "" } 24 | }, 25 | "minimum-stability": "dev", 26 | "extra": { 27 | "branch-alias": { 28 | "dev-main": "3.6-dev" 29 | }, 30 | "thanks": { 31 | "name": "symfony/contracts", 32 | "url": "https://github.com/symfony/contracts" 33 | } 34 | } 35 | } 36 | --------------------------------------------------------------------------------