Skip to content

Commit

Permalink
feat(openapi): improve type detection (#399)
Browse files Browse the repository at this point in the history
Introduce type classes in order to represent array and composite types.
Manage correctly allOf and oneOf keywords.
  • Loading branch information
alanpoulain committed Dec 13, 2022
1 parent a5c0082 commit c783b1d
Show file tree
Hide file tree
Showing 210 changed files with 8,724 additions and 589 deletions.
5 changes: 5 additions & 0 deletions bin/compile
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ php schema.phar generate tmp/original tests/e2e/schema_openapi_ref.yml -n -vv --
diff tests/e2e/original/App/OpenApi/Entity/Order.php tmp/original/App/OpenApi/Entity/Order.php;
diff tests/e2e/original/App/OpenApi/Entity/Pet.php tmp/original/App/OpenApi/Entity/Pet.php;
diff tests/e2e/original/App/OpenApi/Entity/User.php tmp/original/App/OpenApi/Entity/User.php;

php schema.phar generate tmp/original tests/e2e/schema_open_education_api.yml -n -vv --ansi;

diff tests/e2e/original/App/OpenApi/Entity/AcademicSession.php tmp/original/App/OpenApi/Entity/AcademicSession.php;
diff tests/e2e/original/App/OpenApi/Entity/Association.php tmp/original/App/OpenApi/Entity/Association.php;
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"symfony/string": "^5.2 || ^6.0"
},
"require-dev": {
"api-platform/core": "^v2.7.0-rc.1",
"api-platform/core": "^v2.7",
"doctrine/orm": "^2.7",
"myclabs/php-enum": "^1.7",
"symfony/doctrine-bridge": "^5.2 || ^6.0",
Expand Down
847 changes: 403 additions & 444 deletions composer.lock

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions src/AnnotationGenerator/PhpDocAnnotationGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,27 @@ protected function toPhpDocType(Property $property, bool $adderOrRemover = false
{
$suffix = $property->isNullable ? '|null' : '';
if ($property->isEnum) {
if ($property->isArray) {
if ($property->isArray()) {
return 'string[]'.$suffix;
}

return 'string'.$suffix;
}

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

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

if (!$property->reference) {
return null;
}

$phpDocType = $property->reference->interfaceName() ?: $property->reference->name();
if (!$property->isArray || $adderOrRemover) {
if ($adderOrRemover || !$property->isArray()) {
return $phpDocType.$suffix;
}

Expand Down
6 changes: 3 additions & 3 deletions src/AttributeGenerator/ConstraintAttributeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public function generatePropertyAttributes(Property $property, string $className

$asserts = [];

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

if ($property->isArray) {
if ($property->isArray()) {
$args['multiple'] = true;
}

Expand Down
59 changes: 32 additions & 27 deletions src/AttributeGenerator/DoctrineMongoDBAttributeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use ApiPlatform\SchemaGenerator\Model\Attribute;
use ApiPlatform\SchemaGenerator\Model\Class_;
use ApiPlatform\SchemaGenerator\Model\Property;
use ApiPlatform\SchemaGenerator\Model\Type\CompositeType;
use ApiPlatform\SchemaGenerator\Model\Use_;
use Nette\PhpGenerator\Literal;

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

$type = null;
if ($property->isEnum) {
$type = $property->isArray ? 'simple_array' : 'string';
} elseif ($property->isArray && $property->type) {
$type = $property->isArray() ? 'simple_array' : 'string';
} elseif (!$property->reference && $property->isArray()) {
$type = 'collection';
} elseif (!$property->isArray && $property->type && !$property->reference && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
switch ($property->type) {
case 'time':
$type = 'time';
break;
case 'dateTime':
$type = 'date';
break;
default:
$type = $phpType;
switch ($phpType) {
case 'bool':
$type = 'boolean';
break;
case 'int':
$type = 'integer';
break;
case '\\'.\DateTimeInterface::class:
$type = 'date';
break;
case '\\'.\DateInterval::class:
$type = 'string';
break;
}
break;
} elseif ($property->type && !$property->reference && !$property->isArray() && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
if ($property->type instanceof CompositeType) {
$type = 'raw';
} else {
switch ((string) $property->type) {
case 'time':
$type = 'time';
break;
case 'dateTime':
$type = 'date';
break;
default:
$type = $phpType;
switch ($phpType) {
case 'bool':
$type = 'boolean';
break;
case 'int':
$type = 'integer';
break;
case '\\'.\DateTimeInterface::class:
$type = 'date';
break;
case '\\'.\DateInterval::class:
$type = 'string';
break;
}
break;
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/AttributeGenerator/DoctrineOrmAttributeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use ApiPlatform\SchemaGenerator\Model\Attribute;
use ApiPlatform\SchemaGenerator\Model\Class_;
use ApiPlatform\SchemaGenerator\Model\Property;
use ApiPlatform\SchemaGenerator\Model\Type\CompositeType;
use ApiPlatform\SchemaGenerator\Model\Use_;
use Nette\PhpGenerator\Literal;

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

$type = null;
if ($property->isEnum) {
$type = $property->isArray ? 'simple_array' : 'string';
} elseif ($property->isArray && $property->type) {
$type = $property->isArray() ? 'simple_array' : 'string';
} elseif (!$property->reference && $property->isArray()) {
$type = 'json';
} elseif (!$property->isArray && $property->type && !$property->reference && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
} elseif ($property->type && !$property instanceof CompositeType && !$property->reference && !$property->isArray() && null !== ($phpType = $this->phpTypeConverter->getPhpType($property, $this->config, []))) {
switch ($property->type) {
case 'time':
$type = 'time';
Expand Down
2 changes: 1 addition & 1 deletion src/ClassMutator/AnnotationsAppender.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private function generatePropertiesAnnotations(Class_ $class): void
$property->addGetterAnnotation($getterAnnotation);
}

if ($property->isArray) {
if ($property->isArray()) {
foreach ($annotationGenerator->generateAdderAnnotations($property) as $adderAnnotation) {
$property->addAdderAnnotation($adderAnnotation);
}
Expand Down
5 changes: 3 additions & 2 deletions src/ClassMutator/ClassPropertiesTypehintMutator.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace ApiPlatform\SchemaGenerator\ClassMutator;

use ApiPlatform\SchemaGenerator\Model\Class_;
use ApiPlatform\SchemaGenerator\Model\Type\ArrayType;
use ApiPlatform\SchemaGenerator\PhpTypeConverterInterface;

final class ClassPropertiesTypehintMutator implements ClassMutatorInterface
Expand Down Expand Up @@ -48,9 +49,9 @@ public function __invoke(Class_ $class, array $context): void
$this->classes
);

if ($property->isArray) {
if ($property->type instanceof ArrayType) {
$nonArrayForcedProperty = clone $property;
$nonArrayForcedProperty->isArray = false;
$nonArrayForcedProperty->type = $property->type->type;

$property->adderRemoverTypeHint = $this->phpTypeConverter->getPhpType($nonArrayForcedProperty, $this->config, $this->classes);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Class_.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ public function toNetteFile(array $config, InflectorInterface $inflector, ?PhpFi
}

foreach ($sortedProperties as $property) {
if ($property->isArray && 'array' !== $property->typeHint && !$property->isEnum) {
if (!$property->isEnum && 'array' !== $property->typeHint && $property->isArray()) {
$constructor->addBody('$this->? = new ArrayCollection();', [$property->name()]);
}
}
Expand Down
23 changes: 13 additions & 10 deletions src/Model/Property.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace ApiPlatform\SchemaGenerator\Model;

use ApiPlatform\SchemaGenerator\Model\Type\ArrayType;
use ApiPlatform\SchemaGenerator\Model\Type\Type;
use Nette\PhpGenerator\ClassType;
use Nette\PhpGenerator\Method;
use Nette\PhpGenerator\PhpNamespace;
Expand All @@ -25,14 +27,10 @@ abstract class Property

private string $name;
public string $cardinality;
/** @var ?string the data type (array and object are not one) */
public ?string $type = null;
/** @var ?Type the data types (object is not one) */
public ?Type $type = null;
/** @var mixed */
public $defaultValue = null;
/** @var ?string the array data type (object is not one) */
public ?string $arrayType = null;
/** @var bool can be true and array type false if the property is an array of references */
public bool $isArray = false;
public ?Class_ $reference = null;
public bool $isReadable = true;
public bool $isWritable = true;
Expand Down Expand Up @@ -76,6 +74,11 @@ abstract public function description(): ?string;

abstract public function rdfType(): ?string;

public function isArray(): bool
{
return $this->type instanceof ArrayType;
}

public function addAnnotation(string $annotation): self
{
if ('' === $annotation || !\in_array($annotation, $this->annotations, true)) {
Expand Down Expand Up @@ -146,7 +149,7 @@ public function toNetteProperty(PhpNamespace $namespace, string $visibility = nu
$property->setType($this->resolveName($namespace, $this->typeHint));
}

if (!$this->isArray || $this->isTypeHintedAsCollection()) {
if (!$this->isArray() || $this->isTypeHintedAsCollection()) {
$property->setNullable($this->isNullable);
}

Expand Down Expand Up @@ -206,7 +209,7 @@ private function generateGetter(PhpNamespace $namespace): Method
}
if ($this->typeHint) {
$getter->setReturnType($this->resolveName($namespace, $this->typeHint));
if ($this->isNullable && !$this->isArray) {
if ($this->isNullable && !$this->isArray()) {
$getter->setReturnNullable();
}
}
Expand All @@ -229,7 +232,7 @@ private function generateMutators(
}

$mutators = [];
if ($this->isArray) {
if ($this->isArray()) {
$singularProperty = $singularize($this->name());

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

Expand Down
42 changes: 42 additions & 0 deletions src/Model/Type/ArrayType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\SchemaGenerator\Model\Type;

final class ArrayType implements Type
{
public ?Type $type;

public function __construct(?Type $type = null)
{
$this->type = $type;
}

public function __toString(): string
{
if ($this->type instanceof CompositeType) {
return '('.$this->type.')[]';
}

return $this->type ? $this->type.'[]' : 'array';
}

public function getPhp(): string
{
if ($this->type instanceof CompositeType) {
return '('.$this->type.')[]';
}

return $this->type ? $this->type.'[]' : 'array';
}
}
18 changes: 18 additions & 0 deletions src/Model/Type/CompositeType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\SchemaGenerator\Model\Type;

interface CompositeType extends Type
{
}
34 changes: 34 additions & 0 deletions src/Model/Type/PrimitiveType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\SchemaGenerator\Model\Type;

final class PrimitiveType implements Type
{
public string $name;

public function __construct(string $name)
{
$this->name = $name;
}

public function __toString(): string
{
return $this->name;
}

public function getPhp(): string
{
return $this->name;
}
}
21 changes: 21 additions & 0 deletions src/Model/Type/Type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\SchemaGenerator\Model\Type;

interface Type
{
public function __toString(): string;

public function getPhp(): string;
}

0 comments on commit c783b1d

Please sign in to comment.