From e7a8f4162dec9da2cee52226aaf0739de44234c2 Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Wed, 10 Jun 2026 14:38:40 +0200 Subject: [PATCH] Add `ImportData::getNumber()` and `ImportData::getArray()` --- CHANGELOG.md | 7 +++ src/Import/ImportData.php | 101 ++++++++++++++++++++++++++++++++ tests/Import/ImportDataTest.php | 23 ++++++++ 3 files changed, 131 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac8e97f..501f838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +3.6.0 +===== + +* (feature) Add `ImportData::getNumber()`. +* (feature) Add `ImportData::getArray()`. + + 3.5.0 ===== diff --git a/src/Import/ImportData.php b/src/Import/ImportData.php index a55fa14..1cfb064 100644 --- a/src/Import/ImportData.php +++ b/src/Import/ImportData.php @@ -146,6 +146,72 @@ public function getOptionalFloat (string $path) : ?float } // endregion + // region Number + /** + * + */ + public function getNumber (string $path) : int|float + { + return $this->filterOutNull( + $this->getOptionalNumber($path), + "number", + $path, + ); + } + + /** + * + */ + public function getOptionalNumber (string $path) : int|float|null + { + $value = $this->get($path); + + if (null === $value) + { + return null; + } + + if (\is_int($value) || \is_float($value)) + { + return $value; + } + + if (!\is_string($value)) + { + throw new InvalidImportDataException(\sprintf( + "Expected number at path '%s', but got '%s'", + $path, + get_debug_type($value), + )); + } + + $options['flags'] = \FILTER_REQUIRE_SCALAR | \FILTER_NULL_ON_FAILURE; + $intValue = filter_var($value, \FILTER_VALIDATE_INT, $options); + + if (null !== $intValue) + { + \assert(\is_int($intValue)); + + return $intValue; + } + + $floatValue = filter_var($value, \FILTER_VALIDATE_FLOAT, $options); + + if (null === $floatValue) + { + throw new InvalidImportDataException(\sprintf( + "Expected number at path '%s', but got '%s'", + $path, + get_debug_type($value), + )); + } + + \assert(\is_float($floatValue)); + + return $floatValue; + } + // endregion + // region Boolean /** * @@ -179,6 +245,7 @@ public function getOptionalBoolean (string $path) : ?bool } // endregion + // region Enum /** * @template EnumClass of \BackedEnum * @@ -245,6 +312,40 @@ public function getOptionalEnum (string $path, string $enumClass) : ?\BackedEnum ); } } + // endregion + + // region Array + /** + * + */ + public function getArray (string $path) : array + { + return $this->filterOutNull( + $this->getOptionalArray($path), + "array", + $path, + ); + } + + /** + * + */ + public function getOptionalArray (string $path) : ?array + { + $value = $this->get($path); + + if (null !== $value && !\is_array($value)) + { + throw new InvalidImportDataException(\sprintf( + "Expected array at path '%s', but got '%s'", + $path, + get_debug_type($value), + )); + } + + return $value; + } + // endregion /** * @phpstan-param "int"|"float" $expectedType diff --git a/tests/Import/ImportDataTest.php b/tests/Import/ImportDataTest.php index e6af71b..9b55074 100644 --- a/tests/Import/ImportDataTest.php +++ b/tests/Import/ImportDataTest.php @@ -26,6 +26,7 @@ public function testValid () : void "bool" => true, "string" => "text", "enum" => "test", + "array" => ["a" => 15], "empty-string" => "", "null" => null, "nested" => [ @@ -57,6 +58,10 @@ public function testValid () : void self::assertSame(ExampleBackedEnum::Test, $data->getEnum("enum", ExampleBackedEnum::class)); self::assertSame(ExampleBackedEnum::Test, $data->getOptionalEnum("enum", ExampleBackedEnum::class)); + // array + self::assertSame(["a" => 15], $data->getArray("array")); + self::assertSame(["a" => 15], $data->getOptionalArray("array")); + // nested self::assertSame(15, $data->getInt("[nested][a]")); @@ -71,11 +76,13 @@ public function testValid () : void self::assertNull($data->getOptionalFloat("missing")); self::assertNull($data->getOptionalBoolean("missing")); self::assertNull($data->getOptionalEnum("missing", ExampleBackedEnum::class)); + self::assertNull($data->getOptionalArray("missing")); self::assertNull($data->getOptionalString("null")); self::assertNull($data->getOptionalInt("null")); self::assertNull($data->getOptionalFloat("null")); self::assertNull($data->getOptionalBoolean("null")); self::assertNull($data->getOptionalEnum("null", ExampleBackedEnum::class)); + self::assertNull($data->getOptionalArray("null")); } public static function provideInvalid () : iterable @@ -127,6 +134,11 @@ public static function provideInvalid () : iterable "Expected string at path 'nested', but got 'array'", ]; + yield "unparseable optional array" => [ + static fn (ImportData $data) => $data->getOptionalArray("string"), + "Expected array at path 'string', but got 'string'", + ]; + // missing fields yield "missing string" => [ static fn (ImportData $data) => $data->getString("missing"), @@ -148,6 +160,11 @@ public static function provideInvalid () : iterable "Expected bool at path 'missing', but got 'null'", ]; + yield "missing array" => [ + static fn (ImportData $data) => $data->getArray("missing"), + "Expected array at path 'missing', but got 'null'", + ]; + yield "missing enum" => [ static fn (ImportData $data) => $data->getEnum("missing", ExampleBackedEnum::class), "Could not fetch value of backed enum of type 'Tests\Torr\Rad\Fixtures\ExampleBackedEnum' at path 'missing', as there is no value at this path.", @@ -173,6 +190,11 @@ public static function provideInvalid () : iterable "Expected bool at path 'null', but got 'null'", ]; + yield "explicit null as array" => [ + static fn (ImportData $data) => $data->getArray("null"), + "Expected array at path 'null', but got 'null'", + ]; + yield "explicit null as enum" => [ static fn (ImportData $data) => $data->getEnum("null", ExampleBackedEnum::class), "Could not fetch value of backed enum of type 'Tests\Torr\Rad\Fixtures\ExampleBackedEnum' at path 'null', as there is no value at this path.", @@ -197,6 +219,7 @@ public function testInvalid ( "float-text" => "3.5", "bool" => true, "string" => "text", + "array" => ["a" => 15], "null" => null, "nested" => [ "a" => 15,