diff --git a/src/Cache.php b/src/Cache.php index a769fdd..6974fd7 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -67,12 +67,112 @@ 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; } + /** + * @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("Data '$name' is not an array"); + } + + foreach($array as $key => $value) { + $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 @@ -81,7 +181,7 @@ public function getArray(string $name, callable $callback):array { 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/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); + } } diff --git a/test/phpunit/CacheTest.php b/test/phpunit/CacheTest.php index f99800c..20f0e87 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 { @@ -97,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([ @@ -110,11 +125,104 @@ public function testGetArray_notArray():void { $sut = $this->getSut([ "numbers" => $value, ]); - self::expectException(\TypeError::class); - self::expectExceptionMessage("Value with key 'numbers' is not an array"); + 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([ + "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::expectExceptionMessage("Array value at key '1' is not an instance of SplFileInfo"); + self::expectException(TypeError::class); + $sut->getTypedArray("files", SplFileInfo::class, fn() => []); + } + private function getSut(array $mockFiles = []):Cache { $mockFileAccess = null; if(!empty($mockFiles)) {