Skip to content

Commit

Permalink
Fix type extraction from array/mixed native types
Browse files Browse the repository at this point in the history
  • Loading branch information
r-st authored and f3l1x committed Jul 22, 2022
1 parent 60f2977 commit 3033fc7
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 15 deletions.
41 changes: 26 additions & 15 deletions src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -233,6 +224,7 @@ protected function phpScalarTypeToOpenApiType(string $type): string
'float' => 'number',
'bool' => 'boolean',
'string' => 'string',
'array' => 'array',
];

$type = $this->normalizeType($type);
Expand All @@ -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));
}

}
40 changes: 40 additions & 0 deletions tests/Cases/OpenApi/SchemaDefinition/Entity/EntityAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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();
13 changes: 13 additions & 0 deletions tests/Fixtures/ResponseEntity/MixedEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

namespace Tests\Fixtures\ResponseEntity;

class MixedEntity
{

public mixed $mixed;

/** @var string[]|(int[]&float[]) */
public mixed $complexType;

}
5 changes: 5 additions & 0 deletions tests/Fixtures/ResponseEntity/NativeUnionEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ class NativeUnionEntity

public DateTime|int $dateOrInt;

public array|int $arrayOrInt;

/** @var string[]|string */
public array|string $strings;

}
5 changes: 5 additions & 0 deletions tests/Fixtures/ResponseEntity/TypedResponseEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ class TypedResponseEntity
// phpcs:ignore
public $untypedProperty;

public array $untypedArray;

/** @var int[] */
public array $arrayOfInt;

}

0 comments on commit 3033fc7

Please sign in to comment.