diff --git a/composer.json b/composer.json index f223b78..02d1994 100755 --- a/composer.json +++ b/composer.json @@ -23,9 +23,11 @@ } ], "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", "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/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)); + } }