From 43606becc493b73598cd85a422cc9be0c88df0a5 Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Tue, 10 Jun 2025 21:30:29 +0100 Subject: [PATCH 1/3] feature: getTypedArray closes #59 --- src/Cache.php | 52 +++++++++++++++++++++++ test/phpunit/CacheTest.php | 87 +++++++++++++++++++++++++++++++++++++- 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/Cache.php b/src/Cache.php index a769fdd..555e1d6 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -73,6 +73,58 @@ public function getArray(string $name, callable $callback):array { return $value; } + /** + * @template T + * @param class-string $className + * @return array + */ + public function getTypedArray(string $name, string $className, callable $callback):array { + $array = $this->get($name, $callback); + if(!is_array($array)) { + throw new TypeError("Value with key '$name' is not an array"); + } + + foreach($array as $key => $value) { + if($className === "int" || $className === "integer") { + if(!is_int($value)) { + if(is_numeric($value)) { + $array[$key] = (int)$value; + } + else { + throw new TypeError("Array value at key '$key' is not an integer"); + } + } + } + elseif($className === "float" || $className === "double") { + if(!is_float($value)) { + if(is_numeric($value)) { + $array[$key] = (float)$value; + } + else { + throw new TypeError("Array value at key '$key' is not a float"); + } + } + } + elseif($className === "string") { + if(!is_string($value)) { + $array[$key] = (string)$value; + } + } + elseif($className === "bool" || $className === "boolean") { + if(!is_bool($value)) { + $array[$key] = (bool)$value; + } + } + else { + if(!$value instanceof $className) { + throw new TypeError("Array value at key '$key' is not an instance of $className"); + } + } + } + + return $array; + } + /** * @template T * @param class-string $className diff --git a/test/phpunit/CacheTest.php b/test/phpunit/CacheTest.php index f99800c..a44cbc3 100644 --- a/test/phpunit/CacheTest.php +++ b/test/phpunit/CacheTest.php @@ -4,7 +4,10 @@ use Gt\FileCache\Cache; use Gt\FileCache\FileAccess; use PHPUnit\Framework\TestCase; +use SplFileInfo; +use SplFixedArray; use stdClass; +use TypeError; class CacheTest extends TestCase { public function tearDown():void { @@ -105,12 +108,94 @@ public function testGetArray():void { self::assertSame($value, $sut->getArray("numbers", fn() => [])); } + public function testGetTypedArray_int():void { + $value = [1, "2", 3.000]; + $sut = $this->getSut([ + "numbers" => $value, + ]); + $typedArray = $sut->getTypedArray("numbers", "int", fn() => []); + foreach($typedArray as $value) { + self::assertIsInt($value); + } + } + + public function testGetTypedArray_intFailure():void { + $value = [1, "2", 3.000, "four"]; + $sut = $this->getSut([ + "numbers" => $value, + ]); + self::expectException(TypeError::class); + $sut->getTypedArray("numbers", "int", fn() => []); + } + + public function testGetTypedArray_float():void { + $value = [1, "2", 3.000]; + $sut = $this->getSut([ + "numbers" => $value, + ]); + $typedArray = $sut->getTypedArray("numbers", "float", fn() => []); + foreach($typedArray as $value) { + self::assertIsFloat($value); + } + } + + public function testGetTypedArray_floatFailure():void { + $value = [1, "2", 3.000, "four"]; + $sut = $this->getSut([ + "numbers" => $value, + ]); + self::expectException(TypeError::class); + $sut->getTypedArray("numbers", "float", fn() => []); + } + + public function testGetTypedArray_string():void { + $value = [1, "2", 3.000, "four"]; + $sut = $this->getSut([ + "numbers" => $value, + ]); + $typedArray= $sut->getTypedArray("numbers", "string", fn() => []); + foreach($typedArray as $value) { + self::assertIsString($value); + } + } + + public function testGetTypedArray_bool():void { + $value = [0, "1", false, true, [], new StdClass()]; + $sut = $this->getSut([ + "booleans" => $value, + ]); + $typedArray= $sut->getTypedArray("booleans", "bool", fn() => []); + foreach($typedArray as $i => $value) { + self::assertSame((bool)($i % 2), $value, $i); + } + } + + public function testGetTypedArray_class():void { + $value = [new SplFileInfo(__FILE__), new SplFileInfo(__DIR__)]; + $sut = $this->getSut([ + "files" => $value, + ]); + $typedArray= $sut->getTypedArray("files", SplFileInfo::class, fn() => []); + foreach($typedArray as $value) { + self::assertInstanceOf(SplFileInfo::class, $value); + } + } + + public function testGetTypedArray_classError():void { + $value = [new SplFileInfo(__FILE__), new SplFixedArray(), new SplFileInfo(__DIR__)]; + $sut = $this->getSut([ + "files" => $value, + ]); + self::expectException(TypeError::class); + $sut->getTypedArray("files", SplFileInfo::class, fn() => []); + } + public function testGetArray_notArray():void { $value = (object)[1, 2, 3]; $sut = $this->getSut([ "numbers" => $value, ]); - self::expectException(\TypeError::class); + self::expectException(TypeError::class); self::expectExceptionMessage("Value with key 'numbers' is not an array"); $sut->getArray("numbers", fn() => []); } From 13e6e2350a1fb825bb1af944e9b51d0b1a45761b Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Tue, 10 Jun 2025 22:24:05 +0100 Subject: [PATCH 2/3] tweak: improve test coverage --- src/Cache.php | 124 +++++++++++++++++++++++++------------ test/phpunit/CacheTest.php | 43 ++++++++++--- 2 files changed, 119 insertions(+), 48 deletions(-) diff --git a/src/Cache.php b/src/Cache.php index 555e1d6..6974fd7 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -67,7 +67,7 @@ public function getDateTime(string $name, callable $callback):DateTimeInterface public function getArray(string $name, callable $callback):array { $value = $this->get($name, $callback); if(!is_array($value)) { - throw new TypeError("Value with key '$name' is not an array"); + throw new TypeError("Data '$name' is not an array"); } return $value; @@ -81,50 +81,98 @@ public function getArray(string $name, callable $callback):array { public function getTypedArray(string $name, string $className, callable $callback):array { $array = $this->get($name, $callback); if(!is_array($array)) { - throw new TypeError("Value with key '$name' is not an array"); + throw new TypeError("Data '$name' is not an array"); } foreach($array as $key => $value) { - if($className === "int" || $className === "integer") { - if(!is_int($value)) { - if(is_numeric($value)) { - $array[$key] = (int)$value; - } - else { - throw new TypeError("Array value at key '$key' is not an integer"); - } - } - } - elseif($className === "float" || $className === "double") { - if(!is_float($value)) { - if(is_numeric($value)) { - $array[$key] = (float)$value; - } - else { - throw new TypeError("Array value at key '$key' is not a float"); - } - } - } - elseif($className === "string") { - if(!is_string($value)) { - $array[$key] = (string)$value; - } - } - elseif($className === "bool" || $className === "boolean") { - if(!is_bool($value)) { - $array[$key] = (bool)$value; - } - } - else { - if(!$value instanceof $className) { - throw new TypeError("Array value at key '$key' is not an instance of $className"); - } - } + $array[$key] = $this->validateAndConvertValue($value, $className, $key); } return $array; } + /** + * @template T + * @param mixed $value + * @param class-string $className + * @param string|int $key + * @return T + */ + private function validateAndConvertValue(mixed $value, string $className, string|int $key): mixed { + return match(strtolower($className)) { + "int", "integer" => $this->validateAndConvertInt($value, $key), + "float", "double" => $this->validateAndConvertFloat($value, $key), + "string" => $this->convertToString($value), + "bool", "boolean" => $this->convertToBool($value), + default => $this->validateInstance($value, $className, $key), + }; + } + + /** + * @param mixed $value + * @param string|int $key + * @return int + */ + private function validateAndConvertInt(mixed $value, string|int $key): int { + if(is_int($value)) { + return $value; + } + + if(is_numeric($value)) { + return (int)$value; + } + + throw new TypeError("Array value at key '$key' is not an integer"); + } + + /** + * @param mixed $value + * @param string|int $key + * @return float + */ + private function validateAndConvertFloat(mixed $value, string|int $key): float { + if(is_float($value)) { + return $value; + } + + if(is_numeric($value)) { + return (float)$value; + } + + throw new TypeError("Array value at key '$key' is not a float"); + } + + /** + * @param mixed $value + * @return string + */ + private function convertToString(mixed $value): string { + return (string)$value; + } + + /** + * @param mixed $value + * @return bool + */ + private function convertToBool(mixed $value): bool { + return (bool)$value; + } + + /** + * @template T + * @param mixed $value + * @param class-string $className + * @param string|int $key + * @return T + */ + private function validateInstance(mixed $value, string $className, string|int $key): object { + if($value instanceof $className) { + return $value; + } + + throw new TypeError("Array value at key '$key' is not an instance of $className"); + } + /** * @template T * @param class-string $className @@ -133,7 +181,7 @@ public function getTypedArray(string $name, string $className, callable $callbac public function getInstance(string $name, string $className, callable $callback):object { $value = $this->get($name, $callback); if(get_class($value) !== $className) { - throw new TypeError("Value is not of type $className"); + throw new TypeError("Value is not an instance of $className"); } return $value; diff --git a/test/phpunit/CacheTest.php b/test/phpunit/CacheTest.php index a44cbc3..20f0e87 100644 --- a/test/phpunit/CacheTest.php +++ b/test/phpunit/CacheTest.php @@ -100,6 +100,18 @@ public function testGetInstance():void { self::assertSame($value->name, $class->name); } + public function testGetInstance_error():void { + $value = new StdClass(); + $value->name = uniqid(); + + $sut = $this->getSut([ + "test" => $value, + ]); + self::expectException(TypeError::class); + self::expectExceptionMessage("Value is not an instance of SplFileInfo"); + $sut->getInstance("test", SplFileInfo::class, fn() => false); + } + public function testGetArray():void { $value = [1, 2, 3]; $sut = $this->getSut([ @@ -108,6 +120,26 @@ public function testGetArray():void { self::assertSame($value, $sut->getArray("numbers", fn() => [])); } + public function testGetArray_notArray():void { + $value = (object)[1, 2, 3]; + $sut = $this->getSut([ + "numbers" => $value, + ]); + self::expectException(TypeError::class); + self::expectExceptionMessage("Data 'numbers' is not an array"); + $sut->getArray("numbers", fn() => []); + } + + public function testGetTypedArray_notArray():void { + $value = (object)[1, 2, 3]; + $sut = $this->getSut([ + "numbers" => $value, + ]); + self::expectException(TypeError::class); + self::expectExceptionMessage("Data 'numbers' is not an array"); + $sut->getTypedArray("numbers", "int", fn() => []); + } + public function testGetTypedArray_int():void { $value = [1, "2", 3.000]; $sut = $this->getSut([ @@ -186,20 +218,11 @@ public function testGetTypedArray_classError():void { $sut = $this->getSut([ "files" => $value, ]); + self::expectExceptionMessage("Array value at key '1' is not an instance of SplFileInfo"); self::expectException(TypeError::class); $sut->getTypedArray("files", SplFileInfo::class, fn() => []); } - public function testGetArray_notArray():void { - $value = (object)[1, 2, 3]; - $sut = $this->getSut([ - "numbers" => $value, - ]); - self::expectException(TypeError::class); - self::expectExceptionMessage("Value with key 'numbers' is not an array"); - $sut->getArray("numbers", fn() => []); - } - private function getSut(array $mockFiles = []):Cache { $mockFileAccess = null; if(!empty($mockFiles)) { From b65640bc19a772d0ea14a02f0b302e710ec0635a Mon Sep 17 00:00:00 2001 From: Greg Bowler Date: Tue, 10 Jun 2025 22:58:27 +0100 Subject: [PATCH 3/3] feature: invalidate function --- src/FileAccess.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/FileAccess.php b/src/FileAccess.php index 1ef0924..e61cb59 100644 --- a/src/FileAccess.php +++ b/src/FileAccess.php @@ -35,4 +35,13 @@ public function checkValidity(string $name, int $secondsValidity):void { throw new CacheInvalidException($filePath); } } + + public function invalidate(string $name):void { + $filePath = "$this->dirPath/$name"; + if(!is_file($filePath)) { + return; + } + + unlink($filePath); + } }