diff --git a/src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php b/src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php index 29132948..5fa3d279 100644 --- a/src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php +++ b/src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php @@ -150,28 +150,19 @@ protected function getProperties(string $type): array private function getPropertyType(ReflectionProperty $property): ?string { + $nativeType = null; if (PHP_VERSION_ID >= 70400 && ($type = Type::fromReflection($property)) !== null) { - if ($type->isSingle() && count($type->getNames()) === 1) { - return $type->getNames()[0]; + $nativeType = $this->getNativePropertyType($type, $property); + // If type is array/mixed or union/intersection of it, try to get more information from annotations + if (!preg_match('#[|&]?(array|mixed)[|&]?#', $nativeType)) { + return $nativeType; } - - if ($type->isUnion() - || ($type->isSingle() && count($type->getNames()) === 2) // nullable type is single but returns name of type and null in names - ) { - return implode('|', $type->getNames()); - } - - if ($type->isIntersection()) { - return implode('&', $type->getNames()); - } - - throw new RuntimeException(sprintf('Could not parse type "%s"', $property)); } $annotation = $this->parseAnnotation($property, 'var'); if ($annotation === null) { - return null; + return $nativeType; } if (($type = preg_replace('#\s.*#', '', $annotation)) !== null) { @@ -233,6 +224,7 @@ protected function phpScalarTypeToOpenApiType(string $type): string 'float' => 'number', 'bool' => 'boolean', 'string' => 'string', + 'array' => 'array', ]; $type = $this->normalizeType($type); @@ -259,4 +251,23 @@ protected function normalizeType(string $type): string return $map[strtolower($type)] ?? $type; } + private function getNativePropertyType(Type $type, ReflectionProperty $property): string + { + if ($type->isSingle() && count($type->getNames()) === 1) { + return $type->getNames()[0]; + } + + if ($type->isUnion() + || ($type->isSingle() && count($type->getNames()) === 2) // nullable type is single but returns name of type and null in names + ) { + return implode('|', $type->getNames()); + } + + if ($type->isIntersection()) { + return implode('&', $type->getNames()); + } + + throw new RuntimeException(sprintf('Could not parse type "%s"', $property)); + } + } diff --git a/tests/Cases/OpenApi/SchemaDefinition/Entity/EntityAdapterTest.php b/tests/Cases/OpenApi/SchemaDefinition/Entity/EntityAdapterTest.php index f9075e89..e83c0c31 100644 --- a/tests/Cases/OpenApi/SchemaDefinition/Entity/EntityAdapterTest.php +++ b/tests/Cases/OpenApi/SchemaDefinition/Entity/EntityAdapterTest.php @@ -9,6 +9,7 @@ use Tester\Assert; use Tester\TestCase; use Tests\Fixtures\ResponseEntity\CompoundResponseEntity; +use Tests\Fixtures\ResponseEntity\MixedEntity; use Tests\Fixtures\ResponseEntity\NativeIntersectionEntity; use Tests\Fixtures\ResponseEntity\NativeUnionEntity; use Tests\Fixtures\ResponseEntity\SelfReferencingEntity; @@ -199,6 +200,8 @@ public function testTypedEntity(): void 'datetime' => ['type' => 'string', 'format' => 'date-time'], 'phpdocInt' => ['type' => 'integer'], 'untypedProperty' => ['type' => 'string'], + 'untypedArray' => ['type' => 'array'], + 'arrayOfInt' => ['type' => 'array', 'items' => ['type' => 'integer']], ], ], $adapter->getMetadata(TypedResponseEntity::class) @@ -230,6 +233,18 @@ public function testNativeUnionEntity(): void ['type' => 'integer'], ], ], + 'arrayOrInt' => [ + 'oneOf' => [ + ['type' => 'array'], + ['type' => 'integer'], + ], + ], + 'strings' => [ + 'oneOf' => [ + ['type' => 'array', 'items' => ['type' => 'string']], + ['type' => 'string'], + ], + ], ], ], $adapter->getMetadata(NativeUnionEntity::class) @@ -260,6 +275,31 @@ public function testNativeIntersectionEntity(): void ); } + public function testMixedEntity(): void + { + if (PHP_VERSION_ID < 80000) { + $this->skip(); + } + + $adapter = new EntityAdapter(); + Assert::same( + [ + 'type' => 'object', + 'properties' => [ + 'mixed' => ['nullable' => true], + 'complexType' => [ + 'anyOf' => [ + ['type' => 'array', 'items' => ['type' => 'string']], + ['type' => 'array', 'items' => ['type' => 'integer']], + ['type' => 'array', 'items' => ['type' => 'number']], + ], + ], + ], + ], + $adapter->getMetadata(MixedEntity::class) + ); + } + } (new EntityAdapterTest())->run(); diff --git a/tests/Fixtures/ResponseEntity/MixedEntity.php b/tests/Fixtures/ResponseEntity/MixedEntity.php new file mode 100644 index 00000000..de7851e6 --- /dev/null +++ b/tests/Fixtures/ResponseEntity/MixedEntity.php @@ -0,0 +1,13 @@ +