├── 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
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 |
--------------------------------------------------------------------------------