├── .editorconfig
├── .github
└── workflows
│ └── quality-assurance.yaml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── phpcs.xml
├── phpunit.xml.dist
├── src
├── BasicCacheItemAccessorsTrait.php
├── BasicCacheItemTrait.php
├── BasicPoolTrait.php
├── CacheException.php
├── CachePoolDeferTrait.php
├── InvalidArgumentException.php
├── KeyValidatorTrait.php
└── Memory
│ ├── MemoryCacheItem.php
│ └── MemoryPool.php
├── test.php
└── test
├── CachePoolDeferTraitTest.php
├── KeyValidatorTest.php
└── MemoryPoolTest.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | # PHP should follow the PSR-2 standard (https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
13 | [*.php]
14 | indent_size = 4
15 |
--------------------------------------------------------------------------------
/.github/workflows/quality-assurance.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Quality Assurance
3 | on:
4 | push: ~
5 | pull_request: ~
6 |
7 | jobs:
8 | phpunit:
9 | name: PHPUnit tests on ${{ matrix.php }} ${{ matrix.composer-flags }}
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | php: [ '8.0', '8.1', '8.2', '8.3' ]
14 | composer-flags: [ '' ]
15 | phpunit-flags: [ '--coverage-text' ]
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: ${{ matrix.php }}
21 | coverage: xdebug
22 | tools: composer:v2
23 | - run: composer update --no-progress ${{ matrix.composer-flags }}
24 | - run: vendor/bin/phpunit ${{ matrix.phpunit-flags }}
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | composer.lock
3 | .phpunit.result.cache
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file, in reverse chronological order by release.
4 |
5 | ## 1.0.0 - 2016-08-06
6 |
7 | ### Fixed
8 |
9 | Initial stable release
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2015-2016 the PHP Framework Interoperability Group
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PSR-6 Cache Utilities
2 |
3 | 
4 |
5 | This package contains a series of traits and base classes to cover the common,
6 | boilerplate portions of implementing a PSR-6-compliant cache library.
7 |
8 | This package also provides a basic in-memory-only PSR-6 implementation. While
9 | not useful for actual use, it serves as a demo-implementation as well as a
10 | debugging tool.
11 |
12 | This package does not, and will not, provide a complete production-ready PSR-6
13 | implementation. If you are looking for a PSR-6 implementation, consult your
14 | friendly local Packagist.org server.
15 |
16 | ## License
17 |
18 | This package is released under the MIT license. See LICENSE for details.
19 |
20 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fig/cache-util",
3 | "description": "Useful utility classes and traits for implementing the PSR cache standard",
4 | "keywords": ["psr", "psr-6", "cache"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "PHP-FIG",
9 | "homepage": "http://www.php-fig.org/"
10 | },
11 | {
12 | "name": "Larry Garfield",
13 | "homepage": "http://www.garfieldtech.com/"
14 | }
15 | ],
16 | "require": {
17 | "php": ">=8.0.0",
18 | "psr/cache": "^2.0 | ^3.0"
19 | },
20 | "require-dev": {
21 | "phpunit/phpunit": "^9.5",
22 | "squizlabs/php_codesniffer": "^2.3.1"
23 | },
24 | "autoload": {
25 | "psr-4": {
26 | "Fig\\Cache\\": "src/"
27 | }
28 | },
29 | "autoload-dev": {
30 | "psr-4": {
31 | "Fig\\Cache\\Test\\": "test/"
32 | }
33 | },
34 | "extra": {
35 | "branch-alias": {
36 | "dev-master": "2.0.x-dev"
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | PSR-2 coding standards
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | src
20 | test
21 |
22 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ./test
5 |
6 |
7 |
8 |
9 |
10 | src
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/BasicCacheItemAccessorsTrait.php:
--------------------------------------------------------------------------------
1 | expiration ?? new \DateTime('now +1 year');
30 | }
31 |
32 | /**
33 | * Returns the raw value, regardless of hit status.
34 | *
35 | * Although not part of the CacheItemInterface, this method is used by
36 | * the pool for extracting information for saving.
37 | *
38 | * @return mixed
39 | *
40 | * @internal
41 | */
42 | public function getRawValue(): mixed
43 | {
44 | return $this->value;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/BasicCacheItemTrait.php:
--------------------------------------------------------------------------------
1 | key;
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function get(): mixed
30 | {
31 | return $this->isHit() ? $this->value : null;
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function set($value = null): static
38 | {
39 | $this->value = $value;
40 | return $this;
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function isHit(): bool
47 | {
48 | return $this->hit;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function expiresAt(?\DateTimeInterface $expiration): static
55 | {
56 | $this->expiration = $expiration ?? new \DateTimeImmutable('now +1 year');
57 |
58 | return $this;
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | public function expiresAfter(int|\DateInterval|null $time): static
65 | {
66 | $this->expiration = match(true) {
67 | is_null($time) => new \DateTimeImmutable('now +1 year'),
68 | is_int($time) => new \DateTimeImmutable('now +' . $time . ' seconds'),
69 | $time instanceof \DateInterval => (new \DateTimeImmutable())->add($time),
70 | };
71 | return $this;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/BasicPoolTrait.php:
--------------------------------------------------------------------------------
1 | deleteItems([$key]);
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | abstract public function deleteItems(array $items): bool;
25 | }
26 |
--------------------------------------------------------------------------------
/src/CacheException.php:
--------------------------------------------------------------------------------
1 | write([$item]);
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | public function saveDeferred(CacheItemInterface $item): bool
32 | {
33 | $this->deferred[] = $item;
34 | return true;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function commit(): bool
41 | {
42 | $success = $this->write($this->deferred);
43 | if ($success) {
44 | $this->deferred = [];
45 | }
46 | return $success;
47 | }
48 |
49 | /**
50 | * Commits the specified cache items to storage.
51 | *
52 | * @param CacheItemInterface[] $items
53 | *
54 | * @return bool
55 | * TRUE if all provided items were successfully saved. FALSE otherwise.
56 | */
57 | abstract protected function write(array $items): bool;
58 | }
59 |
--------------------------------------------------------------------------------
/src/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 | getReservedKeyCharacters()) . ']#', $key);
41 | if ($unsupportedMatched > 0) {
42 | throw new InvalidArgumentException('Can\'t validate the specified key');
43 | }
44 |
45 | return true;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Memory/MemoryCacheItem.php:
--------------------------------------------------------------------------------
1 | key = $key;
28 | $this->value = $data['value'];
29 | $this->expiration = $data['ttd'];
30 | $this->hit = $data['hit'];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Memory/MemoryPool.php:
--------------------------------------------------------------------------------
1 | validateKey($key);
35 |
36 | if (!$this->hasItem($key)) {
37 | $this->data[$key] = $this->emptyItem();
38 | }
39 |
40 | return new MemoryCacheItem($key, $this->data[$key]);
41 | }
42 |
43 | /**
44 | * Returns an empty item definition.
45 | *
46 | * @return array
47 | */
48 | protected function emptyItem(): array
49 | {
50 | return [
51 | 'value' => null,
52 | 'hit' => false,
53 | 'ttd' => null,
54 | ];
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function getItems(array $keys = []): iterable
61 | {
62 | // This method will throw an appropriate exception if any key is not valid.
63 | array_map([$this, 'validateKey'], $keys);
64 |
65 | $collection = [];
66 | foreach ($keys as $key) {
67 | $collection[$key] = $this->getItem($key);
68 | }
69 | return $collection;
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | public function clear(): bool
76 | {
77 | $this->data = [];
78 | return true;
79 | }
80 |
81 | /**
82 | * {@inheritdoc}
83 | */
84 | public function deleteItems(array $keys): bool
85 | {
86 | foreach ($keys as $key) {
87 | unset($this->data[$key]);
88 | }
89 | return true;
90 | }
91 |
92 | /**
93 | * {@inheritdoc}
94 | */
95 | public function hasItem($key): bool
96 | {
97 | return array_key_exists($key, $this->data) && $this->data[$key]['ttd'] > new \DateTimeImmutable();
98 | }
99 |
100 | /**
101 | * {@inheritdoc}
102 | */
103 | protected function write(array $items): bool
104 | {
105 | /** @var \Psr\Cache\CacheItemInterface $item */
106 | foreach ($items as $item) {
107 | $this->data[$item->getKey()] = [
108 | // Assumes use of the BasicCacheItemAccessorsTrait.
109 | 'value' => $item->getRawValue(),
110 | 'ttd' => $item->getExpiration(),
111 | 'hit' => true,
112 | ];
113 | }
114 |
115 | return true;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/test.php:
--------------------------------------------------------------------------------
1 | getItem('foo');
16 | $item->set('foo value', '300');
17 | $pool->save($item);
18 | $item = $pool->getItem('bar');
19 | $item->set('bar value', new \DateTime('now + 5min'));
20 | $pool->save($item);
21 |
22 | foreach ($pool->getItems(['foo', 'bar']) as $key => $item) {
23 | if ($key == 'foo') {
24 | assert($item->get() == 'foo value');
25 | }
26 | if ($key == 'bar') {
27 | assert($item->get() == 'bar value');
28 | }
29 | }
30 |
31 | // Update an existing item.
32 | $items = $pool->getItems(['foo', 'bar']);
33 | $items['bar']->set('new bar value');
34 | array_map([$pool, 'save'], $items);
35 |
36 | foreach ($pool->getItems(['foo', 'bar']) as $item) {
37 | if ($item->getKey() == 'foo') {
38 | assert($item->get() == 'foo value');
39 | }
40 | if ($item->getKey() == 'bar') {
41 | assert($item->get() == 'new bar value');
42 | }
43 | }
44 |
45 | // Defer saving to a later operation.
46 | $item = $pool->getItem('baz')->set('baz value', '100');
47 | $pool->saveDeferred($item);
48 | $item = $pool->getItem('foo')->set('new foo value', new \DateTime('now + 1min'));
49 | $pool->saveDeferred($item);
50 | $pool->commit();
51 |
52 | $items = $pool->getItems(['foo', 'bar', 'baz']);
53 | assert($items['foo']->get() == 'new foo value');
54 | assert($items['bar']->get() == 'new bar value');
55 | assert($items['baz']->get() == 'baz value');
56 |
--------------------------------------------------------------------------------
/test/CachePoolDeferTraitTest.php:
--------------------------------------------------------------------------------
1 | traitStub = $this->getMockForTrait(CachePoolDeferTrait::class);
17 | $this->itemStub = $this->createMock(CacheItemInterface::class);
18 | }
19 |
20 | public function testSaveSuccess(): void
21 | {
22 | $this->traitStub->expects(static::once())
23 | ->method('write')
24 | ->with(static::equalTo([$this->itemStub]))
25 | ->willReturn(true);
26 | static::assertTrue($this->traitStub->save($this->itemStub));
27 | }
28 |
29 | public function testSaveFail(): void
30 | {
31 | $this->traitStub->expects(static::once())
32 | ->method('write')
33 | ->willReturn(false);
34 | static::assertFalse($this->traitStub->save($this->itemStub));
35 | }
36 |
37 | public function testSaveDeferred(): void
38 | {
39 | static::assertTrue($this->traitStub->saveDeferred($this->itemStub));
40 | }
41 |
42 | public function testCommitSuccess(): void
43 | {
44 | $otherItem = clone $this->itemStub;
45 | $this->traitStub->expects(static::once())
46 | ->method('write')
47 | ->with(static::equalTo([$this->itemStub, $otherItem]))
48 | ->willReturn(true);
49 |
50 | $this->traitStub->saveDeferred($this->itemStub);
51 | $this->traitStub->saveDeferred($otherItem);
52 | static::assertTrue($this->traitStub->commit());
53 | }
54 |
55 | public function testCommitFail(): void
56 | {
57 | $this->traitStub->expects(static::once())
58 | ->method('write')
59 | ->willReturn(false);
60 |
61 | $this->traitStub->saveDeferred($this->itemStub);
62 | static::assertFalse($this->traitStub->commit());
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/test/KeyValidatorTest.php:
--------------------------------------------------------------------------------
1 | pool = new MemoryPool();
18 | }
19 |
20 | /**
21 | * Verifies key's name in positive cases.
22 | *
23 | * @param string $key
24 | * The key's name.
25 | *
26 | * @dataProvider providerValidKeyNames
27 | */
28 | public function testPositiveValidateKey($key)
29 | {
30 | static::assertInstanceOf(MemoryCacheItem::class, $this->pool->getItem($key));
31 | }
32 |
33 | /**
34 | * Provides a set of valid test key names.
35 | *
36 | * @return array
37 | */
38 | public function providerValidKeyNames(): array
39 | {
40 | return [
41 | ['bar'],
42 | ['barFoo1234567890'],
43 | ['bar_Foo.1'],
44 | ['1'],
45 | [str_repeat('a', 64)]
46 | ];
47 | }
48 |
49 | /**
50 | * Verifies key's name in negative cases.
51 | *
52 | * @param string $key
53 | * The key's name.
54 | *
55 | * @expectedException InvalidArgumentException
56 | * @dataProvider providerNotValidKeyNames
57 | */
58 | public function testNegativeValidateKey($key): void
59 | {
60 | $this->expectException(InvalidArgumentException::class);
61 | $this->pool->getItem($key);
62 | }
63 |
64 | /**
65 | * Provides a set of not valid test key names.
66 | *
67 | * @return array
68 | */
69 | public function providerNotValidKeyNames(): array
70 | {
71 | return [
72 | [null],
73 | [1],
74 | [''],
75 | ['bar{Foo'],
76 | ['bar}Foo'],
77 | ['bar(Foo'],
78 | ['bar)Foo'],
79 | ['bar/Foo'],
80 | ['bar\Foo'],
81 | ['bar@Foo'],
82 | ['bar:Foo']
83 | ];
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/test/MemoryPoolTest.php:
--------------------------------------------------------------------------------
1 | hasItem('foo'));
19 |
20 | $item = $pool->getItem('foo');
21 |
22 | static::assertNull($item->get());
23 | static::assertFalse($item->isHit());
24 | }
25 |
26 | /**
27 | * Verifies that primitive items can be added and retrieved from the pool.
28 | *
29 | * @param mixed $value
30 | * A value to try and cache.
31 | * @param string $type
32 | * The type of variable we expect to be cached.
33 | *
34 | * @dataProvider providerPrimitiveValues
35 | */
36 | public function testAddItem($value, $type): void
37 | {
38 | $pool = new MemoryPool();
39 |
40 | $item = $pool->getItem('foo');
41 | $item->set($value);
42 | $pool->save($item);
43 |
44 | $item = $pool->getItem('foo');
45 | static::assertEquals($value, $item->get());
46 | static::assertEquals($type, gettype($item->get()));
47 | }
48 |
49 | /**
50 | * Provides a set of test values for saving and retrieving.
51 | *
52 | * @return array
53 | */
54 | public function providerPrimitiveValues(): array
55 | {
56 | return [
57 | ['bar', 'string'],
58 | [1, 'integer'],
59 | [3.141592, 'double'],
60 | [['a', 'b', 'c'], 'array'],
61 | [['a' => 'A', 'b' => 'B', 'c' => 'C'], 'array'],
62 | ];
63 | }
64 |
65 | /**
66 | * Verifies that an item with an expiration time in the past won't be retrieved.
67 | *
68 | * @param mixed $value
69 | * A value to try and cache.
70 | *
71 | * @dataProvider providerPrimitiveValues
72 | */
73 | public function testExpiresAt($value): void
74 | {
75 | $pool = new MemoryPool();
76 |
77 | $item = $pool->getItem('foo');
78 | $item
79 | ->set($value)
80 | ->expiresAt(new \DateTimeImmutable('-1 minute'));
81 | $pool->save($item);
82 |
83 | $item = $pool->getItem('foo');
84 | static::assertNull($item->get());
85 | static::assertFalse($item->isHit());
86 | }
87 | }
88 |
--------------------------------------------------------------------------------