Skip to content

Commit 3033fc7

Browse files
r-stf3l1x
authored andcommitted
Fix type extraction from array/mixed native types
1 parent 60f2977 commit 3033fc7

File tree

5 files changed

+89
-15
lines changed

5 files changed

+89
-15
lines changed

src/OpenApi/SchemaDefinition/Entity/EntityAdapter.php

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -150,28 +150,19 @@ protected function getProperties(string $type): array
150150

151151
private function getPropertyType(ReflectionProperty $property): ?string
152152
{
153+
$nativeType = null;
153154
if (PHP_VERSION_ID >= 70400 && ($type = Type::fromReflection($property)) !== null) {
154-
if ($type->isSingle() && count($type->getNames()) === 1) {
155-
return $type->getNames()[0];
155+
$nativeType = $this->getNativePropertyType($type, $property);
156+
// If type is array/mixed or union/intersection of it, try to get more information from annotations
157+
if (!preg_match('#[|&]?(array|mixed)[|&]?#', $nativeType)) {
158+
return $nativeType;
156159
}
157-
158-
if ($type->isUnion()
159-
|| ($type->isSingle() && count($type->getNames()) === 2) // nullable type is single but returns name of type and null in names
160-
) {
161-
return implode('|', $type->getNames());
162-
}
163-
164-
if ($type->isIntersection()) {
165-
return implode('&', $type->getNames());
166-
}
167-
168-
throw new RuntimeException(sprintf('Could not parse type "%s"', $property));
169160
}
170161

171162
$annotation = $this->parseAnnotation($property, 'var');
172163

173164
if ($annotation === null) {
174-
return null;
165+
return $nativeType;
175166
}
176167

177168
if (($type = preg_replace('#\s.*#', '', $annotation)) !== null) {
@@ -233,6 +224,7 @@ protected function phpScalarTypeToOpenApiType(string $type): string
233224
'float' => 'number',
234225
'bool' => 'boolean',
235226
'string' => 'string',
227+
'array' => 'array',
236228
];
237229

238230
$type = $this->normalizeType($type);
@@ -259,4 +251,23 @@ protected function normalizeType(string $type): string
259251
return $map[strtolower($type)] ?? $type;
260252
}
261253

254+
private function getNativePropertyType(Type $type, ReflectionProperty $property): string
255+
{
256+
if ($type->isSingle() && count($type->getNames()) === 1) {
257+
return $type->getNames()[0];
258+
}
259+
260+
if ($type->isUnion()
261+
|| ($type->isSingle() && count($type->getNames()) === 2) // nullable type is single but returns name of type and null in names
262+
) {
263+
return implode('|', $type->getNames());
264+
}
265+
266+
if ($type->isIntersection()) {
267+
return implode('&', $type->getNames());
268+
}
269+
270+
throw new RuntimeException(sprintf('Could not parse type "%s"', $property));
271+
}
272+
262273
}

tests/Cases/OpenApi/SchemaDefinition/Entity/EntityAdapterTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Tester\Assert;
1010
use Tester\TestCase;
1111
use Tests\Fixtures\ResponseEntity\CompoundResponseEntity;
12+
use Tests\Fixtures\ResponseEntity\MixedEntity;
1213
use Tests\Fixtures\ResponseEntity\NativeIntersectionEntity;
1314
use Tests\Fixtures\ResponseEntity\NativeUnionEntity;
1415
use Tests\Fixtures\ResponseEntity\SelfReferencingEntity;
@@ -199,6 +200,8 @@ public function testTypedEntity(): void
199200
'datetime' => ['type' => 'string', 'format' => 'date-time'],
200201
'phpdocInt' => ['type' => 'integer'],
201202
'untypedProperty' => ['type' => 'string'],
203+
'untypedArray' => ['type' => 'array'],
204+
'arrayOfInt' => ['type' => 'array', 'items' => ['type' => 'integer']],
202205
],
203206
],
204207
$adapter->getMetadata(TypedResponseEntity::class)
@@ -230,6 +233,18 @@ public function testNativeUnionEntity(): void
230233
['type' => 'integer'],
231234
],
232235
],
236+
'arrayOrInt' => [
237+
'oneOf' => [
238+
['type' => 'array'],
239+
['type' => 'integer'],
240+
],
241+
],
242+
'strings' => [
243+
'oneOf' => [
244+
['type' => 'array', 'items' => ['type' => 'string']],
245+
['type' => 'string'],
246+
],
247+
],
233248
],
234249
],
235250
$adapter->getMetadata(NativeUnionEntity::class)
@@ -260,6 +275,31 @@ public function testNativeIntersectionEntity(): void
260275
);
261276
}
262277

278+
public function testMixedEntity(): void
279+
{
280+
if (PHP_VERSION_ID < 80000) {
281+
$this->skip();
282+
}
283+
284+
$adapter = new EntityAdapter();
285+
Assert::same(
286+
[
287+
'type' => 'object',
288+
'properties' => [
289+
'mixed' => ['nullable' => true],
290+
'complexType' => [
291+
'anyOf' => [
292+
['type' => 'array', 'items' => ['type' => 'string']],
293+
['type' => 'array', 'items' => ['type' => 'integer']],
294+
['type' => 'array', 'items' => ['type' => 'number']],
295+
],
296+
],
297+
],
298+
],
299+
$adapter->getMetadata(MixedEntity::class)
300+
);
301+
}
302+
263303
}
264304

265305
(new EntityAdapterTest())->run();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Tests\Fixtures\ResponseEntity;
4+
5+
class MixedEntity
6+
{
7+
8+
public mixed $mixed;
9+
10+
/** @var string[]|(int[]&float[]) */
11+
public mixed $complexType;
12+
13+
}

tests/Fixtures/ResponseEntity/NativeUnionEntity.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ class NativeUnionEntity
1313

1414
public DateTime|int $dateOrInt;
1515

16+
public array|int $arrayOrInt;
17+
18+
/** @var string[]|string */
19+
public array|string $strings;
20+
1621
}

tests/Fixtures/ResponseEntity/TypedResponseEntity.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ class TypedResponseEntity
2323
// phpcs:ignore
2424
public $untypedProperty;
2525

26+
public array $untypedArray;
27+
28+
/** @var int[] */
29+
public array $arrayOfInt;
30+
2631
}

0 commit comments

Comments
 (0)