Skip to content

Commit c783b1d

Browse files
authored
feat(openapi): improve type detection (#399)
Introduce type classes in order to represent array and composite types. Manage correctly allOf and oneOf keywords.
1 parent a5c0082 commit c783b1d

File tree

210 files changed

+8724
-589
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

210 files changed

+8724
-589
lines changed

Diff for: bin/compile

+5
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ php schema.phar generate tmp/original tests/e2e/schema_openapi_ref.yml -n -vv --
4040
diff tests/e2e/original/App/OpenApi/Entity/Order.php tmp/original/App/OpenApi/Entity/Order.php;
4141
diff tests/e2e/original/App/OpenApi/Entity/Pet.php tmp/original/App/OpenApi/Entity/Pet.php;
4242
diff tests/e2e/original/App/OpenApi/Entity/User.php tmp/original/App/OpenApi/Entity/User.php;
43+
44+
php schema.phar generate tmp/original tests/e2e/schema_open_education_api.yml -n -vv --ansi;
45+
46+
diff tests/e2e/original/App/OpenApi/Entity/AcademicSession.php tmp/original/App/OpenApi/Entity/AcademicSession.php;
47+
diff tests/e2e/original/App/OpenApi/Entity/Association.php tmp/original/App/OpenApi/Entity/Association.php;

Diff for: composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"symfony/string": "^5.2 || ^6.0"
5252
},
5353
"require-dev": {
54-
"api-platform/core": "^v2.7.0-rc.1",
54+
"api-platform/core": "^v2.7",
5555
"doctrine/orm": "^2.7",
5656
"myclabs/php-enum": "^1.7",
5757
"symfony/doctrine-bridge": "^5.2 || ^6.0",

Diff for: composer.lock

+403-444
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/AnnotationGenerator/PhpDocAnnotationGenerator.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -199,26 +199,27 @@ protected function toPhpDocType(Property $property, bool $adderOrRemover = false
199199
{
200200
$suffix = $property->isNullable ? '|null' : '';
201201
if ($property->isEnum) {
202-
if ($property->isArray) {
202+
if ($property->isArray()) {
203203
return 'string[]'.$suffix;
204204
}
205205

206206
return 'string'.$suffix;
207207
}
208208

209-
$enforcedNonArrayProperty = clone $property;
210-
$enforcedNonArrayProperty->isArray = false;
209+
if (!$property->reference && null !== $phpDocType = $this->phpTypeConverter->getPhpType($property)) {
210+
if ('array' === $phpDocType && $property->type) {
211+
$phpDocType = $property->type->getPhp();
212+
}
211213

212-
if (!$property->reference && null !== $phpDocType = $this->phpTypeConverter->getPhpType($enforcedNonArrayProperty)) {
213-
return ($property->isArray ? sprintf('%s[]', $phpDocType) : $phpDocType).$suffix;
214+
return $phpDocType.$suffix;
214215
}
215216

216217
if (!$property->reference) {
217218
return null;
218219
}
219220

220221
$phpDocType = $property->reference->interfaceName() ?: $property->reference->name();
221-
if (!$property->isArray || $adderOrRemover) {
222+
if ($adderOrRemover || !$property->isArray()) {
222223
return $phpDocType.$suffix;
223224
}
224225

Diff for: src/AttributeGenerator/ConstraintAttributeGenerator.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public function generatePropertyAttributes(Property $property, string $className
4242

4343
$asserts = [];
4444

45-
if (!$property->isArray && $property->type) {
46-
switch ($property->type) {
45+
if ($property->type && !$property->isArray()) {
46+
switch ((string) $property->type) {
4747
case 'url':
4848
$asserts[] = new Attribute('Assert\Url');
4949
break;
@@ -74,7 +74,7 @@ public function generatePropertyAttributes(Property $property, string $className
7474
if ($property->isEnum && $property->reference) {
7575
$args = ['callback' => [new Literal(sprintf('%s::class', $property->reference->name())), 'toArray']];
7676

77-
if ($property->isArray) {
77+
if ($property->isArray()) {
7878
$args['multiple'] = true;
7979
}
8080

Diff for: src/AttributeGenerator/DoctrineMongoDBAttributeGenerator.php

+32-27
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use ApiPlatform\SchemaGenerator\Model\Attribute;
1818
use ApiPlatform\SchemaGenerator\Model\Class_;
1919
use ApiPlatform\SchemaGenerator\Model\Property;
20+
use ApiPlatform\SchemaGenerator\Model\Type\CompositeType;
2021
use ApiPlatform\SchemaGenerator\Model\Use_;
2122
use Nette\PhpGenerator\Literal;
2223

@@ -86,34 +87,38 @@ public function generatePropertyAttributes(Property $property, string $className
8687

8788
$type = null;
8889
if ($property->isEnum) {
89-
$type = $property->isArray ? 'simple_array' : 'string';
90-
} elseif ($property->isArray && $property->type) {
90+
$type = $property->isArray() ? 'simple_array' : 'string';
91+
} elseif (!$property->reference && $property->isArray()) {
9192
$type = 'collection';
92-
} elseif (!$property->isArray && $property->type && !$property->reference && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
93-
switch ($property->type) {
94-
case 'time':
95-
$type = 'time';
96-
break;
97-
case 'dateTime':
98-
$type = 'date';
99-
break;
100-
default:
101-
$type = $phpType;
102-
switch ($phpType) {
103-
case 'bool':
104-
$type = 'boolean';
105-
break;
106-
case 'int':
107-
$type = 'integer';
108-
break;
109-
case '\\'.\DateTimeInterface::class:
110-
$type = 'date';
111-
break;
112-
case '\\'.\DateInterval::class:
113-
$type = 'string';
114-
break;
115-
}
116-
break;
93+
} elseif ($property->type && !$property->reference && !$property->isArray() && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
94+
if ($property->type instanceof CompositeType) {
95+
$type = 'raw';
96+
} else {
97+
switch ((string) $property->type) {
98+
case 'time':
99+
$type = 'time';
100+
break;
101+
case 'dateTime':
102+
$type = 'date';
103+
break;
104+
default:
105+
$type = $phpType;
106+
switch ($phpType) {
107+
case 'bool':
108+
$type = 'boolean';
109+
break;
110+
case 'int':
111+
$type = 'integer';
112+
break;
113+
case '\\'.\DateTimeInterface::class:
114+
$type = 'date';
115+
break;
116+
case '\\'.\DateInterval::class:
117+
$type = 'string';
118+
break;
119+
}
120+
break;
121+
}
117122
}
118123
}
119124

Diff for: src/AttributeGenerator/DoctrineOrmAttributeGenerator.php

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use ApiPlatform\SchemaGenerator\Model\Attribute;
1818
use ApiPlatform\SchemaGenerator\Model\Class_;
1919
use ApiPlatform\SchemaGenerator\Model\Property;
20+
use ApiPlatform\SchemaGenerator\Model\Type\CompositeType;
2021
use ApiPlatform\SchemaGenerator\Model\Use_;
2122
use Nette\PhpGenerator\Literal;
2223

@@ -102,10 +103,10 @@ public function generatePropertyAttributes(Property $property, string $className
102103

103104
$type = null;
104105
if ($property->isEnum) {
105-
$type = $property->isArray ? 'simple_array' : 'string';
106-
} elseif ($property->isArray && $property->type) {
106+
$type = $property->isArray() ? 'simple_array' : 'string';
107+
} elseif (!$property->reference && $property->isArray()) {
107108
$type = 'json';
108-
} elseif (!$property->isArray && $property->type && !$property->reference && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
109+
} elseif ($property->type && !$property instanceof CompositeType && !$property->reference && !$property->isArray() && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
109110
switch ($property->type) {
110111
case 'time':
111112
$type = 'time';

Diff for: src/ClassMutator/AnnotationsAppender.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private function generatePropertiesAnnotations(Class_ $class): void
119119
$property->addGetterAnnotation($getterAnnotation);
120120
}
121121

122-
if ($property->isArray) {
122+
if ($property->isArray()) {
123123
foreach ($annotationGenerator->generateAdderAnnotations($property) as $adderAnnotation) {
124124
$property->addAdderAnnotation($adderAnnotation);
125125
}

Diff for: src/ClassMutator/ClassPropertiesTypehintMutator.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\SchemaGenerator\ClassMutator;
1515

1616
use ApiPlatform\SchemaGenerator\Model\Class_;
17+
use ApiPlatform\SchemaGenerator\Model\Type\ArrayType;
1718
use ApiPlatform\SchemaGenerator\PhpTypeConverterInterface;
1819

1920
final class ClassPropertiesTypehintMutator implements ClassMutatorInterface
@@ -48,9 +49,9 @@ public function __invoke(Class_ $class, array $context): void
4849
$this->classes
4950
);
5051

51-
if ($property->isArray) {
52+
if ($property->type instanceof ArrayType) {
5253
$nonArrayForcedProperty = clone $property;
53-
$nonArrayForcedProperty->isArray = false;
54+
$nonArrayForcedProperty->type = $property->type->type;
5455

5556
$property->adderRemoverTypeHint = $this->phpTypeConverter->getPhpType($nonArrayForcedProperty, $this->config, $this->classes);
5657
}

Diff for: src/Model/Class_.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public function toNetteFile(array $config, InflectorInterface $inflector, ?PhpFi
318318
}
319319

320320
foreach ($sortedProperties as $property) {
321-
if ($property->isArray && 'array' !== $property->typeHint && !$property->isEnum) {
321+
if (!$property->isEnum && 'array' !== $property->typeHint && $property->isArray()) {
322322
$constructor->addBody('$this->? = new ArrayCollection();', [$property->name()]);
323323
}
324324
}

Diff for: src/Model/Property.php

+13-10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace ApiPlatform\SchemaGenerator\Model;
1515

16+
use ApiPlatform\SchemaGenerator\Model\Type\ArrayType;
17+
use ApiPlatform\SchemaGenerator\Model\Type\Type;
1618
use Nette\PhpGenerator\ClassType;
1719
use Nette\PhpGenerator\Method;
1820
use Nette\PhpGenerator\PhpNamespace;
@@ -25,14 +27,10 @@ abstract class Property
2527

2628
private string $name;
2729
public string $cardinality;
28-
/** @var ?string the data type (array and object are not one) */
29-
public ?string $type = null;
30+
/** @var ?Type the data types (object is not one) */
31+
public ?Type $type = null;
3032
/** @var mixed */
3133
public $defaultValue = null;
32-
/** @var ?string the array data type (object is not one) */
33-
public ?string $arrayType = null;
34-
/** @var bool can be true and array type false if the property is an array of references */
35-
public bool $isArray = false;
3634
public ?Class_ $reference = null;
3735
public bool $isReadable = true;
3836
public bool $isWritable = true;
@@ -76,6 +74,11 @@ abstract public function description(): ?string;
7674

7775
abstract public function rdfType(): ?string;
7876

77+
public function isArray(): bool
78+
{
79+
return $this->type instanceof ArrayType;
80+
}
81+
7982
public function addAnnotation(string $annotation): self
8083
{
8184
if ('' === $annotation || !\in_array($annotation, $this->annotations, true)) {
@@ -146,7 +149,7 @@ public function toNetteProperty(PhpNamespace $namespace, string $visibility = nu
146149
$property->setType($this->resolveName($namespace, $this->typeHint));
147150
}
148151

149-
if (!$this->isArray || $this->isTypeHintedAsCollection()) {
152+
if (!$this->isArray() || $this->isTypeHintedAsCollection()) {
150153
$property->setNullable($this->isNullable);
151154
}
152155

@@ -206,7 +209,7 @@ private function generateGetter(PhpNamespace $namespace): Method
206209
}
207210
if ($this->typeHint) {
208211
$getter->setReturnType($this->resolveName($namespace, $this->typeHint));
209-
if ($this->isNullable && !$this->isArray) {
212+
if ($this->isNullable && !$this->isArray()) {
210213
$getter->setReturnNullable();
211214
}
212215
}
@@ -229,7 +232,7 @@ private function generateMutators(
229232
}
230233

231234
$mutators = [];
232-
if ($this->isArray) {
235+
if ($this->isArray()) {
233236
$singularProperty = $singularize($this->name());
234237

235238
$adder = (new Method('add'.ucfirst($singularProperty)))->setVisibility(ClassType::VISIBILITY_PUBLIC);
@@ -307,7 +310,7 @@ private function generateMutators(
307310
*/
308311
private function guessDefaultGeneratedValue(bool $useDoctrineCollections = true)
309312
{
310-
if ($this->isArray && !$this->isTypeHintedAsCollection() && ($this->isEnum || !$this->typeHint || 'array' === $this->typeHint || !$useDoctrineCollections)) {
313+
if (($this->isEnum || !$this->typeHint || 'array' === $this->typeHint || !$useDoctrineCollections) && $this->isArray() && !$this->isTypeHintedAsCollection()) {
311314
return [];
312315
}
313316

Diff for: src/Model/Type/ArrayType.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\SchemaGenerator\Model\Type;
15+
16+
final class ArrayType implements Type
17+
{
18+
public ?Type $type;
19+
20+
public function __construct(?Type $type = null)
21+
{
22+
$this->type = $type;
23+
}
24+
25+
public function __toString(): string
26+
{
27+
if ($this->type instanceof CompositeType) {
28+
return '('.$this->type.')[]';
29+
}
30+
31+
return $this->type ? $this->type.'[]' : 'array';
32+
}
33+
34+
public function getPhp(): string
35+
{
36+
if ($this->type instanceof CompositeType) {
37+
return '('.$this->type.')[]';
38+
}
39+
40+
return $this->type ? $this->type.'[]' : 'array';
41+
}
42+
}

Diff for: src/Model/Type/CompositeType.php

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\SchemaGenerator\Model\Type;
15+
16+
interface CompositeType extends Type
17+
{
18+
}

Diff for: src/Model/Type/PrimitiveType.php

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\SchemaGenerator\Model\Type;
15+
16+
final class PrimitiveType implements Type
17+
{
18+
public string $name;
19+
20+
public function __construct(string $name)
21+
{
22+
$this->name = $name;
23+
}
24+
25+
public function __toString(): string
26+
{
27+
return $this->name;
28+
}
29+
30+
public function getPhp(): string
31+
{
32+
return $this->name;
33+
}
34+
}

Diff for: src/Model/Type/Type.php

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\SchemaGenerator\Model\Type;
15+
16+
interface Type
17+
{
18+
public function __toString(): string;
19+
20+
public function getPhp(): string;
21+
}

0 commit comments

Comments
 (0)