Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/.gitattributes export-ignore
/.github/ export-ignore
/.gitignore export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/phpunit.xml.legacy export-ignore
/tests export-ignore
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,26 @@ jobs:
if: ${{ matrix.php >= 7.3 }}
- run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy
if: ${{ matrix.php < 7.3 }}

PHPStan:
name: PHPStan (PHP ${{ matrix.php }})
runs-on: ubuntu-22.04
strategy:
matrix:
php:
- 8.3
- 8.2
- 8.1
- 8.0
- 7.4
- 7.3
- 7.2
- 7.1
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- run: composer install
- run: vendor/bin/phpstan
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Similarly, an expired cache item (once the time-to-live is expired) is
considered a cache miss.

```php
$cache->getMultiple(['name', 'age'])->then(function (array $values) {
$cache->getMultiple(['name', 'age'])->then(function (array $values): void {
$name = $values['name'] ?? 'User';
$age = $values['age'] ?? 'n/a';

Expand Down Expand Up @@ -369,6 +369,12 @@ To run the test suite, go to the project root and run:
vendor/bin/phpunit
```

On top of this, we use PHPStan on max level to ensure type safety across the project:

```bash
vendor/bin/phpstan
```

## License

MIT, see [LICENSE file](LICENSE).
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"react/promise": "^3.0"
},
"require-dev": {
"phpstan/phpstan": "1.11.1 || 1.4.10",
"phpunit/phpunit": "^9.6 || ^7.5"
},
"autoload": {
Expand Down
6 changes: 6 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parameters:
level: max

paths:
- src/
- tests/
8 changes: 8 additions & 0 deletions src/ArrayCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@

class ArrayCache implements CacheInterface
{
/** @var ?int */
private $limit;

/** @var array<string,mixed> */
private $data = [];

/** @var array<string,float> */
private $expires = [];

/** @var bool */
private $supportsHighResolution;

/**
Expand Down Expand Up @@ -124,6 +131,7 @@ public function getMultiple(array $keys, $default = null): PromiseInterface
$values[$key] = $this->get($key, $default);
}

/** @var PromiseInterface<array<string, mixed>> */
return all($values);
}

Expand Down
8 changes: 4 additions & 4 deletions src/CacheInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function delete(string $key): PromiseInterface;
* considered a cache miss.
*
* ```php
* $cache->getMultiple(['name', 'age'])->then(function (array $values) {
* $cache->getMultiple(['name', 'age'])->then(function (array $values): void {
* $name = $values['name'] ?? 'User';
* $age = $values['age'] ?? 'n/a';
*
Expand All @@ -120,7 +120,7 @@ public function delete(string $key): PromiseInterface;
*
* @param string[] $keys A list of keys that can obtained in a single operation.
* @param mixed $default Default value to return for keys that do not exist.
* @return PromiseInterface<array> Returns a promise which resolves to an `array` of cached values
* @return PromiseInterface<array<string,mixed>> Returns a promise which resolves to an `array` of cached values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we considered making the cache typeable? (Out of scope for this PR of course.)

Suggested change
* @return PromiseInterface<array<string,mixed>> Returns a promise which resolves to an `array` of cached values
* @return PromiseInterface<array<string,T>> Returns a promise which resolves to an `array` of cached values

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how useful this is, but happy to discuss in a separate PR if this is something you want to look into 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will do a PR once this is in together with one for DNS to show that off after doing some more research 👍 .

*/
public function getMultiple(array $keys, $default = null): PromiseInterface;

Expand All @@ -144,8 +144,8 @@ public function getMultiple(array $keys, $default = null): PromiseInterface;
* This example eventually sets the list of values - the key `foo` to 1 value
* and the key `bar` to 2. If some of the keys already exist, they are overridden.
*
* @param array $values A list of key => value pairs for a multiple-set operation.
* @param ?float $ttl Optional. The TTL value of this item.
* @param array<string,mixed> $values A list of key => value pairs for a multiple-set operation.
* @param ?float $ttl Optional. The TTL value of this item.
* @return PromiseInterface<bool> Returns a promise which resolves to `true` on success or `false` on error
*/
public function setMultiple(array $values, ?float $ttl = null): PromiseInterface;
Expand Down
105 changes: 31 additions & 74 deletions tests/ArrayCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,101 +14,58 @@ class ArrayCacheTest extends TestCase
/**
* @before
*/
public function setUpArrayCache()
public function setUpArrayCache(): void
{
$this->cache = new ArrayCache();
}

/** @test */
public function getShouldResolvePromiseWithNullForNonExistentKey()
public function getShouldResolvePromiseWithNullForNonExistentKey(): void
{
$success = $this->createCallableMock();
$success
->expects($this->once())
->method('__invoke')
->with(null);

$this->cache
->get('foo')
->then(
$success,
$this->expectCallableNever()
);
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
}

/** @test */
public function setShouldSetKey()
public function setShouldSetKey(): void
{
$setPromise = $this->cache
->set('foo', 'bar');

$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(true));
$this->cache->set('foo', 'bar')->then($this->expectCallableOnceWith(true));

$setPromise->then($mock);

$success = $this->createCallableMock();
$success
->expects($this->once())
->method('__invoke')
->with('bar');

$this->cache
->get('foo')
->then($success);
$this->cache->get('foo')->then($this->expectCallableOnceWith('bar'));
}

/** @test */
public function deleteShouldDeleteKey()
public function deleteShouldDeleteKey(): void
{
$this->cache
->set('foo', 'bar');

$deletePromise = $this->cache
->delete('foo');

$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(true));
$this->cache->set('foo', 'bar');

$deletePromise->then($mock);
$this->cache->delete('foo')->then($this->expectCallableOnceWith(true));

$this->cache
->get('foo')
->then(
$this->expectCallableOnce(),
$this->expectCallableNever()
);
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
}

public function testGetWillResolveWithNullForCacheMiss()
public function testGetWillResolveWithNullForCacheMiss(): void
{
$this->cache = new ArrayCache();

$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
}

public function testGetWillResolveWithDefaultValueForCacheMiss()
public function testGetWillResolveWithDefaultValueForCacheMiss(): void
{
$this->cache = new ArrayCache();

$this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith('bar'));
}

public function testGetWillResolveWithExplicitNullValueForCacheHit()
public function testGetWillResolveWithExplicitNullValueForCacheHit(): void
{
$this->cache = new ArrayCache();

$this->cache->set('foo', null);
$this->cache->get('foo', 'bar')->then($this->expectCallableOnceWith(null));
}

public function testLimitSizeToZeroDoesNotStoreAnyData()
public function testLimitSizeToZeroDoesNotStoreAnyData(): void
{
$this->cache = new ArrayCache(0);

Expand All @@ -117,7 +74,7 @@ public function testLimitSizeToZeroDoesNotStoreAnyData()
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
}

public function testLimitSizeToOneWillOnlyReturnLastWrite()
public function testLimitSizeToOneWillOnlyReturnLastWrite(): void
{
$this->cache = new ArrayCache(1);

Expand All @@ -128,7 +85,7 @@ public function testLimitSizeToOneWillOnlyReturnLastWrite()
$this->cache->get('bar')->then($this->expectCallableOnceWith('2'));
}

public function testOverwriteWithLimitedSizeWillUpdateLRUInfo()
public function testOverwriteWithLimitedSizeWillUpdateLRUInfo(): void
{
$this->cache = new ArrayCache(2);

Expand All @@ -142,7 +99,7 @@ public function testOverwriteWithLimitedSizeWillUpdateLRUInfo()
$this->cache->get('baz')->then($this->expectCallableOnceWith('4'));
}

public function testGetWithLimitedSizeWillUpdateLRUInfo()
public function testGetWithLimitedSizeWillUpdateLRUInfo(): void
{
$this->cache = new ArrayCache(2);

Expand All @@ -156,7 +113,7 @@ public function testGetWithLimitedSizeWillUpdateLRUInfo()
$this->cache->get('baz')->then($this->expectCallableOnceWith('3'));
}

public function testGetWillResolveWithValueIfItemIsNotExpired()
public function testGetWillResolveWithValueIfItemIsNotExpired(): void
{
$this->cache = new ArrayCache();

Expand All @@ -165,7 +122,7 @@ public function testGetWillResolveWithValueIfItemIsNotExpired()
$this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
}

public function testGetWillResolveWithDefaultIfItemIsExpired()
public function testGetWillResolveWithDefaultIfItemIsExpired(): void
{
$this->cache = new ArrayCache();

Expand All @@ -174,7 +131,7 @@ public function testGetWillResolveWithDefaultIfItemIsExpired()
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
}

public function testSetWillOverwritOldestItemIfNoEntryIsExpired()
public function testSetWillOverwritOldestItemIfNoEntryIsExpired(): void
{
$this->cache = new ArrayCache(2);

Expand All @@ -185,7 +142,7 @@ public function testSetWillOverwritOldestItemIfNoEntryIsExpired()
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
}

public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired()
public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired(): void
{
$this->cache = new ArrayCache(2);

Expand All @@ -197,7 +154,7 @@ public function testSetWillOverwriteExpiredItemIfAnyEntryIsExpired()
$this->cache->get('bar')->then($this->expectCallableOnceWith(null));
}

public function testGetMultiple()
public function testGetMultiple(): void
{
$this->cache = new ArrayCache();
$this->cache->set('foo', '1');
Expand All @@ -207,7 +164,7 @@ public function testGetMultiple()
->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => 'baz']));
}

public function testSetMultiple()
public function testSetMultiple(): void
{
$this->cache = new ArrayCache();
$this->cache->setMultiple(['foo' => '1', 'bar' => '2'], 10);
Expand All @@ -217,7 +174,7 @@ public function testSetMultiple()
->then($this->expectCallableOnceWith(['foo' => '1', 'bar' => '2']));
}

public function testDeleteMultiple()
public function testDeleteMultiple(): void
{
$this->cache = new ArrayCache();
$this->cache->setMultiple(['foo' => 1, 'bar' => 2, 'baz' => 3]);
Expand All @@ -239,7 +196,7 @@ public function testDeleteMultiple()
->then($this->expectCallableOnceWith(false));
}

public function testClearShouldClearCache()
public function testClearShouldClearCache(): void
{
$this->cache = new ArrayCache();
$this->cache->setMultiple(['foo' => 1, 'bar' => 2, 'baz' => 3]);
Expand All @@ -259,7 +216,7 @@ public function testClearShouldClearCache()
->then($this->expectCallableOnceWith(false));
}

public function hasShouldResolvePromiseForExistingKey()
public function hasShouldResolvePromiseForExistingKey(): void
{
$this->cache = new ArrayCache();
$this->cache->set('foo', 'bar');
Expand All @@ -269,7 +226,7 @@ public function hasShouldResolvePromiseForExistingKey()
->then($this->expectCallableOnceWith(true));
}

public function hasShouldResolvePromiseForNonExistentKey()
public function hasShouldResolvePromiseForNonExistentKey(): void
{
$this->cache = new ArrayCache();
$this->cache->set('foo', 'bar');
Expand All @@ -279,7 +236,7 @@ public function hasShouldResolvePromiseForNonExistentKey()
->then($this->expectCallableOnceWith(false));
}

public function testHasWillResolveIfItemIsNotExpired()
public function testHasWillResolveIfItemIsNotExpired(): void
{
$this->cache = new ArrayCache();
$this->cache->set('foo', '1', 10);
Expand All @@ -289,7 +246,7 @@ public function testHasWillResolveIfItemIsNotExpired()
->then($this->expectCallableOnceWith(true));
}

public function testHasWillResolveIfItemIsExpired()
public function testHasWillResolveIfItemIsExpired(): void
{
$this->cache = new ArrayCache();
$this->cache->set('foo', '1', 0);
Expand All @@ -299,7 +256,7 @@ public function testHasWillResolveIfItemIsExpired()
->then($this->expectCallableOnceWith(false));
}

public function testHasWillResolveForExplicitNullValue()
public function testHasWillResolveForExplicitNullValue(): void
{
$this->cache = new ArrayCache();
$this->cache->set('foo', null);
Expand All @@ -309,7 +266,7 @@ public function testHasWillResolveForExplicitNullValue()
->then($this->expectCallableOnceWith(true));
}

public function testHasWithLimitedSizeWillUpdateLRUInfo()
public function testHasWithLimitedSizeWillUpdateLRUInfo(): void
{
$this->cache = new ArrayCache(2);

Expand Down
Loading