diff --git a/src/Handler/UnionHandler.php b/src/Handler/UnionHandler.php index ba57f339e..ca6045ad5 100644 --- a/src/Handler/UnionHandler.php +++ b/src/Handler/UnionHandler.php @@ -115,12 +115,15 @@ private function matchSimpleType(mixed $data, array $type, Context $context): mi private function isPrimitiveType(string $type): bool { - return in_array($type, ['int', 'integer', 'float', 'double', 'bool', 'boolean', 'string'], true); + return in_array($type, ['int', 'integer', 'float', 'double', 'bool', 'boolean', 'string', 'array'], true); } private function testPrimitive(mixed $data, string $type, string $format): bool { switch ($type) { + case 'array': + return is_array($data); + case 'integer': case 'int': return (string) (int) $data === (string) $data; @@ -134,7 +137,7 @@ private function testPrimitive(mixed $data, string $type, string $format): bool return (string) (bool) $data === (string) $data; case 'string': - return (string) $data === (string) $data; + return is_string($data); } return false; diff --git a/src/Metadata/Driver/TypedPropertiesDriver.php b/src/Metadata/Driver/TypedPropertiesDriver.php index 2e7af8a66..5c447a946 100644 --- a/src/Metadata/Driver/TypedPropertiesDriver.php +++ b/src/Metadata/Driver/TypedPropertiesDriver.php @@ -60,9 +60,9 @@ public function __construct(DriverInterface $delegate, ?ParserInterface $typePar private function reorderTypes(array $types): array { uasort($types, static function ($a, $b) { - $order = ['null' => 0, 'true' => 1, 'false' => 2, 'bool' => 3, 'int' => 4, 'float' => 5, 'string' => 6]; + $order = ['null' => 0, 'true' => 1, 'false' => 2, 'bool' => 3, 'int' => 4, 'float' => 5, 'array' => 6, 'string' => 7]; - return ($order[$a['name']] ?? 7) <=> ($order[$b['name']] ?? 7); + return ($order[$a['name']] ?? 8) <=> ($order[$b['name']] ?? 8); }); return $types; @@ -119,7 +119,7 @@ public function loadMetadataForClass(ReflectionClass $class): ?ClassMetadata $this->reorderTypes( array_map( fn (string $type) => $this->typeParser->parse($type), - array_filter($reflectionType->getTypes(), [$this, 'shouldTypeHint']), + array_filter($reflectionType->getTypes(), [$this, 'shouldTypeHintInsideUnion']), ), ), ], @@ -181,11 +181,16 @@ private function shouldTypeHintUnion(?ReflectionType $reflectionType) } foreach ($reflectionType->getTypes() as $type) { - if ($this->shouldTypeHint($type)) { + if ($this->shouldTypeHintInsideUnion($type)) { return true; } } return false; } + + private function shouldTypeHintInsideUnion(ReflectionNamedType $reflectionType) + { + return $this->shouldTypeHint($reflectionType) || 'array' === $reflectionType->getName(); + } } diff --git a/tests/Fixtures/TypedProperties/UnionTypedProperties.php b/tests/Fixtures/TypedProperties/UnionTypedProperties.php index f88de896b..d8310e865 100644 --- a/tests/Fixtures/TypedProperties/UnionTypedProperties.php +++ b/tests/Fixtures/TypedProperties/UnionTypedProperties.php @@ -6,7 +6,7 @@ class UnionTypedProperties { - private int|bool|float|string $data; + private int|bool|float|string|array $data; private int|bool|float|string|null $nullableData; diff --git a/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php b/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php index 1da90bfd4..d6e0a3168 100644 --- a/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php +++ b/tests/Metadata/Driver/UnionTypedPropertiesDriverTest.php @@ -34,6 +34,10 @@ public function testInferUnionTypesShouldResultInManyTypes() 'params' => [ [ + [ + 'name' => 'array', + 'params' => [], + ], [ 'name' => 'string', 'params' => [], diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index b41c771df..f30319dcf 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -154,6 +154,7 @@ protected static function getContent($key) $outputs['data_float'] = '{"data":1.236}'; $outputs['data_bool'] = '{"data":false}'; $outputs['data_string'] = '{"data":"foo"}'; + $outputs['data_array'] = '{"data":[1,2,3]}'; $outputs['data_true'] = '{"data":true}'; $outputs['data_false'] = '{"data":false}'; $outputs['data_author'] = '{"data":{"full_name":"foo"}}'; @@ -443,28 +444,20 @@ public static function getTypeHintedArraysAndStdClass() ]; } - public function testDeserializingUnionProperties() + public static function getSimpleUnionProperties(): iterable { - if (PHP_VERSION_ID < 80000) { - $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); - - return; - } - - $object = new UnionTypedProperties(10000); - self::assertEquals($object, $this->deserialize(static::getContent('data_integer'), UnionTypedProperties::class)); - - $object = new UnionTypedProperties(1.236); - self::assertEquals($object, $this->deserialize(static::getContent('data_float'), UnionTypedProperties::class)); - - $object = new UnionTypedProperties(false); - self::assertEquals($object, $this->deserialize(static::getContent('data_bool'), UnionTypedProperties::class)); - - $object = new UnionTypedProperties('foo'); - self::assertEquals($object, $this->deserialize(static::getContent('data_string'), UnionTypedProperties::class)); + yield 'int' => [10000, 'data_integer']; + yield [1.236, 'data_float']; + yield [false, 'data_bool']; + yield ['foo', 'data_string']; + yield [[1, 2, 3], 'data_array']; } - public function testSerializingUnionProperties() + /** + * @dataProvider getSimpleUnionProperties + */ + #[DataProvider('getSimpleUnionProperties')] + public function testUnionProperties($data, string $expected): void { if (PHP_VERSION_ID < 80000) { $this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class)); @@ -472,17 +465,9 @@ public function testSerializingUnionProperties() return; } - $serialized = $this->serialize(new UnionTypedProperties(10000)); - self::assertEquals(static::getContent('data_integer'), $serialized); - - $serialized = $this->serialize(new UnionTypedProperties(1.236)); - self::assertEquals(static::getContent('data_float'), $serialized); - - $serialized = $this->serialize(new UnionTypedProperties(false)); - self::assertEquals(static::getContent('data_bool'), $serialized); - - $serialized = $this->serialize(new UnionTypedProperties('foo')); - self::assertEquals(static::getContent('data_string'), $serialized); + $object = new UnionTypedProperties($data); + self::assertEquals($object, $this->deserialize(static::getContent($expected), UnionTypedProperties::class)); + self::assertEquals($this->serialize($object), static::getContent($expected)); } public function testTrueDataType()