From c024521f4dbb10633b1bd079c9a767f4828739e4 Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Tue, 19 May 2026 08:29:54 +0530 Subject: [PATCH 1/6] Add SimpleCacheV3Test for PSR-16 v3 strict type hints (#121) --- README.md | 12 ++ composer.json | 1 + src/SimpleCacheV3Test.php | 328 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 src/SimpleCacheV3Test.php diff --git a/README.md b/README.md index d8c4901..af6f0f3 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,18 @@ class CacheIntegrationTest extends SimpleCacheTest } ``` +If your implementation uses **psr/simple-cache ^3.0** (with strict PHP type hints), extend `SimpleCacheV3Test` instead. It provides the same test coverage with data providers adapted for v3's typed interface: + +```php +class CacheV3IntegrationTest extends SimpleCacheV3Test +{ + public function createSimpleCache() + { + return new SimpleCache(); + } +} +``` + ### Contribute Contributions are very welcome! Send a pull request or diff --git a/composer.json b/composer.json index f223b78..874f5fb 100755 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "php": ">=5.5.9" }, "require-dev": { + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "cache/cache": "^1.0", "symfony/cache": "^3.4.31|^4.3.4|^5.0", "symfony/phpunit-bridge": "^5.1,<5.3", diff --git a/src/SimpleCacheV3Test.php b/src/SimpleCacheV3Test.php new file mode 100644 index 0000000..f371a33 --- /dev/null +++ b/src/SimpleCacheV3Test.php @@ -0,0 +1,328 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\IntegrationTests; + +use PHPUnit\Framework\Attributes\DataProvider; + +/** + * PSR-16 integration test suite for implementations using psr/simple-cache ^3.0. + * + * PSR-16 v3 introduced strict PHP type hints (string $key, iterable $keys, + * null|int|DateInterval $ttl). These type hints cause PHP to throw \TypeError + * before the library code ever runs when clearly-invalid argument types are + * passed. This makes many of the original SimpleCacheTest data providers + * incompatible with v3. + * + * This subclass: + * - Filters data providers to only values that survive strict/coercion checks + * - Accepts both \TypeError (PHP-level rejection) and + * \Psr\SimpleCache\InvalidArgumentException (library-level validation) as + * valid failure modes + * - Is fully backward-compatible: existing v1/v2 consumers keep using + * SimpleCacheTest; v3 consumers switch to SimpleCacheV3Test + * + * @see \Cache\IntegrationTests\SimpleCacheTest + */ +abstract class SimpleCacheV3Test extends SimpleCacheTest +{ + /** + * Data provider for invalid array keys in setMultiple. + * + * Contains only string keys that are invalid per PSR-16 spec. + * Non-string key types (bool, null, float, int, object, array) are removed + * because PSR-16 v3's string type hint rejects them at PHP level before the + * implementation is invoked. + * + * @return list> + */ + public static function invalidArrayKeys() + { + return [ + [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand/str'], + ['rand\\str'], + ['rand@str'], + ['rand:str'], + ]; + } + + /** + * Data provider for invalid cache keys. + * + * Keeps the parent's structure (array_merge around invalidArrayKeys) but + * adds no extra items, since the parent's extra `[2]` is an integer and + * PSR-16 v3's string type hint rejects it at PHP level. + * + * @return list> + */ + public static function invalidKeys() + { + return array_merge( + self::invalidArrayKeys(), + [] + ); + } + + /** + * Data provider for invalid TTL values. + * + * PSR-16 v3 accepts only null|int|DateInterval for TTL. Values that coerce + * to valid types in PHP weak mode (e.g. true→1, false→0, ' 1'→1, '025'→25) + * are removed because they would not exercise invalid-TTL handling. + * + * @return list> + */ + public static function invalidTtl() + { + return [ + [''], + ['abc'], + ['12foo'], + [new \stdClass()], + [['array']], + ]; + } + + /** + * Helper that runs the supplied callable inside a try/catch. + * + * With PSR-16 v3 strict type hints a non-string key, non-iterable iterable, + * or invalid TTL will cause a \TypeError before the library sees the data. + * The PSR-16 spec says a key MUST be a string, so that is consistent. + * When the implementation is also typed we accept TypeError as a valid + * failure indicator for clearly-invalid arguments. + * + * @param callable(): void $callable + */ + private function assertCacheExceptionOrTypeError(callable $callable): void + { + try { + $callable(); + $this->fail('Expected exception to be thrown.'); + } catch (\TypeError $e) { + // PSR-16 v3 typed interfaces throw TypeError for clearly-invalid + // argument types (e.g. null key, object key, string for iterable). + $this->assertTrue(true); + } catch (\Psr\SimpleCache\InvalidArgumentException $e) { + // Explicit library-level validation. + $this->assertTrue(true); + } + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testGetInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->get($key); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testGetMultipleInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->getMultiple(['key1', $key, 'key2']); + }); + } + + public function testGetMultipleNoIterable() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->getMultiple('key'); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testSetInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->set($key, 'foobar'); + }); + } + + /** + * @dataProvider invalidArrayKeys + */ + #[DataProvider('invalidArrayKeys')] + public function testSetMultipleInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $values = function () use ($key) { + yield 'key1' => 'foo'; + yield $key => 'bar'; + yield 'key2' => 'baz'; + }; + $this->cache->setMultiple($values()); + }); + } + + public function testSetMultipleNoIterable() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->setMultiple('key'); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testHasInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->has($key); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testDeleteInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->delete($key); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testDeleteMultipleInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->deleteMultiple(['key1', $key, 'key2']); + }); + } + + public function testDeleteMultipleNoIterable() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->deleteMultiple('key'); + }); + } + + /** + * @dataProvider invalidTtl + */ + #[DataProvider('invalidTtl')] + public function testSetInvalidTtl($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->set('key', 'value', $ttl); + }); + } + + /** + * @dataProvider invalidTtl + */ + #[DataProvider('invalidTtl')] + public function testSetMultipleInvalidTtl($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->setMultiple(['key' => 'value'], $ttl); + }); + } + + /** + * Tests the PSR-16 mandated minimum key length of 64 characters. + * + * PSR-16 explicitly states: "The key length MUST be at least 64 characters." + * This test ensures every compliant implementation supports at least 64 chars. + * + * @see https://www.php-fig.org/psr/psr-16/ + */ + public function testBasicUsageWithLongKey64() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $key = str_repeat('a', 64); + + $this->assertFalse($this->cache->has($key)); + $this->assertTrue($this->cache->set($key, 'value')); + + $this->assertTrue($this->cache->has($key)); + $this->assertSame('value', $this->cache->get($key)); + + $this->assertTrue($this->cache->delete($key)); + + $this->assertFalse($this->cache->has($key)); + } +} From d88069881155e2cf2aea4349254929189c7290e1 Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Sat, 30 May 2026 11:38:29 +0530 Subject: [PATCH 2/6] Merged SimpleCacheTest and SimpleCachev3Test as sugggested by stof --- README.md | 12 -- composer.json | 3 +- src/SimpleCacheTest.php | 431 ++++++++++++++++++++++++++++++++++++-- src/SimpleCacheV3Test.php | 328 ----------------------------- 4 files changed, 411 insertions(+), 363 deletions(-) delete mode 100644 src/SimpleCacheV3Test.php diff --git a/README.md b/README.md index af6f0f3..d8c4901 100644 --- a/README.md +++ b/README.md @@ -52,18 +52,6 @@ class CacheIntegrationTest extends SimpleCacheTest } ``` -If your implementation uses **psr/simple-cache ^3.0** (with strict PHP type hints), extend `SimpleCacheV3Test` instead. It provides the same test coverage with data providers adapted for v3's typed interface: - -```php -class CacheV3IntegrationTest extends SimpleCacheV3Test -{ - public function createSimpleCache() - { - return new SimpleCache(); - } -} -``` - ### Contribute Contributions are very welcome! Send a pull request or diff --git a/composer.json b/composer.json index 874f5fb..02d1994 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ } ], "require": { - "php": ">=5.5.9" + "php": ">=5.5.9", + "composer-runtime-api": "^2.0" }, "require-dev": { "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", diff --git a/src/SimpleCacheTest.php b/src/SimpleCacheTest.php index 0165b26..99bde35 100644 --- a/src/SimpleCacheTest.php +++ b/src/SimpleCacheTest.php @@ -1,5 +1,7 @@ > */ public static function invalidKeys() { - return array_merge( - self::invalidArrayKeys(), - [ - [2], - ] - ); + if (self::isPsr16V1()) { + return array_merge( + self::invalidArrayKeys(), + [ + [2], + ] + ); + } + + // v2/v3: only syntax violations survive PHP type hints + return [ + [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand/str'], + ['rand\\str'], + ['rand@str'], + ['rand:str'], + ]; } /** - * Data provider for invalid array keys. + * Data provider for invalid array keys (used in setMultiple). * - * @return array + * @return list> */ public static function invalidArrayKeys() { + if (self::isPsr16V1()) { + return [ + [''], + [true], + [false], + [null], + [2.5], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand/str'], + ['rand\\str'], + ['rand@str'], + ['rand:str'], + [new \stdClass()], + [['array']], + ]; + } + return [ [''], - [true], - [false], - [null], - [2.5], ['{str'], ['rand{'], ['rand{str'], @@ -108,34 +187,140 @@ public static function invalidArrayKeys() ['rand\\str'], ['rand@str'], ['rand:str'], + ]; + } + + /** + * Data provider for invalid TTL values. + * + * On v1, includes every invalid TTL type. + * On v2/v3, only values that survive PHP's null|int|\DateInterval hint + * but are still invalid per the PSR-16 spec remain. + * + * @return list> + */ + public static function invalidTtl() + { + $values = [ + [''], + ['abc'], [new \stdClass()], [['array']], ]; + + if (self::isPsr16V1()) { + // In v1 all of these reach the library and must throw + // InvalidArgumentException. + $values = array_merge( + $values, + [ + [true], + [false], + [2.5], + [' 1'], // can be casted to a int + ['12foo'], // can be casted to a int + ['025'], // can be interpreted as hex + ] + ); + } + + return $values; } /** - * @return array + * Data provider for keys whose *type* violates the interface contract. + * + * These values are only valid when psr/simple-cache ^2.0|^3.0 is installed, + * because v2/v3 introduced parameter type hints that reject them with a + * PHP \TypeError before the library code runs. + * + * @return list> */ - public static function invalidTtl() + public static function invalidKeysTypeViolation() { + if (self::isPsr16V1()) { + return []; + } + return [ - [''], [true], [false], - ['abc'], + [null], + [2.5], + [2], + [new \stdClass()], + [['array']], + ]; + } + + /** + * Data provider for array keys whose *type* violates the interface contract. + * + * Differs from invalidKeysTypeViolation: integer keys are excluded because + * PHP arrays/generators auto-cast integer keys to strings (e.g. yield 0 => 'v'), + * so an integer key inside an iterable is not a testable contract violation. + * + * @return list> + */ + public static function invalidArrayKeysTypeViolation() + { + if (self::isPsr16V1()) { + return []; + } + + return [ + [true], + [false], + [null], [2.5], - [' 1'], // can be casted to a int - ['12foo'], // can be casted to a int - ['025'], // can be interpreted as hex [new \stdClass()], [['array']], ]; } + /** + * Data provider for TTL values whose *type* violates the interface contract. + * + * @return list> + */ + public static function invalidTtlTypeViolation() + { + if (self::isPsr16V1()) { + return []; + } + + return [ + [true], + [false], + [2.5], + [' 1'], + ['12foo'], + ['025'], + ]; + } + + /** + * Accepts either \Psr\SimpleCache\InvalidArgumentException (library-level) or + * \TypeError (PHP-level contract violation) as a valid failure. + * + * @param callable(): void $callable + */ + private function assertCacheExceptionOrTypeError(callable $callable): void + { + try { + $callable(); + $this->fail('Expected exception to be thrown.'); + } catch (\TypeError $e) { + $this->assertTrue(true); + } catch (\Psr\SimpleCache\InvalidArgumentException $e) { + $this->assertTrue(true); + } + } + /** * Data provider for valid keys. * - * @return array + * @return list> */ public static function validKeys() { @@ -148,7 +333,7 @@ public static function validKeys() /** * Data provider for valid data to store. * - * @return array + * @return list> */ public static function validData() { @@ -460,6 +645,21 @@ public function testGetInvalidKeys($key) $this->cache->get($key); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testGetInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->get($key); + }); + } + /** * @dataProvider invalidKeys */ @@ -474,12 +674,34 @@ public function testGetMultipleInvalidKeys($key) $result = $this->cache->getMultiple(['key1', $key, 'key2']); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testGetMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->getMultiple(['key1', $key, 'key2']); + }); + } + public function testGetMultipleNoIterable() { if (isset($this->skippedTests[__FUNCTION__])) { $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->getMultiple('key'); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $result = $this->cache->getMultiple('key'); } @@ -498,6 +720,21 @@ public function testSetInvalidKeys($key) $this->cache->set($key, 'foobar'); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testSetInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->set($key, 'foobar'); + }); + } + /** * @dataProvider invalidArrayKeys */ @@ -517,12 +754,39 @@ public function testSetMultipleInvalidKeys($key) $this->cache->setMultiple($values()); } + /** + * @dataProvider invalidArrayKeysTypeViolation + */ + #[DataProvider('invalidArrayKeysTypeViolation')] + public function testSetMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $values = function () use ($key) { + yield 'key1' => 'foo'; + yield $key => 'bar'; + yield 'key2' => 'baz'; + }; + $this->cache->setMultiple($values()); + }); + } + public function testSetMultipleNoIterable() { if (isset($this->skippedTests[__FUNCTION__])) { $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->setMultiple('key'); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->setMultiple('key'); } @@ -541,6 +805,21 @@ public function testHasInvalidKeys($key) $this->cache->has($key); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testHasInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->has($key); + }); + } + /** * @dataProvider invalidKeys */ @@ -555,6 +834,21 @@ public function testDeleteInvalidKeys($key) $this->cache->delete($key); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testDeleteInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->delete($key); + }); + } + /** * @dataProvider invalidKeys */ @@ -569,12 +863,34 @@ public function testDeleteMultipleInvalidKeys($key) $this->cache->deleteMultiple(['key1', $key, 'key2']); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testDeleteMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->deleteMultiple(['key1', $key, 'key2']); + }); + } + public function testDeleteMultipleNoIterable() { if (isset($this->skippedTests[__FUNCTION__])) { $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->deleteMultiple('key'); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->deleteMultiple('key'); } @@ -589,10 +905,32 @@ public function testSetInvalidTtl($ttl) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->set('key', 'value', $ttl); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->set('key', 'value', $ttl); } + /** + * @dataProvider invalidTtlTypeViolation + */ + #[DataProvider('invalidTtlTypeViolation')] + public function testSetInvalidTtlTypeViolation($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->set('key', 'value', $ttl); + }); + } + /** * @dataProvider invalidTtl */ @@ -603,10 +941,32 @@ public function testSetMultipleInvalidTtl($ttl) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->setMultiple(['key' => 'value'], $ttl); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->setMultiple(['key' => 'value'], $ttl); } + /** + * @dataProvider invalidTtlTypeViolation + */ + #[DataProvider('invalidTtlTypeViolation')] + public function testSetMultipleInvalidTtlTypeViolation($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->setMultiple(['key' => 'value'], $ttl); + }); + } + public function testNullOverwrite() { if (isset($this->skippedTests[__FUNCTION__])) { @@ -807,4 +1167,31 @@ public function testObjectDoesNotChangeInCache() $cacheObject = $this->cache->get('key'); $this->assertEquals('value', $cacheObject->foo, 'Object in cache should not have their values changed.'); } + + /** + * Tests the PSR-16 mandated minimum key length of 64 characters. + * + * PSR-16 explicitly states: "The key length MUST be at least 64 characters." + * This test ensures every compliant implementation supports at least 64 chars. + * + * @see https://www.php-fig.org/psr/psr-16/ + */ + public function testBasicUsageWithLongKey64() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $key = str_repeat('a', 64); + + $this->assertFalse($this->cache->has($key)); + $this->assertTrue($this->cache->set($key, 'value')); + + $this->assertTrue($this->cache->has($key)); + $this->assertSame('value', $this->cache->get($key)); + + $this->assertTrue($this->cache->delete($key)); + + $this->assertFalse($this->cache->has($key)); + } } diff --git a/src/SimpleCacheV3Test.php b/src/SimpleCacheV3Test.php deleted file mode 100644 index f371a33..0000000 --- a/src/SimpleCacheV3Test.php +++ /dev/null @@ -1,328 +0,0 @@ -, Tobias Nyholm - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Cache\IntegrationTests; - -use PHPUnit\Framework\Attributes\DataProvider; - -/** - * PSR-16 integration test suite for implementations using psr/simple-cache ^3.0. - * - * PSR-16 v3 introduced strict PHP type hints (string $key, iterable $keys, - * null|int|DateInterval $ttl). These type hints cause PHP to throw \TypeError - * before the library code ever runs when clearly-invalid argument types are - * passed. This makes many of the original SimpleCacheTest data providers - * incompatible with v3. - * - * This subclass: - * - Filters data providers to only values that survive strict/coercion checks - * - Accepts both \TypeError (PHP-level rejection) and - * \Psr\SimpleCache\InvalidArgumentException (library-level validation) as - * valid failure modes - * - Is fully backward-compatible: existing v1/v2 consumers keep using - * SimpleCacheTest; v3 consumers switch to SimpleCacheV3Test - * - * @see \Cache\IntegrationTests\SimpleCacheTest - */ -abstract class SimpleCacheV3Test extends SimpleCacheTest -{ - /** - * Data provider for invalid array keys in setMultiple. - * - * Contains only string keys that are invalid per PSR-16 spec. - * Non-string key types (bool, null, float, int, object, array) are removed - * because PSR-16 v3's string type hint rejects them at PHP level before the - * implementation is invoked. - * - * @return list> - */ - public static function invalidArrayKeys() - { - return [ - [''], - ['{str'], - ['rand{'], - ['rand{str'], - ['rand}str'], - ['rand(str'], - ['rand)str'], - ['rand/str'], - ['rand\\str'], - ['rand@str'], - ['rand:str'], - ]; - } - - /** - * Data provider for invalid cache keys. - * - * Keeps the parent's structure (array_merge around invalidArrayKeys) but - * adds no extra items, since the parent's extra `[2]` is an integer and - * PSR-16 v3's string type hint rejects it at PHP level. - * - * @return list> - */ - public static function invalidKeys() - { - return array_merge( - self::invalidArrayKeys(), - [] - ); - } - - /** - * Data provider for invalid TTL values. - * - * PSR-16 v3 accepts only null|int|DateInterval for TTL. Values that coerce - * to valid types in PHP weak mode (e.g. true→1, false→0, ' 1'→1, '025'→25) - * are removed because they would not exercise invalid-TTL handling. - * - * @return list> - */ - public static function invalidTtl() - { - return [ - [''], - ['abc'], - ['12foo'], - [new \stdClass()], - [['array']], - ]; - } - - /** - * Helper that runs the supplied callable inside a try/catch. - * - * With PSR-16 v3 strict type hints a non-string key, non-iterable iterable, - * or invalid TTL will cause a \TypeError before the library sees the data. - * The PSR-16 spec says a key MUST be a string, so that is consistent. - * When the implementation is also typed we accept TypeError as a valid - * failure indicator for clearly-invalid arguments. - * - * @param callable(): void $callable - */ - private function assertCacheExceptionOrTypeError(callable $callable): void - { - try { - $callable(); - $this->fail('Expected exception to be thrown.'); - } catch (\TypeError $e) { - // PSR-16 v3 typed interfaces throw TypeError for clearly-invalid - // argument types (e.g. null key, object key, string for iterable). - $this->assertTrue(true); - } catch (\Psr\SimpleCache\InvalidArgumentException $e) { - // Explicit library-level validation. - $this->assertTrue(true); - } - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testGetInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->get($key); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testGetMultipleInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->getMultiple(['key1', $key, 'key2']); - }); - } - - public function testGetMultipleNoIterable() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->getMultiple('key'); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testSetInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->set($key, 'foobar'); - }); - } - - /** - * @dataProvider invalidArrayKeys - */ - #[DataProvider('invalidArrayKeys')] - public function testSetMultipleInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $values = function () use ($key) { - yield 'key1' => 'foo'; - yield $key => 'bar'; - yield 'key2' => 'baz'; - }; - $this->cache->setMultiple($values()); - }); - } - - public function testSetMultipleNoIterable() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->setMultiple('key'); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testHasInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->has($key); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testDeleteInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->delete($key); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testDeleteMultipleInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->deleteMultiple(['key1', $key, 'key2']); - }); - } - - public function testDeleteMultipleNoIterable() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->deleteMultiple('key'); - }); - } - - /** - * @dataProvider invalidTtl - */ - #[DataProvider('invalidTtl')] - public function testSetInvalidTtl($ttl) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { - $this->cache->set('key', 'value', $ttl); - }); - } - - /** - * @dataProvider invalidTtl - */ - #[DataProvider('invalidTtl')] - public function testSetMultipleInvalidTtl($ttl) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { - $this->cache->setMultiple(['key' => 'value'], $ttl); - }); - } - - /** - * Tests the PSR-16 mandated minimum key length of 64 characters. - * - * PSR-16 explicitly states: "The key length MUST be at least 64 characters." - * This test ensures every compliant implementation supports at least 64 chars. - * - * @see https://www.php-fig.org/psr/psr-16/ - */ - public function testBasicUsageWithLongKey64() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $key = str_repeat('a', 64); - - $this->assertFalse($this->cache->has($key)); - $this->assertTrue($this->cache->set($key, 'value')); - - $this->assertTrue($this->cache->has($key)); - $this->assertSame('value', $this->cache->get($key)); - - $this->assertTrue($this->cache->delete($key)); - - $this->assertFalse($this->cache->has($key)); - } -} From 930a17f77947fa86c46336aa82be756e108b7511 Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Tue, 2 Jun 2026 05:39:11 +0530 Subject: [PATCH 3/6] Address @stof review: remove version detection, unify exception handling, add PSR-6 --- composer.json | 3 +- src/CachePoolTest.php | 35 +++- src/SimpleCacheTest.php | 392 ++++------------------------------------ 3 files changed, 63 insertions(+), 367 deletions(-) diff --git a/composer.json b/composer.json index 02d1994..874f5fb 100755 --- a/composer.json +++ b/composer.json @@ -23,8 +23,7 @@ } ], "require": { - "php": ">=5.5.9", - "composer-runtime-api": "^2.0" + "php": ">=5.5.9" }, "require-dev": { "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", diff --git a/src/CachePoolTest.php b/src/CachePoolTest.php index d91b8f9..d13a590 100644 --- a/src/CachePoolTest.php +++ b/src/CachePoolTest.php @@ -1,5 +1,7 @@ fail('Expected exception to be thrown.'); + } catch (\TypeError $e) { + $this->assertTrue(true); + } catch (\Psr\Cache\InvalidArgumentException $e) { + $this->assertTrue(true); + } + } + /** * Data provider for invalid keys. * @@ -581,8 +601,9 @@ public function testGetItemInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\Cache\InvalidArgumentException'); - $this->cache->getItem($key); + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->getItem($key); + }); } /** @@ -609,8 +630,9 @@ public function testHasItemInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\Cache\InvalidArgumentException'); - $this->cache->hasItem($key); + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->hasItem($key); + }); } /** @@ -623,8 +645,9 @@ public function testDeleteItemInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\Cache\InvalidArgumentException'); - $this->cache->deleteItem($key); + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->deleteItem($key); + }); } /** diff --git a/src/SimpleCacheTest.php b/src/SimpleCacheTest.php index 99bde35..b5934bf 100644 --- a/src/SimpleCacheTest.php +++ b/src/SimpleCacheTest.php @@ -72,111 +72,34 @@ public function tearDownService() } } - /** - * Determine whether the installed psr/simple-cache is v1 (untyped interface). - * - * v2/v3 introduced parameter type hints (string $key, iterable $keys, - * null|int|\DateInterval $ttl). When those type hints are present, passing - * a non-string key or invalid TTL causes a PHP \TypeError before the - * library code runs. This helper lets data providers and tests adapt - * automatically so a single test class works with all three versions. - * - * The result is cached for the duration of the PHPUnit process. - * - * @return bool - */ - private static function isPsr16V1(): bool - { - static $isV1 = null; - if ($isV1 !== null) { - return $isV1; - } - - if (!class_exists(\Composer\InstalledVersions::class)) { - $isV1 = true; - return $isV1; - } - - if (!\Composer\InstalledVersions::isInstalled('psr/simple-cache')) { - // Fallback for environments where the package is not tracked - // (e.g. manual include). Assume v1 behaviour to stay safe. - $isV1 = true; - return $isV1; - } - - $version = \Composer\InstalledVersions::getVersion('psr/simple-cache'); - $isV1 = version_compare($version, '2.0.0', '<'); - - return $isV1; - } - /** * Data provider for invalid cache keys. * - * On v1, includes every invalid key type (string syntax, non-string type). - * On v2/v3, only string-syntax-invalid keys remain; non-string types are - * tested via the contract-violation data provider instead. - * - * @return list> + * @return array */ public static function invalidKeys() { - if (self::isPsr16V1()) { - return array_merge( - self::invalidArrayKeys(), - [ - [2], - ] - ); - } - - // v2/v3: only syntax violations survive PHP type hints - return [ - [''], - ['{str'], - ['rand{'], - ['rand{str'], - ['rand}str'], - ['rand(str'], - ['rand)str'], - ['rand/str'], - ['rand\\str'], - ['rand@str'], - ['rand:str'], - ]; + return array_merge( + self::invalidArrayKeys(), + [ + [2], + ] + ); } /** - * Data provider for invalid array keys (used in setMultiple). + * Data provider for invalid array keys. * - * @return list> + * @return array */ public static function invalidArrayKeys() { - if (self::isPsr16V1()) { - return [ - [''], - [true], - [false], - [null], - [2.5], - ['{str'], - ['rand{'], - ['rand{str'], - ['rand}str'], - ['rand(str'], - ['rand)str'], - ['rand/str'], - ['rand\\str'], - ['rand@str'], - ['rand:str'], - [new \stdClass()], - [['array']], - ]; - } - return [ [''], + [true], + [false], + [null], + [2.5], ['{str'], ['rand{'], ['rand{str'], @@ -187,118 +110,32 @@ public static function invalidArrayKeys() ['rand\\str'], ['rand@str'], ['rand:str'], - ]; - } - - /** - * Data provider for invalid TTL values. - * - * On v1, includes every invalid TTL type. - * On v2/v3, only values that survive PHP's null|int|\DateInterval hint - * but are still invalid per the PSR-16 spec remain. - * - * @return list> - */ - public static function invalidTtl() - { - $values = [ - [''], - ['abc'], [new \stdClass()], [['array']], ]; - - if (self::isPsr16V1()) { - // In v1 all of these reach the library and must throw - // InvalidArgumentException. - $values = array_merge( - $values, - [ - [true], - [false], - [2.5], - [' 1'], // can be casted to a int - ['12foo'], // can be casted to a int - ['025'], // can be interpreted as hex - ] - ); - } - - return $values; } /** - * Data provider for keys whose *type* violates the interface contract. - * - * These values are only valid when psr/simple-cache ^2.0|^3.0 is installed, - * because v2/v3 introduced parameter type hints that reject them with a - * PHP \TypeError before the library code runs. - * - * @return list> - */ - public static function invalidKeysTypeViolation() - { - if (self::isPsr16V1()) { - return []; - } - - return [ - [true], - [false], - [null], - [2.5], - [2], - [new \stdClass()], - [['array']], - ]; - } - - /** - * Data provider for array keys whose *type* violates the interface contract. - * - * Differs from invalidKeysTypeViolation: integer keys are excluded because - * PHP arrays/generators auto-cast integer keys to strings (e.g. yield 0 => 'v'), - * so an integer key inside an iterable is not a testable contract violation. + * Data provider for invalid TTL values. * - * @return list> + * @return array */ - public static function invalidArrayKeysTypeViolation() + public static function invalidTtl() { - if (self::isPsr16V1()) { - return []; - } - return [ + [''], [true], [false], - [null], + ['abc'], [2.5], + [' 1'], // can be casted to an int + ['12foo'], // can be casted to an int + ['025'], // can be interpreted as hex [new \stdClass()], [['array']], ]; } - /** - * Data provider for TTL values whose *type* violates the interface contract. - * - * @return list> - */ - public static function invalidTtlTypeViolation() - { - if (self::isPsr16V1()) { - return []; - } - - return [ - [true], - [false], - [2.5], - [' 1'], - ['12foo'], - ['025'], - ]; - } - /** * Accepts either \Psr\SimpleCache\InvalidArgumentException (library-level) or * \TypeError (PHP-level contract violation) as a valid failure. @@ -320,7 +157,7 @@ private function assertCacheExceptionOrTypeError(callable $callable): void /** * Data provider for valid keys. * - * @return list> + * @return array */ public static function validKeys() { @@ -333,7 +170,7 @@ public static function validKeys() /** * Data provider for valid data to store. * - * @return list> + * @return array */ public static function validData() { @@ -641,20 +478,6 @@ public function testGetInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->get($key); - } - - /** - * @dataProvider invalidKeysTypeViolation - */ - #[DataProvider('invalidKeysTypeViolation')] - public function testGetInvalidKeysTypeViolation($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->get($key); }); @@ -670,20 +493,6 @@ public function testGetMultipleInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $result = $this->cache->getMultiple(['key1', $key, 'key2']); - } - - /** - * @dataProvider invalidKeysTypeViolation - */ - #[DataProvider('invalidKeysTypeViolation')] - public function testGetMultipleInvalidKeysTypeViolation($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->getMultiple(['key1', $key, 'key2']); }); @@ -695,15 +504,9 @@ public function testGetMultipleNoIterable() $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - if (!self::isPsr16V1()) { - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->getMultiple('key'); - }); - return; - } - - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $result = $this->cache->getMultiple('key'); + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->getMultiple('key'); + }); } /** @@ -716,20 +519,6 @@ public function testSetInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->set($key, 'foobar'); - } - - /** - * @dataProvider invalidKeysTypeViolation - */ - #[DataProvider('invalidKeysTypeViolation')] - public function testSetInvalidKeysTypeViolation($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->set($key, 'foobar'); }); @@ -750,26 +539,7 @@ public function testSetMultipleInvalidKeys($key) yield $key => 'bar'; yield 'key2' => 'baz'; }; - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->setMultiple($values()); - } - - /** - * @dataProvider invalidArrayKeysTypeViolation - */ - #[DataProvider('invalidArrayKeysTypeViolation')] - public function testSetMultipleInvalidKeysTypeViolation($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $values = function () use ($key) { - yield 'key1' => 'foo'; - yield $key => 'bar'; - yield 'key2' => 'baz'; - }; + $this->assertCacheExceptionOrTypeError(function () use ($values) { $this->cache->setMultiple($values()); }); } @@ -780,15 +550,9 @@ public function testSetMultipleNoIterable() $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - if (!self::isPsr16V1()) { - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->setMultiple('key'); - }); - return; - } - - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->setMultiple('key'); + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->setMultiple('key'); + }); } /** @@ -801,20 +565,6 @@ public function testHasInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->has($key); - } - - /** - * @dataProvider invalidKeysTypeViolation - */ - #[DataProvider('invalidKeysTypeViolation')] - public function testHasInvalidKeysTypeViolation($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->has($key); }); @@ -830,20 +580,6 @@ public function testDeleteInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->delete($key); - } - - /** - * @dataProvider invalidKeysTypeViolation - */ - #[DataProvider('invalidKeysTypeViolation')] - public function testDeleteInvalidKeysTypeViolation($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->delete($key); }); @@ -859,20 +595,6 @@ public function testDeleteMultipleInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->deleteMultiple(['key1', $key, 'key2']); - } - - /** - * @dataProvider invalidKeysTypeViolation - */ - #[DataProvider('invalidKeysTypeViolation')] - public function testDeleteMultipleInvalidKeysTypeViolation($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->deleteMultiple(['key1', $key, 'key2']); }); @@ -884,15 +606,9 @@ public function testDeleteMultipleNoIterable() $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - if (!self::isPsr16V1()) { - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->deleteMultiple('key'); - }); - return; - } - - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->deleteMultiple('key'); + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->deleteMultiple('key'); + }); } /** @@ -905,27 +621,6 @@ public function testSetInvalidTtl($ttl) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - if (!self::isPsr16V1()) { - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { - $this->cache->set('key', 'value', $ttl); - }); - return; - } - - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->set('key', 'value', $ttl); - } - - /** - * @dataProvider invalidTtlTypeViolation - */ - #[DataProvider('invalidTtlTypeViolation')] - public function testSetInvalidTtlTypeViolation($ttl) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { $this->cache->set('key', 'value', $ttl); }); @@ -941,27 +636,6 @@ public function testSetMultipleInvalidTtl($ttl) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } - if (!self::isPsr16V1()) { - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { - $this->cache->setMultiple(['key' => 'value'], $ttl); - }); - return; - } - - $this->expectException('Psr\SimpleCache\InvalidArgumentException'); - $this->cache->setMultiple(['key' => 'value'], $ttl); - } - - /** - * @dataProvider invalidTtlTypeViolation - */ - #[DataProvider('invalidTtlTypeViolation')] - public function testSetMultipleInvalidTtlTypeViolation($ttl) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { $this->cache->setMultiple(['key' => 'value'], $ttl); }); From 11e7ed43c3e62943dbd037bbbbdef79500bd2783 Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Tue, 2 Jun 2026 08:56:40 +0530 Subject: [PATCH 4/6] Address @stof review: remove version branching, add PSR-6, keep type violations separate --- src/SimpleCacheTest.php | 228 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 212 insertions(+), 16 deletions(-) diff --git a/src/SimpleCacheTest.php b/src/SimpleCacheTest.php index b5934bf..d7b5c4f 100644 --- a/src/SimpleCacheTest.php +++ b/src/SimpleCacheTest.php @@ -75,31 +75,62 @@ public function tearDownService() /** * Data provider for invalid cache keys. * + * These are syntax-invalid string keys that always reach the library + * and must throw InvalidArgumentException. + * * @return array */ public static function invalidKeys() { - return array_merge( - self::invalidArrayKeys(), - [ - [2], - ] - ); + return [ + [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand/str'], + ['rand\\str'], + ['rand@str'], + ['rand:str'], + ]; } /** - * Data provider for invalid array keys. + * Data provider for keys whose *type* violates the interface contract. + * + * These are non-string types that may be rejected by PHP \TypeError + * (when the implementation has strict type hints) or by library-level + * InvalidArgumentException (when the implementation validates manually). * * @return array */ - public static function invalidArrayKeys() + public static function invalidKeysTypeViolation() { return [ - [''], [true], [false], [null], [2.5], + [2], + [new \stdClass()], + [['array']], + ]; + } + + /** + * Data provider for invalid array keys (used in setMultiple). + * + * These are syntax-invalid string keys that always reach the library + * and must throw InvalidArgumentException. + * + * @return array + */ + public static function invalidArrayKeys() + { + return [ + [''], ['{str'], ['rand{'], ['rand{str'], @@ -110,6 +141,24 @@ public static function invalidArrayKeys() ['rand\\str'], ['rand@str'], ['rand:str'], + ]; + } + + /** + * Data provider for array keys whose *type* violates the interface contract. + * + * Integer keys are excluded because PHP arrays/generators auto-cast + * integer keys to strings (e.g. yield 0 => 'v'). + * + * @return array + */ + public static function invalidArrayKeysTypeViolation() + { + return [ + [true], + [false], + [null], + [2.5], [new \stdClass()], [['array']], ]; @@ -118,24 +167,38 @@ public static function invalidArrayKeys() /** * Data provider for invalid TTL values. * + * These are values that survive PHP's null|int|\DateInterval type hint + * but are still invalid per the PSR-16 spec. + * * @return array */ public static function invalidTtl() { return [ [''], - [true], - [false], ['abc'], - [2.5], - [' 1'], // can be casted to an int - ['12foo'], // can be casted to an int - ['025'], // can be interpreted as hex [new \stdClass()], [['array']], ]; } + /** + * Data provider for TTL values whose *type* violates the interface contract. + * + * @return array + */ + public static function invalidTtlTypeViolation() + { + return [ + [true], + [false], + [2.5], + [' 1'], + ['12foo'], + ['025'], + ]; + } + /** * Accepts either \Psr\SimpleCache\InvalidArgumentException (library-level) or * \TypeError (PHP-level contract violation) as a valid failure. @@ -478,6 +541,20 @@ public function testGetInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); + $this->cache->get($key); + } + + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testGetInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->get($key); }); @@ -493,6 +570,20 @@ public function testGetMultipleInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); + $result = $this->cache->getMultiple(['key1', $key, 'key2']); + } + + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testGetMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->getMultiple(['key1', $key, 'key2']); }); @@ -519,6 +610,20 @@ public function testSetInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); + $this->cache->set($key, 'foobar'); + } + + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testSetInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->set($key, 'foobar'); }); @@ -539,7 +644,26 @@ public function testSetMultipleInvalidKeys($key) yield $key => 'bar'; yield 'key2' => 'baz'; }; - $this->assertCacheExceptionOrTypeError(function () use ($values) { + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); + $this->cache->setMultiple($values()); + } + + /** + * @dataProvider invalidArrayKeysTypeViolation + */ + #[DataProvider('invalidArrayKeysTypeViolation')] + public function testSetMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $values = function () use ($key) { + yield 'key1' => 'foo'; + yield $key => 'bar'; + yield 'key2' => 'baz'; + }; $this->cache->setMultiple($values()); }); } @@ -565,6 +689,20 @@ public function testHasInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); + $this->cache->has($key); + } + + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testHasInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->has($key); }); @@ -580,6 +718,20 @@ public function testDeleteInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); + $this->cache->delete($key); + } + + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testDeleteInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->delete($key); }); @@ -595,6 +747,20 @@ public function testDeleteMultipleInvalidKeys($key) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); + $this->cache->deleteMultiple(['key1', $key, 'key2']); + } + + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testDeleteMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + $this->assertCacheExceptionOrTypeError(function () use ($key) { $this->cache->deleteMultiple(['key1', $key, 'key2']); }); @@ -626,6 +792,21 @@ public function testSetInvalidTtl($ttl) }); } + /** + * @dataProvider invalidTtlTypeViolation + */ + #[DataProvider('invalidTtlTypeViolation')] + public function testSetInvalidTtlTypeViolation($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->set('key', 'value', $ttl); + }); + } + /** * @dataProvider invalidTtl */ @@ -641,6 +822,21 @@ public function testSetMultipleInvalidTtl($ttl) }); } + /** + * @dataProvider invalidTtlTypeViolation + */ + #[DataProvider('invalidTtlTypeViolation')] + public function testSetMultipleInvalidTtlTypeViolation($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->setMultiple(['key' => 'value'], $ttl); + }); + } + public function testNullOverwrite() { if (isset($this->skippedTests[__FUNCTION__])) { From 51de1c21e5d1c3caa829ebd08f66dedf51e3facf Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Tue, 2 Jun 2026 09:16:50 +0530 Subject: [PATCH 5/6] Fix DateTime int-to-string casts for declare(strict_types=1) --- src/CachePoolTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CachePoolTest.php b/src/CachePoolTest.php index d13a590..c57c914 100644 --- a/src/CachePoolTest.php +++ b/src/CachePoolTest.php @@ -380,9 +380,9 @@ public function testSaveExpired() $item = $this->cache->getItem('key'); $item->set('value'); - $item->expiresAt(\DateTime::createFromFormat('U', time() + 10)); + $item->expiresAt(\DateTime::createFromFormat('U', (string) (time() + 10))); $this->cache->save($item); - $item->expiresAt(\DateTime::createFromFormat('U', time() - 1)); + $item->expiresAt(\DateTime::createFromFormat('U', (string) (time() - 1))); $this->cache->save($item); $item = $this->cache->getItem('key'); $this->assertFalse($item->isHit(), 'Cache should not save expired items'); @@ -441,7 +441,7 @@ public function testDeferredExpired() $item = $this->cache->getItem('key'); $item->set('4711'); - $item->expiresAt(\DateTime::createFromFormat('U', time() - 1)); + $item->expiresAt(\DateTime::createFromFormat('U', (string) (time() - 1))); $this->cache->saveDeferred($item); $this->assertFalse($this->cache->hasItem('key'), 'Cache should not have expired deferred item'); From 6e3416461699f100fcc1111a1d5f6ff485782e19 Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Tue, 2 Jun 2026 15:21:13 +0530 Subject: [PATCH 6/6] Delete testBasicUsageWithLongKey64 method Removed test for PSR-16 minimum key length of 64 characters. Moving it in a seperate PR as suggested by @stof --- src/SimpleCacheTest.php | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/SimpleCacheTest.php b/src/SimpleCacheTest.php index d7b5c4f..91c5128 100644 --- a/src/SimpleCacheTest.php +++ b/src/SimpleCacheTest.php @@ -1037,31 +1037,4 @@ public function testObjectDoesNotChangeInCache() $cacheObject = $this->cache->get('key'); $this->assertEquals('value', $cacheObject->foo, 'Object in cache should not have their values changed.'); } - - /** - * Tests the PSR-16 mandated minimum key length of 64 characters. - * - * PSR-16 explicitly states: "The key length MUST be at least 64 characters." - * This test ensures every compliant implementation supports at least 64 chars. - * - * @see https://www.php-fig.org/psr/psr-16/ - */ - public function testBasicUsageWithLongKey64() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $key = str_repeat('a', 64); - - $this->assertFalse($this->cache->has($key)); - $this->assertTrue($this->cache->set($key, 'value')); - - $this->assertTrue($this->cache->has($key)); - $this->assertSame('value', $this->cache->get($key)); - - $this->assertTrue($this->cache->delete($key)); - - $this->assertFalse($this->cache->has($key)); - } }