From 915ae64491d80a7ec16fa9d70f373c7d19564907 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 15:06:23 +0200 Subject: [PATCH 01/78] Add type-info --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3ff9ab761..62c1657fe 100644 --- a/composer.json +++ b/composer.json @@ -58,6 +58,7 @@ "symfony/twig-bundle": "^5.4 || ^6.4 || ^7.0", "symfony/uid": "^5.4 || ^6.4 || ^7.0", "symfony/validator": "^5.4 || ^6.4 || ^7.0", + "symfony/type-info": "^7.1", "willdurand/hateoas-bundle": "^1.0 || ^2.0" }, "conflict": { @@ -76,7 +77,8 @@ "symfony/serializer": "For describing your models.", "symfony/twig-bundle": "For using the Swagger UI.", "symfony/validator": "For describing the validation constraints in your models.", - "willdurand/hateoas-bundle": "For extracting HATEOAS metadata." + "willdurand/hateoas-bundle": "For extracting HATEOAS metadata.", + "symfony/type-info": "For using the type info extractor." }, "autoload": { "psr-4": { From 99c86c441eadf4e3dacf47cdd23e3309fcc42e2e Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 19:44:20 +0200 Subject: [PATCH 02/78] Implement experimental type-info describers --- src/DependencyInjection/Configuration.php | 4 ++ src/SchemaDescriber/BoolDescriber.php | 34 ++++++++++ src/SchemaDescriber/ChainDescriber.php | 63 +++++++++++++++++++ src/SchemaDescriber/DictionaryDescriber.php | 43 +++++++++++++ src/SchemaDescriber/FloatDescriber.php | 35 +++++++++++ src/SchemaDescriber/IntegerDescriber.php | 34 ++++++++++ src/SchemaDescriber/IntersectionDescriber.php | 45 +++++++++++++ src/SchemaDescriber/ListDescriber.php | 43 +++++++++++++ src/SchemaDescriber/MixedDescriber.php | 35 +++++++++++ src/SchemaDescriber/NullableDescriber.php | 35 +++++++++++ src/SchemaDescriber/ObjectDescriber.php | 43 +++++++++++++ .../SchemaDescriberAwareInterface.php | 20 ++++++ .../SchemaDescriberAwareTrait.php | 25 ++++++++ .../SchemaDescriberInterface.php | 35 +++++++++++ src/SchemaDescriber/StringDescriber.php | 34 ++++++++++ src/SchemaDescriber/UnionDescriber.php | 45 +++++++++++++ 16 files changed, 573 insertions(+) create mode 100644 src/SchemaDescriber/BoolDescriber.php create mode 100644 src/SchemaDescriber/ChainDescriber.php create mode 100644 src/SchemaDescriber/DictionaryDescriber.php create mode 100644 src/SchemaDescriber/FloatDescriber.php create mode 100644 src/SchemaDescriber/IntegerDescriber.php create mode 100644 src/SchemaDescriber/IntersectionDescriber.php create mode 100644 src/SchemaDescriber/ListDescriber.php create mode 100644 src/SchemaDescriber/MixedDescriber.php create mode 100644 src/SchemaDescriber/NullableDescriber.php create mode 100644 src/SchemaDescriber/ObjectDescriber.php create mode 100644 src/SchemaDescriber/SchemaDescriberAwareInterface.php create mode 100644 src/SchemaDescriber/SchemaDescriberAwareTrait.php create mode 100644 src/SchemaDescriber/SchemaDescriberInterface.php create mode 100644 src/SchemaDescriber/StringDescriber.php create mode 100644 src/SchemaDescriber/UnionDescriber.php diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 0b2ec84fb..3ff0d16f9 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -25,6 +25,10 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() + ->booleanNode('experimental_type_info') + ->info('Use the symfony/type-info component for determining types. This is experimental and could be changed at any time without prior notice.') + ->defaultFalse() + ->end() ->booleanNode('use_validation_groups') ->info('If true, `groups` passed to @Model annotations will be used to limit validation constraints') ->defaultFalse() diff --git a/src/SchemaDescriber/BoolDescriber.php b/src/SchemaDescriber/BoolDescriber.php new file mode 100644 index 000000000..43a4223ed --- /dev/null +++ b/src/SchemaDescriber/BoolDescriber.php @@ -0,0 +1,34 @@ + + * + * @experimental + */ +final class BoolDescriber implements SchemaDescriberInterface +{ + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->type = 'boolean'; + } + + public function supports(Type $type, array $context = []): bool + { + return $type->isA(TypeIdentifier::BOOL); + } +} diff --git a/src/SchemaDescriber/ChainDescriber.php b/src/SchemaDescriber/ChainDescriber.php new file mode 100644 index 000000000..e82c01ceb --- /dev/null +++ b/src/SchemaDescriber/ChainDescriber.php @@ -0,0 +1,63 @@ + + * + * @experimental + */ +final class ChainDescriber implements SchemaDescriberInterface +{ + /** @var iterable */ + private iterable $describers; + + /** + * @param iterable $describers + */ + public function __construct( + iterable $describers + ) { + $this->describers = $describers; + } + + public function describe(Type $type, Schema $schema, array $context = []): void + { + foreach ($this->describers as $describer) { + /* BC layer for Symfony < 6.3 @see https://symfony.com/doc/6.3/service_container/tags.html#reference-tagged-services */ + if ($describer instanceof self) { + continue; + } + + // TODO: Implement proper dependency injection + // if ($describer instanceof ModelRegistryAwareInterface) { + // $describer->setModelRegistry($this->modelRegistry); + // } + + if ($describer instanceof SchemaDescriberAwareInterface) { + $describer->setDescriber($this); + } + + if ($describer->supports($type, $context)) { + $describer->describe($type, $schema, $context); + } + } + } + + public function supports(Type $type, array $context = []): bool + { + return true; + } +} diff --git a/src/SchemaDescriber/DictionaryDescriber.php b/src/SchemaDescriber/DictionaryDescriber.php new file mode 100644 index 000000000..c3bcf9ccf --- /dev/null +++ b/src/SchemaDescriber/DictionaryDescriber.php @@ -0,0 +1,43 @@ + + * + * @experimental + */ +final class DictionaryDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +{ + use SchemaDescriberAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->type = 'object'; + $additionalProperties = Util::getChild($schema, OA\AdditionalProperties::class); + + $this->describer->describe($type->getCollectionValueType(), $additionalProperties, $context); + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof CollectionType + && $type->getCollectionKeyType()->isA(TypeIdentifier::STRING); + } +} diff --git a/src/SchemaDescriber/FloatDescriber.php b/src/SchemaDescriber/FloatDescriber.php new file mode 100644 index 000000000..96941b715 --- /dev/null +++ b/src/SchemaDescriber/FloatDescriber.php @@ -0,0 +1,35 @@ + + * + * @experimental + */ +final class FloatDescriber implements SchemaDescriberInterface +{ + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->type = 'number'; + $schema->format = 'float'; + } + + public function supports(Type $type, array $context = []): bool + { + return $type->isA(TypeIdentifier::FLOAT); + } +} diff --git a/src/SchemaDescriber/IntegerDescriber.php b/src/SchemaDescriber/IntegerDescriber.php new file mode 100644 index 000000000..9e5f933ce --- /dev/null +++ b/src/SchemaDescriber/IntegerDescriber.php @@ -0,0 +1,34 @@ + + * + * @experimental + */ +final class IntegerDescriber implements SchemaDescriberInterface +{ + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->type = 'integer'; + } + + public function supports(Type $type, array $context = []): bool + { + return $type->isA(TypeIdentifier::INT); + } +} diff --git a/src/SchemaDescriber/IntersectionDescriber.php b/src/SchemaDescriber/IntersectionDescriber.php new file mode 100644 index 000000000..69baa54a1 --- /dev/null +++ b/src/SchemaDescriber/IntersectionDescriber.php @@ -0,0 +1,45 @@ + + * + * @experimental + */ +final class IntersectionDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +{ + use SchemaDescriberAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $weakContext = Util::createWeakContext($schema->_context); + + foreach ($type->getTypes() as $innerType) { + $schema->oneOf[] = $childSchema = new Schema([ + '_context' => $weakContext + ]); + + $this->describer->describe($innerType, $childSchema, $context); + } + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof IntersectionType; + } +} diff --git a/src/SchemaDescriber/ListDescriber.php b/src/SchemaDescriber/ListDescriber.php new file mode 100644 index 000000000..5c1b35bcf --- /dev/null +++ b/src/SchemaDescriber/ListDescriber.php @@ -0,0 +1,43 @@ + + * + * @experimental + */ +final class ListDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +{ + use SchemaDescriberAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->type = 'array'; + $item = Util::getChild($schema, OA\Items::class); + + $this->describer->describe($type->getCollectionValueType(), $item, $context); + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof CollectionType + && $type->getCollectionKeyType()->isA(TypeIdentifier::INT); + } +} diff --git a/src/SchemaDescriber/MixedDescriber.php b/src/SchemaDescriber/MixedDescriber.php new file mode 100644 index 000000000..f24338c4d --- /dev/null +++ b/src/SchemaDescriber/MixedDescriber.php @@ -0,0 +1,35 @@ + + * + * @experimental + */ +final class MixedDescriber implements SchemaDescriberInterface +{ + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->type = Generator::UNDEFINED; + } + + public function supports(Type $type, array $context = []): bool + { + return $type->isA(TypeIdentifier::MIXED); + } +} diff --git a/src/SchemaDescriber/NullableDescriber.php b/src/SchemaDescriber/NullableDescriber.php new file mode 100644 index 000000000..3b46ae981 --- /dev/null +++ b/src/SchemaDescriber/NullableDescriber.php @@ -0,0 +1,35 @@ + + * + * @experimental + */ +final class NullableDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +{ + use SchemaDescriberAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->nullable = true; + } + + public function supports(Type $type, array $context = []): bool + { + return $type->isNullable(); + } +} diff --git a/src/SchemaDescriber/ObjectDescriber.php b/src/SchemaDescriber/ObjectDescriber.php new file mode 100644 index 000000000..ceb84839e --- /dev/null +++ b/src/SchemaDescriber/ObjectDescriber.php @@ -0,0 +1,43 @@ + + * + * @experimental + */ +final class ObjectDescriber implements SchemaDescriberInterface +{ + private ModelRegistry $modelRegistry; + + public function __construct(ModelRegistry $modelRegistry) + { + $this->modelRegistry = $modelRegistry; + } + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->ref = $this->modelRegistry->register(new Model($type->getClassName(), null, null, $context)); + } + + public function supports(Type $type, array $context = []): bool + { + return $type->isA(TypeIdentifier::OBJECT); + } +} diff --git a/src/SchemaDescriber/SchemaDescriberAwareInterface.php b/src/SchemaDescriber/SchemaDescriberAwareInterface.php new file mode 100644 index 000000000..beb7f8759 --- /dev/null +++ b/src/SchemaDescriber/SchemaDescriberAwareInterface.php @@ -0,0 +1,20 @@ +describer = $describer; + } +} diff --git a/src/SchemaDescriber/SchemaDescriberInterface.php b/src/SchemaDescriber/SchemaDescriberInterface.php new file mode 100644 index 000000000..060fa9e9f --- /dev/null +++ b/src/SchemaDescriber/SchemaDescriberInterface.php @@ -0,0 +1,35 @@ + $context Context options for describing the property + */ + public function describe(Type $type, Schema $schema, array $context = []): void; + + /** + * @param T $type + * @param array $context Context options for describing the property + */ + public function supports(Type $type, array $context = []): bool; +} diff --git a/src/SchemaDescriber/StringDescriber.php b/src/SchemaDescriber/StringDescriber.php new file mode 100644 index 000000000..8b4a403f6 --- /dev/null +++ b/src/SchemaDescriber/StringDescriber.php @@ -0,0 +1,34 @@ + + * + * @experimental + */ +final class StringDescriber implements SchemaDescriberInterface +{ + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->type = 'string'; + } + + public function supports(Type $type, array $context = []): bool + { + return $type->isA(TypeIdentifier::STRING); + } +} diff --git a/src/SchemaDescriber/UnionDescriber.php b/src/SchemaDescriber/UnionDescriber.php new file mode 100644 index 000000000..fcdecedf9 --- /dev/null +++ b/src/SchemaDescriber/UnionDescriber.php @@ -0,0 +1,45 @@ + + * + * @experimental + */ +final class UnionDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +{ + use SchemaDescriberAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $weakContext = Util::createWeakContext($schema->_context); + + foreach ($type->getTypes() as $innerType) { + $schema->allOf[] = $childSchema = new Schema([ + '_context' => $weakContext + ]); + + $this->describer->describe($innerType, $childSchema, $context); + } + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof UnionType; + } +} From 95dfbe9f334f6c72f626185504286e059f3b66e0 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 19:56:25 +0200 Subject: [PATCH 03/78] Register services for experimental type-info describers --- config/services.xml | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/config/services.xml b/config/services.xml index 3382906f2..134c79360 100644 --- a/config/services.xml +++ b/config/services.xml @@ -165,6 +165,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4c68692ac9ba892c73e264d6b02a0de6147e7b54 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 21:18:34 +0200 Subject: [PATCH 04/78] Also check instanceof type --- src/SchemaDescriber/BoolDescriber.php | 3 ++- src/SchemaDescriber/ChainDescriber.php | 13 +++++++---- src/SchemaDescriber/FloatDescriber.php | 3 ++- src/SchemaDescriber/IntegerDescriber.php | 3 ++- src/SchemaDescriber/IntersectionDescriber.php | 2 ++ src/SchemaDescriber/MixedDescriber.php | 3 ++- src/SchemaDescriber/ObjectDescriber.php | 23 ++++++++++--------- src/SchemaDescriber/StringDescriber.php | 3 ++- src/SchemaDescriber/UnionDescriber.php | 2 ++ 9 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/SchemaDescriber/BoolDescriber.php b/src/SchemaDescriber/BoolDescriber.php index 43a4223ed..f62c22422 100644 --- a/src/SchemaDescriber/BoolDescriber.php +++ b/src/SchemaDescriber/BoolDescriber.php @@ -29,6 +29,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { - return $type->isA(TypeIdentifier::BOOL); + return $type instanceof Type\BuiltinType + && $type->isA(TypeIdentifier::BOOL); } } diff --git a/src/SchemaDescriber/ChainDescriber.php b/src/SchemaDescriber/ChainDescriber.php index e82c01ceb..394d178c4 100644 --- a/src/SchemaDescriber/ChainDescriber.php +++ b/src/SchemaDescriber/ChainDescriber.php @@ -11,6 +11,8 @@ namespace Nelmio\ApiDocBundle\SchemaDescriber; +use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; +use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; @@ -19,8 +21,10 @@ * * @experimental */ -final class ChainDescriber implements SchemaDescriberInterface +final class ChainDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface { + use ModelRegistryAwareTrait; + /** @var iterable */ private iterable $describers; @@ -41,10 +45,9 @@ public function describe(Type $type, Schema $schema, array $context = []): void continue; } - // TODO: Implement proper dependency injection - // if ($describer instanceof ModelRegistryAwareInterface) { - // $describer->setModelRegistry($this->modelRegistry); - // } + if ($describer instanceof ModelRegistryAwareInterface) { + $describer->setModelRegistry($this->modelRegistry); + } if ($describer instanceof SchemaDescriberAwareInterface) { $describer->setDescriber($this); diff --git a/src/SchemaDescriber/FloatDescriber.php b/src/SchemaDescriber/FloatDescriber.php index 96941b715..2f37f5c9f 100644 --- a/src/SchemaDescriber/FloatDescriber.php +++ b/src/SchemaDescriber/FloatDescriber.php @@ -30,6 +30,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { - return $type->isA(TypeIdentifier::FLOAT); + return $type instanceof Type\BuiltinType + && $type->isA(TypeIdentifier::FLOAT); } } diff --git a/src/SchemaDescriber/IntegerDescriber.php b/src/SchemaDescriber/IntegerDescriber.php index 9e5f933ce..6241df3b8 100644 --- a/src/SchemaDescriber/IntegerDescriber.php +++ b/src/SchemaDescriber/IntegerDescriber.php @@ -29,6 +29,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { - return $type->isA(TypeIdentifier::INT); + return $type instanceof Type\BuiltinType + && $type->isA(TypeIdentifier::INT); } } diff --git a/src/SchemaDescriber/IntersectionDescriber.php b/src/SchemaDescriber/IntersectionDescriber.php index 69baa54a1..ab4723587 100644 --- a/src/SchemaDescriber/IntersectionDescriber.php +++ b/src/SchemaDescriber/IntersectionDescriber.php @@ -13,6 +13,7 @@ use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations\Schema; +use OpenApi\Generator; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\IntersectionType; @@ -29,6 +30,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void { $weakContext = Util::createWeakContext($schema->_context); + $schema->oneOf = Generator::UNDEFINED !== $schema->oneOf ? $schema->oneOf : []; foreach ($type->getTypes() as $innerType) { $schema->oneOf[] = $childSchema = new Schema([ '_context' => $weakContext diff --git a/src/SchemaDescriber/MixedDescriber.php b/src/SchemaDescriber/MixedDescriber.php index f24338c4d..6c3860300 100644 --- a/src/SchemaDescriber/MixedDescriber.php +++ b/src/SchemaDescriber/MixedDescriber.php @@ -30,6 +30,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { - return $type->isA(TypeIdentifier::MIXED); + return $type instanceof Type\BuiltinType + && $type->isA(TypeIdentifier::MIXED); } } diff --git a/src/SchemaDescriber/ObjectDescriber.php b/src/SchemaDescriber/ObjectDescriber.php index ceb84839e..8cc7d7998 100644 --- a/src/SchemaDescriber/ObjectDescriber.php +++ b/src/SchemaDescriber/ObjectDescriber.php @@ -11,33 +11,34 @@ namespace Nelmio\ApiDocBundle\SchemaDescriber; +use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; +use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; use Nelmio\ApiDocBundle\Model\Model; -use Nelmio\ApiDocBundle\Model\ModelRegistry; use OpenApi\Annotations\Schema; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements SchemaDescriberInterface * * @experimental */ -final class ObjectDescriber implements SchemaDescriberInterface +final class ObjectDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface { - private ModelRegistry $modelRegistry; - - public function __construct(ModelRegistry $modelRegistry) - { - $this->modelRegistry = $modelRegistry; - } + use ModelRegistryAwareTrait; public function describe(Type $type, Schema $schema, array $context = []): void { - $schema->ref = $this->modelRegistry->register(new Model($type->getClassName(), null, null, $context)); + $schema->ref = $this->modelRegistry->register( + new Model(new LegacyType('object', false, $type->getClassName()), null, null, $context) + ); } public function supports(Type $type, array $context = []): bool { - return $type->isA(TypeIdentifier::OBJECT); + return $type instanceof ObjectType + && $type->isA(TypeIdentifier::OBJECT); } } diff --git a/src/SchemaDescriber/StringDescriber.php b/src/SchemaDescriber/StringDescriber.php index 8b4a403f6..e9cdf157c 100644 --- a/src/SchemaDescriber/StringDescriber.php +++ b/src/SchemaDescriber/StringDescriber.php @@ -29,6 +29,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { - return $type->isA(TypeIdentifier::STRING); + return $type instanceof Type\BuiltinType + && $type->isA(TypeIdentifier::STRING); } } diff --git a/src/SchemaDescriber/UnionDescriber.php b/src/SchemaDescriber/UnionDescriber.php index fcdecedf9..89cbee016 100644 --- a/src/SchemaDescriber/UnionDescriber.php +++ b/src/SchemaDescriber/UnionDescriber.php @@ -13,6 +13,7 @@ use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations\Schema; +use OpenApi\Generator; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\UnionType; @@ -29,6 +30,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void { $weakContext = Util::createWeakContext($schema->_context); + $schema->allOf = Generator::UNDEFINED !== $schema->allOf ? $schema->allOf : []; foreach ($type->getTypes() as $innerType) { $schema->allOf[] = $childSchema = new Schema([ '_context' => $weakContext From 58ddc64dee8a0244961195c0c099a343abaf592b Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 21:19:11 +0200 Subject: [PATCH 05/78] Implement experimental behaviour to ObjectModelDescriber --- .../NelmioApiDocExtension.php | 5 +++ src/ModelDescriber/ObjectModelDescriber.php | 45 ++++++++++++++----- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/DependencyInjection/NelmioApiDocExtension.php b/src/DependencyInjection/NelmioApiDocExtension.php index d3398e8ac..e26e1ff9a 100644 --- a/src/DependencyInjection/NelmioApiDocExtension.php +++ b/src/DependencyInjection/NelmioApiDocExtension.php @@ -164,6 +164,11 @@ public function load(array $configs, ContainerBuilder $container): void array_map(function ($area) { return new Reference(sprintf('nelmio_api_doc.generator.%s', $area)); }, array_keys($config['areas'])) )); + if (true === $config['experimental_type_info']) { + $container->getDefinition('nelmio_api_doc.model_describers.object') + ->setArgument(2, new Reference('nelmio_api_doc.schema_describer.chain')); + } + $container->getDefinition('nelmio_api_doc.model_describers.object') ->setArgument(3, $config['media_types']); diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index f119bdef0..6ff7ed071 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -18,6 +18,7 @@ use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use Nelmio\ApiDocBundle\PropertyDescriber\PropertyDescriberInterface; +use Nelmio\ApiDocBundle\SchemaDescriber\SchemaDescriberInterface; use OpenApi\Annotations as OA; use OpenApi\Generator; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; @@ -34,7 +35,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar private PropertyInfoExtractorInterface $propertyInfo; private ?ClassMetadataFactoryInterface $classMetadataFactory; private ?Reader $doctrineReader; - /** @var PropertyDescriberInterface|PropertyDescriberInterface[] */ + /** @var PropertyDescriberInterface|PropertyDescriberInterface[]|SchemaDescriberInterface */ private $propertyDescriber; /** @var string[] */ private array $mediaTypes; @@ -43,9 +44,9 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar private bool $useValidationGroups; /** - * @param PropertyDescriberInterface|PropertyDescriberInterface[] $propertyDescribers - * @param (NameConverterInterface&AdvancedNameConverterInterface)|null $nameConverter - * @param string[] $mediaTypes + * @param PropertyDescriberInterface|PropertyDescriberInterface[]|SchemaDescriberInterface $propertyDescribers + * @param (NameConverterInterface&AdvancedNameConverterInterface)|null $nameConverter + * @param string[] $mediaTypes */ public function __construct( PropertyInfoExtractorInterface $propertyInfo, @@ -59,7 +60,7 @@ public function __construct( if (is_iterable($propertyDescribers)) { trigger_deprecation('nelmio/api-doc-bundle', '4.17', 'Passing an array of PropertyDescriberInterface to %s() is deprecated. Pass a single PropertyDescriberInterface instead.', __METHOD__); } else { - if (!$propertyDescribers instanceof PropertyDescriberInterface) { + if (!$propertyDescribers instanceof PropertyDescriberInterface && !$propertyDescribers instanceof SchemaDescriberInterface) { throw new \InvalidArgumentException(sprintf('Argument 3 passed to %s() must be an array of %s or a single %s.', __METHOD__, PropertyDescriberInterface::class, PropertyDescriberInterface::class)); } } @@ -175,12 +176,36 @@ public function describe(Model $model, OA\Schema $schema) continue; } - $types = $this->propertyInfo->getTypes($class, $propertyName); - if (null === $types || 0 === count($types)) { - throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); + /* + * @experimental + */ + if ($this->propertyDescriber instanceof SchemaDescriberInterface) { + if (false === method_exists($this->propertyInfo, 'getType')) { + throw new \RuntimeException('The PropertyInfo component is missing the "getType" method. Are you running on version 7.1?'); + } + + $type = $this->propertyInfo->getType($class, $propertyName, $context); + if (null === $type) { + throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); + } + + if ($this->propertyDescriber instanceof ModelRegistryAwareInterface) { + $this->propertyDescriber->setModelRegistry($this->modelRegistry); + } + + if (!$this->propertyDescriber->supports($type, $context)) { + throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $type->__toString(), $model->getType()->getClassName(), $propertyName)); + } + + $this->propertyDescriber->describe($type, $property, $context); + } else { + $types = $this->propertyInfo->getTypes($class, $propertyName); + if (null === $types || 0 === count($types)) { + throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); + } + + $this->describeProperty($types, $model, $property, $propertyName, $schema); } - - $this->describeProperty($types, $model, $property, $propertyName, $schema); } } From 2c97df7897056b30bbcade3c85af5400ea1c301e Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 21:26:04 +0200 Subject: [PATCH 06/78] Implement class property describers as model describers --- config/services.xml | 8 +++++ src/ModelDescriber/DateTimeModelDescriber.php | 31 ++++++++++++++++++ src/ModelDescriber/UuidModelDescriber.php | 32 +++++++++++++++++++ .../DateTimePropertyDescriber.php | 4 +++ .../UuidPropertyDescriber.php | 3 ++ 5 files changed, 78 insertions(+) create mode 100644 src/ModelDescriber/DateTimeModelDescriber.php create mode 100644 src/ModelDescriber/UuidModelDescriber.php diff --git a/config/services.xml b/config/services.xml index 134c79360..d4f046683 100644 --- a/config/services.xml +++ b/config/services.xml @@ -96,6 +96,14 @@ + + + + + + + + diff --git a/src/ModelDescriber/DateTimeModelDescriber.php b/src/ModelDescriber/DateTimeModelDescriber.php new file mode 100644 index 000000000..8e8e2397a --- /dev/null +++ b/src/ModelDescriber/DateTimeModelDescriber.php @@ -0,0 +1,31 @@ +type = 'string'; + $schema->format = 'date-time'; + } + + public function supports(Model $model): bool + { + return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() + && is_a($model->getType()->getClassName(), \DateTimeInterface::class, true); + } +} diff --git a/src/ModelDescriber/UuidModelDescriber.php b/src/ModelDescriber/UuidModelDescriber.php new file mode 100644 index 000000000..c712177cd --- /dev/null +++ b/src/ModelDescriber/UuidModelDescriber.php @@ -0,0 +1,32 @@ +type = 'string'; + $schema->format = 'uuid'; + } + + public function supports(Model $model): bool + { + return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() + && is_a($model->getType()->getClassName(), AbstractUid::class, true); + } +} diff --git a/src/PropertyDescriber/DateTimePropertyDescriber.php b/src/PropertyDescriber/DateTimePropertyDescriber.php index dcddb7fbf..65e4c3db4 100644 --- a/src/PropertyDescriber/DateTimePropertyDescriber.php +++ b/src/PropertyDescriber/DateTimePropertyDescriber.php @@ -11,9 +11,13 @@ namespace Nelmio\ApiDocBundle\PropertyDescriber; +use Nelmio\ApiDocBundle\ModelDescriber\DateTimeModelDescriber; use OpenApi\Annotations as OA; use Symfony\Component\PropertyInfo\Type; +/** + * @deprecated in favor of a model describer {@see DateTimeModelDescriber} + */ class DateTimePropertyDescriber implements PropertyDescriberInterface { /** diff --git a/src/PropertyDescriber/UuidPropertyDescriber.php b/src/PropertyDescriber/UuidPropertyDescriber.php index 843d4ef63..05b6ab81c 100644 --- a/src/PropertyDescriber/UuidPropertyDescriber.php +++ b/src/PropertyDescriber/UuidPropertyDescriber.php @@ -15,6 +15,9 @@ use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Uid\AbstractUid; +/** + * @deprecated in favor of a model describer {@see UuidModelDescriber} + */ final class UuidPropertyDescriber implements PropertyDescriberInterface { /** From 41d5c1c457287c265104e165add4ece8bd26dc7a Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 21:32:39 +0200 Subject: [PATCH 07/78] Fix union & intersection using wrong property --- src/SchemaDescriber/IntersectionDescriber.php | 4 ++-- src/SchemaDescriber/UnionDescriber.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SchemaDescriber/IntersectionDescriber.php b/src/SchemaDescriber/IntersectionDescriber.php index ab4723587..18ebb185e 100644 --- a/src/SchemaDescriber/IntersectionDescriber.php +++ b/src/SchemaDescriber/IntersectionDescriber.php @@ -30,9 +30,9 @@ public function describe(Type $type, Schema $schema, array $context = []): void { $weakContext = Util::createWeakContext($schema->_context); - $schema->oneOf = Generator::UNDEFINED !== $schema->oneOf ? $schema->oneOf : []; + $schema->allOf = Generator::UNDEFINED !== $schema->allOf ? $schema->allOf : []; foreach ($type->getTypes() as $innerType) { - $schema->oneOf[] = $childSchema = new Schema([ + $schema->allOf[] = $childSchema = new Schema([ '_context' => $weakContext ]); diff --git a/src/SchemaDescriber/UnionDescriber.php b/src/SchemaDescriber/UnionDescriber.php index 89cbee016..ef81d86ce 100644 --- a/src/SchemaDescriber/UnionDescriber.php +++ b/src/SchemaDescriber/UnionDescriber.php @@ -30,9 +30,9 @@ public function describe(Type $type, Schema $schema, array $context = []): void { $weakContext = Util::createWeakContext($schema->_context); - $schema->allOf = Generator::UNDEFINED !== $schema->allOf ? $schema->allOf : []; + $schema->oneOf = Generator::UNDEFINED !== $schema->oneOf ? $schema->oneOf : []; foreach ($type->getTypes() as $innerType) { - $schema->allOf[] = $childSchema = new Schema([ + $schema->oneOf[] = $childSchema = new Schema([ '_context' => $weakContext ]); From c462cbe37f62f16295b4ad76ce86eb80fcbb52f3 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 22:46:01 +0200 Subject: [PATCH 08/78] Implement free-form object describer --- config/services.xml | 4 ++ src/SchemaDescriber/ObjectClassDescriber.php | 44 ++++++++++++++++++++ src/SchemaDescriber/ObjectDescriber.php | 13 +++--- 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/SchemaDescriber/ObjectClassDescriber.php diff --git a/config/services.xml b/config/services.xml index d4f046683..e05d20b71 100644 --- a/config/services.xml +++ b/config/services.xml @@ -213,6 +213,10 @@ + + + + diff --git a/src/SchemaDescriber/ObjectClassDescriber.php b/src/SchemaDescriber/ObjectClassDescriber.php new file mode 100644 index 000000000..5d7f73745 --- /dev/null +++ b/src/SchemaDescriber/ObjectClassDescriber.php @@ -0,0 +1,44 @@ + + * + * @experimental + */ +final class ObjectClassDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface +{ + use ModelRegistryAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->ref = $this->modelRegistry->register( + new Model(new LegacyType('object', false, $type->getClassName()), null, null, $context) + ); + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof ObjectType + && $type->isA(TypeIdentifier::OBJECT); + } +} diff --git a/src/SchemaDescriber/ObjectDescriber.php b/src/SchemaDescriber/ObjectDescriber.php index 8cc7d7998..193e03b0d 100644 --- a/src/SchemaDescriber/ObjectDescriber.php +++ b/src/SchemaDescriber/ObjectDescriber.php @@ -13,15 +13,13 @@ use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; -use Nelmio\ApiDocBundle\Model\Model; use OpenApi\Annotations\Schema; -use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; -use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements SchemaDescriberInterface * * @experimental */ @@ -31,14 +29,13 @@ final class ObjectDescriber implements SchemaDescriberInterface, ModelRegistryAw public function describe(Type $type, Schema $schema, array $context = []): void { - $schema->ref = $this->modelRegistry->register( - new Model(new LegacyType('object', false, $type->getClassName()), null, null, $context) - ); + $schema->type = 'object'; + $schema->additionalProperties = true; } public function supports(Type $type, array $context = []): bool { - return $type instanceof ObjectType + return $type instanceof BuiltinType && $type->isA(TypeIdentifier::OBJECT); } } From d5387076aad5b3d7e92d1f19b05d7e476d248cf3 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 22:47:07 +0200 Subject: [PATCH 09/78] Implement enum describer --- config/services.xml | 4 +++ src/SchemaDescriber/EnumDescriber.php | 42 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/SchemaDescriber/EnumDescriber.php diff --git a/config/services.xml b/config/services.xml index e05d20b71..ad8ce9974 100644 --- a/config/services.xml +++ b/config/services.xml @@ -189,6 +189,10 @@ + + + + diff --git a/src/SchemaDescriber/EnumDescriber.php b/src/SchemaDescriber/EnumDescriber.php new file mode 100644 index 000000000..c6bf4ba11 --- /dev/null +++ b/src/SchemaDescriber/EnumDescriber.php @@ -0,0 +1,42 @@ + + * + * @experimental + */ +final class EnumDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface +{ + use ModelRegistryAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + $schema->ref = $this->modelRegistry->register( + new Model(new LegacyType('object', false, $type->getClassName()), null, null, $context) + ); + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof EnumType; + } +} From 99f1491690d47f69b08175d8beca105c03923218 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 23:02:31 +0200 Subject: [PATCH 10/78] Fix union & intersection generating invalid spec --- src/SchemaDescriber/IntersectionDescriber.php | 18 ++++++++++++++++-- src/SchemaDescriber/UnionDescriber.php | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/SchemaDescriber/IntersectionDescriber.php b/src/SchemaDescriber/IntersectionDescriber.php index 18ebb185e..8eaebdc0c 100644 --- a/src/SchemaDescriber/IntersectionDescriber.php +++ b/src/SchemaDescriber/IntersectionDescriber.php @@ -16,6 +16,7 @@ use OpenApi\Generator; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * @implements SchemaDescriberInterface @@ -28,10 +29,23 @@ final class IntersectionDescriber implements SchemaDescriberInterface, SchemaDes public function describe(Type $type, Schema $schema, array $context = []): void { + $innerTypes = array_values(array_filter($type->getTypes(), function (Type $innerType) { + return !$innerType->isA(TypeIdentifier::NULL); + })); + + // Ensure that non $ref schemas are not described in allOf + if (1 === count($innerTypes) && !$type instanceof Type\ObjectType && !$type instanceof Type\EnumType) { + $this->describer->describe($innerTypes[0], $schema, $context); + + return; + } + $weakContext = Util::createWeakContext($schema->_context); + foreach ($innerTypes as $innerType) { + if (Generator::UNDEFINED === $schema->allOf) { + $schema->allOf = []; + } - $schema->allOf = Generator::UNDEFINED !== $schema->allOf ? $schema->allOf : []; - foreach ($type->getTypes() as $innerType) { $schema->allOf[] = $childSchema = new Schema([ '_context' => $weakContext ]); diff --git a/src/SchemaDescriber/UnionDescriber.php b/src/SchemaDescriber/UnionDescriber.php index ef81d86ce..c23db0375 100644 --- a/src/SchemaDescriber/UnionDescriber.php +++ b/src/SchemaDescriber/UnionDescriber.php @@ -16,6 +16,7 @@ use OpenApi\Generator; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * @implements SchemaDescriberInterface @@ -28,10 +29,23 @@ final class UnionDescriber implements SchemaDescriberInterface, SchemaDescriberA public function describe(Type $type, Schema $schema, array $context = []): void { + $innerTypes = array_values(array_filter($type->getTypes(), function (Type $innerType) { + return !$innerType->isA(TypeIdentifier::NULL); + })); + + // Ensure that non $ref schemas are not described in oneOf + if (1 === count($innerTypes) && !$type instanceof Type\ObjectType && !$type instanceof Type\EnumType) { + $this->describer->describe($innerTypes[0], $schema, $context); + + return; + } + $weakContext = Util::createWeakContext($schema->_context); + foreach ($innerTypes as $innerType) { + if (Generator::UNDEFINED === $schema->oneOf) { + $schema->oneOf = []; + } - $schema->oneOf = Generator::UNDEFINED !== $schema->oneOf ? $schema->oneOf : []; - foreach ($type->getTypes() as $innerType) { $schema->oneOf[] = $childSchema = new Schema([ '_context' => $weakContext ]); From b8c68c7a99e1777d880196425763ecee234bc8ad Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 4 Oct 2024 23:23:12 +0200 Subject: [PATCH 11/78] Remove enum describer --- config/services.xml | 4 -- src/SchemaDescriber/EnumDescriber.php | 42 ------------------- src/SchemaDescriber/IntersectionDescriber.php | 2 +- src/SchemaDescriber/UnionDescriber.php | 2 +- 4 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 src/SchemaDescriber/EnumDescriber.php diff --git a/config/services.xml b/config/services.xml index ad8ce9974..e05d20b71 100644 --- a/config/services.xml +++ b/config/services.xml @@ -189,10 +189,6 @@ - - - - diff --git a/src/SchemaDescriber/EnumDescriber.php b/src/SchemaDescriber/EnumDescriber.php deleted file mode 100644 index c6bf4ba11..000000000 --- a/src/SchemaDescriber/EnumDescriber.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * @experimental - */ -final class EnumDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface -{ - use ModelRegistryAwareTrait; - - public function describe(Type $type, Schema $schema, array $context = []): void - { - $schema->ref = $this->modelRegistry->register( - new Model(new LegacyType('object', false, $type->getClassName()), null, null, $context) - ); - } - - public function supports(Type $type, array $context = []): bool - { - return $type instanceof EnumType; - } -} diff --git a/src/SchemaDescriber/IntersectionDescriber.php b/src/SchemaDescriber/IntersectionDescriber.php index 8eaebdc0c..f5104224b 100644 --- a/src/SchemaDescriber/IntersectionDescriber.php +++ b/src/SchemaDescriber/IntersectionDescriber.php @@ -34,7 +34,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void })); // Ensure that non $ref schemas are not described in allOf - if (1 === count($innerTypes) && !$type instanceof Type\ObjectType && !$type instanceof Type\EnumType) { + if (1 === count($innerTypes) && !$innerTypes[0] instanceof Type\ObjectType) { $this->describer->describe($innerTypes[0], $schema, $context); return; diff --git a/src/SchemaDescriber/UnionDescriber.php b/src/SchemaDescriber/UnionDescriber.php index c23db0375..f64cafafa 100644 --- a/src/SchemaDescriber/UnionDescriber.php +++ b/src/SchemaDescriber/UnionDescriber.php @@ -34,7 +34,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void })); // Ensure that non $ref schemas are not described in oneOf - if (1 === count($innerTypes) && !$type instanceof Type\ObjectType && !$type instanceof Type\EnumType) { + if (1 === count($innerTypes) && !$innerTypes[0] instanceof Type\ObjectType) { $this->describer->describe($innerTypes[0], $schema, $context); return; From d41df52f7167e8a260bdafa527b1c695dfc2a173 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 5 Oct 2024 00:24:09 +0200 Subject: [PATCH 12/78] Remove unnecessary result class --- .../Annotations/AnnotationsReader.php | 6 +-- .../UpdateClassDefinitionResult.php | 41 ------------------- src/ModelDescriber/FormModelDescriber.php | 2 +- src/ModelDescriber/JMSModelDescriber.php | 2 +- src/ModelDescriber/ObjectModelDescriber.php | 2 +- 5 files changed, 5 insertions(+), 48 deletions(-) delete mode 100644 src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php diff --git a/src/ModelDescriber/Annotations/AnnotationsReader.php b/src/ModelDescriber/Annotations/AnnotationsReader.php index df1597263..583c2c58a 100644 --- a/src/ModelDescriber/Annotations/AnnotationsReader.php +++ b/src/ModelDescriber/Annotations/AnnotationsReader.php @@ -42,14 +42,12 @@ public function __construct( ); } - public function updateDefinition(\ReflectionClass $reflectionClass, OA\Schema $schema): UpdateClassDefinitionResult + public function updateDefinition(\ReflectionClass $reflectionClass, OA\Schema $schema): bool { $this->openApiAnnotationsReader->updateSchema($reflectionClass, $schema); $this->symfonyConstraintAnnotationReader->setSchema($schema); - return new UpdateClassDefinitionResult( - $this->shouldDescribeModelProperties($schema) - ); + return $this->shouldDescribeModelProperties($schema); } /** diff --git a/src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php b/src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php deleted file mode 100644 index 1cafbd4f2..000000000 --- a/src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php +++ /dev/null @@ -1,41 +0,0 @@ -shouldDescribeModelProperties = $shouldDescribeModelProperties; - } - - public function shouldDescribeModelProperties(): bool - { - return $this->shouldDescribeModelProperties; - } -} diff --git a/src/ModelDescriber/FormModelDescriber.php b/src/ModelDescriber/FormModelDescriber.php index 5ad00753b..c74fad47f 100644 --- a/src/ModelDescriber/FormModelDescriber.php +++ b/src/ModelDescriber/FormModelDescriber.php @@ -91,7 +91,7 @@ public function describe(Model $model, OA\Schema $schema): void ); $classResult = $annotationsReader->updateDefinition(new \ReflectionClass($class), $schema); - if (!$classResult->shouldDescribeModelProperties()) { + if (!$classResult) { return; } diff --git a/src/ModelDescriber/JMSModelDescriber.php b/src/ModelDescriber/JMSModelDescriber.php index 2a677ddd4..49da02fb2 100644 --- a/src/ModelDescriber/JMSModelDescriber.php +++ b/src/ModelDescriber/JMSModelDescriber.php @@ -119,7 +119,7 @@ public function describe(Model $model, OA\Schema $schema) ); $classResult = $annotationsReader->updateDefinition(new \ReflectionClass($className), $schema); - if (!$classResult->shouldDescribeModelProperties()) { + if (!$classResult) { return; } $schema->type = 'object'; diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index 6ff7ed071..bd5a8a05a 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -93,7 +93,7 @@ public function describe(Model $model, OA\Schema $schema) ); $classResult = $annotationsReader->updateDefinition($reflClass, $schema); - if (!$classResult->shouldDescribeModelProperties()) { + if (!$classResult) { return; } From f1b4bef28e9cd848e363ec56a6871d6ed1a069f0 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 8 Oct 2024 11:35:33 +0200 Subject: [PATCH 13/78] Deprecate RequiredPropertyDescriber --- config/services.xml | 4 ---- src/ModelDescriber/ObjectModelDescriber.php | 20 +++++++++++++++++++ .../RequiredPropertyDescriber.php | 3 +++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/config/services.xml b/config/services.xml index e05d20b71..93c4515d6 100644 --- a/config/services.xml +++ b/config/services.xml @@ -147,10 +147,6 @@ - - - - diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index bd5a8a05a..2ddf29905 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -207,6 +207,8 @@ public function describe(Model $model, OA\Schema $schema) $this->describeProperty($types, $model, $property, $propertyName, $schema); } } + + $this->markRequiredProperties($schema); } /** @@ -258,6 +260,24 @@ private function describeProperty(array $types, Model $model, OA\Schema $propert throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $types[0]->getBuiltinType(), $model->getType()->getClassName(), $propertyName)); } + private function markRequiredProperties(OA\Schema $schema): void + { + if (Generator::isDefault($properties = $schema->properties)) { + return; + } + + foreach ($properties as $property) { + if (true === $property->nullable || !Generator::isDefault($property->default)) { + continue; + } + + $existingRequiredFields = Generator::UNDEFINED !== $schema->required ? $schema->required : []; + $existingRequiredFields[] = $property->property; + + $schema->required = array_values(array_unique($existingRequiredFields)); + } + } + public function supports(Model $model): bool { return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() diff --git a/src/PropertyDescriber/RequiredPropertyDescriber.php b/src/PropertyDescriber/RequiredPropertyDescriber.php index 1b8b70e68..f1cf58894 100644 --- a/src/PropertyDescriber/RequiredPropertyDescriber.php +++ b/src/PropertyDescriber/RequiredPropertyDescriber.php @@ -11,11 +11,14 @@ namespace Nelmio\ApiDocBundle\PropertyDescriber; +use Nelmio\ApiDocBundle\ModelDescriber\ObjectModelDescriber; use OpenApi\Annotations as OA; use OpenApi\Generator; /** * Mark a property as required if it is not nullable. + * + * @deprecated {@see ObjectModelDescriber::markRequiredProperties()} */ final class RequiredPropertyDescriber implements PropertyDescriberInterface, PropertyDescriberAwareInterface { From 29bc887f596d06f3090b41d12792d94080f437b9 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 8 Oct 2024 13:57:35 +0200 Subject: [PATCH 14/78] Update tests for new required property behaviour --- .../Fixtures/MapQueryStringCleanupComponents.json | 4 ++-- .../Fixtures/MapQueryStringController.json | 13 ++++++++----- .../Fixtures/MapRequestPayloadController.json | 3 ++- tests/Functional/FunctionalTest.php | 6 +++++- tests/Functional/ValidationGroupsFunctionalTest.php | 1 + 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json b/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json index 999263bcf..e34e9aa3b 100644 --- a/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json +++ b/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json @@ -148,7 +148,7 @@ { "name": "propertyArray[]", "in": "query", - "required": false, + "required": true, "schema": { "type": "array", "items": { @@ -304,7 +304,7 @@ "name": "productIds[]", "in": "query", "description": "List of product ids", - "required": false, + "required": true, "schema": { "description": "List of product ids", "type": "array", diff --git a/tests/Functional/Fixtures/MapQueryStringController.json b/tests/Functional/Fixtures/MapQueryStringController.json index d29e83361..cc9d8af2b 100644 --- a/tests/Functional/Fixtures/MapQueryStringController.json +++ b/tests/Functional/Fixtures/MapQueryStringController.json @@ -148,7 +148,7 @@ { "name": "propertyArray[]", "in": "query", - "required": false, + "required": true, "schema": { "type": "array", "items": { @@ -304,7 +304,7 @@ "name": "productIds[]", "in": "query", "description": "List of product ids", - "required": false, + "required": true, "schema": { "description": "List of product ids", "type": "array", @@ -1103,8 +1103,9 @@ "SymfonyConstraintsWithValidationGroups": { "required": [ "property", + "propertyNotNullOnSpecificGroup", "propertyInDefaultGroup", - "propertyNotNullOnSpecificGroup" + "propertyArray" ], "properties": { "property": { @@ -1180,7 +1181,8 @@ }, "ArrayQueryModel": { "required": [ - "ids" + "ids", + "productIds" ], "properties": { "ids": { @@ -1248,7 +1250,8 @@ }, "ArrayQueryModel2": { "required": [ - "ids" + "ids", + "productIds" ], "properties": { "ids": { diff --git a/tests/Functional/Fixtures/MapRequestPayloadController.json b/tests/Functional/Fixtures/MapRequestPayloadController.json index 7fff78337..6fc775596 100644 --- a/tests/Functional/Fixtures/MapRequestPayloadController.json +++ b/tests/Functional/Fixtures/MapRequestPayloadController.json @@ -147,8 +147,9 @@ "SymfonyConstraintsWithValidationGroups": { "required": [ "property", + "propertyNotNullOnSpecificGroup", "propertyInDefaultGroup", - "propertyNotNullOnSpecificGroup" + "propertyArray" ], "properties": { "property": { diff --git a/tests/Functional/FunctionalTest.php b/tests/Functional/FunctionalTest.php index 6a534e4b1..ad6eda5c9 100644 --- a/tests/Functional/FunctionalTest.php +++ b/tests/Functional/FunctionalTest.php @@ -252,6 +252,9 @@ public function testUserModel(): void ], 'schema' => 'User', 'required' => [ + 'email', + 'location', + 'friendsNumber', 'creationDate', 'users', 'status', @@ -566,7 +569,8 @@ public function testSymfonyConstraintDocumentation(): void ]; if (Helper::isCompoundValidatorConstraintSupported()) { - $expected['required'][] = 'propertyWithCompoundValidationRule'; + // array_splice is used to insert the property after "propertyNotNull" + array_splice($expected['required'], 2, 0, 'propertyWithCompoundValidationRule'); $expected['properties']['propertyWithCompoundValidationRule'] = [ 'type' => 'integer', 'maximum' => 5, diff --git a/tests/Functional/ValidationGroupsFunctionalTest.php b/tests/Functional/ValidationGroupsFunctionalTest.php index 3e43eee2b..2cc94b84c 100644 --- a/tests/Functional/ValidationGroupsFunctionalTest.php +++ b/tests/Functional/ValidationGroupsFunctionalTest.php @@ -89,6 +89,7 @@ public function testConstraintDefaultGroupsAreRespectedWhenReadingAnnotations(): 'required' => [ 'property', 'propertyInDefaultGroup', + 'propertyArray', ], ]; From 074bf30df4941d2cc2c06c475e5ba978b12dc645 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 8 Oct 2024 14:54:07 +0200 Subject: [PATCH 15/78] Use getVersion method --- tests/Functional/SwaggerPHPApiComplianceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Functional/SwaggerPHPApiComplianceTest.php b/tests/Functional/SwaggerPHPApiComplianceTest.php index bedb2aa47..3b0c8ab10 100644 --- a/tests/Functional/SwaggerPHPApiComplianceTest.php +++ b/tests/Functional/SwaggerPHPApiComplianceTest.php @@ -30,7 +30,7 @@ public function testAllContextsCopyRoot(): void self::assertTrue($root->is('version')); foreach ((new Analysis([$openApi], Util::createContext()))->annotations as $annotation) { - self::assertSame($annotation->_context->version, $root->version); + self::assertSame($annotation->_context->getVersion(), $root->getVersion()); } } } From 9d61909bafd376fa9fc32db39a05dd2adc445788 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 8 Oct 2024 14:57:39 +0200 Subject: [PATCH 16/78] Remove type-info component (comes with symfony/property-info) --- composer.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 62c1657fe..3ff9ab761 100644 --- a/composer.json +++ b/composer.json @@ -58,7 +58,6 @@ "symfony/twig-bundle": "^5.4 || ^6.4 || ^7.0", "symfony/uid": "^5.4 || ^6.4 || ^7.0", "symfony/validator": "^5.4 || ^6.4 || ^7.0", - "symfony/type-info": "^7.1", "willdurand/hateoas-bundle": "^1.0 || ^2.0" }, "conflict": { @@ -77,8 +76,7 @@ "symfony/serializer": "For describing your models.", "symfony/twig-bundle": "For using the Swagger UI.", "symfony/validator": "For describing the validation constraints in your models.", - "willdurand/hateoas-bundle": "For extracting HATEOAS metadata.", - "symfony/type-info": "For using the type info extractor." + "willdurand/hateoas-bundle": "For extracting HATEOAS metadata." }, "autoload": { "psr-4": { From 7eaa240bdea031cbab11b940a60ee51a7a3bb560 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 8 Oct 2024 16:41:38 +0200 Subject: [PATCH 17/78] bump phpstan symfony version --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 072d3975f..f2b6fb377 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -127,7 +127,7 @@ jobs: - name: Setup dependencies uses: ./.github/workflows/common/composer-install with: - symfony-version: "7.0.*" + symfony-version: "7.1.*" install-doctrine-annotations: false - name: Run PHPStan From b37214f413641bb7df7d38ab95d3a3d44c073b85 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 00:23:17 +0200 Subject: [PATCH 18/78] style fix --- src/ModelDescriber/ObjectModelDescriber.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index 7dae00345..c92ae770b 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -206,8 +206,8 @@ public function describe(Model $model, OA\Schema $schema) $this->describeProperty($types, $model, $property, $propertyName, $schema); } - $this->markRequiredProperties($schema); - } + $this->markRequiredProperties($schema); + } $this->markRequiredProperties($schema); } From 6c76df2ea419cba1e510f4d9077bb382f4d56675 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 09:22:09 +0200 Subject: [PATCH 19/78] remove phpstan ignore --- .../SymfonyMapRequestPayloadDescriberTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/RouteDescriber/RouteArgumentDescriber/SymfonyMapRequestPayloadDescriberTest.php b/tests/RouteDescriber/RouteArgumentDescriber/SymfonyMapRequestPayloadDescriberTest.php index 613a2ea18..362fd045f 100644 --- a/tests/RouteDescriber/RouteArgumentDescriber/SymfonyMapRequestPayloadDescriberTest.php +++ b/tests/RouteDescriber/RouteArgumentDescriber/SymfonyMapRequestPayloadDescriberTest.php @@ -55,7 +55,6 @@ public function testDescribeHandlesArrayParameterAndRegistersCorrectSchema(): vo null, false, [ - /* @phpstan-ignore-next-line can be removed with Symfony 7.1 integration */ new MapRequestPayload( null, [], From 183eefff96639acb0a239aa6909e1d89d1cfbbe2 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 09:22:34 +0200 Subject: [PATCH 20/78] baseline update --- phpstan-baseline.neon | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6c228bec1..05b1ce46d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -5,6 +5,11 @@ parameters: count: 1 path: src/Describer/ExternalDocDescriber.php + - + message: "#^Call to function method_exists\\(\\) with Symfony\\\\Component\\\\PropertyInfo\\\\PropertyInfoExtractorInterface and 'getType' will always evaluate to true\\.$#" + count: 1 + path: src/ModelDescriber/ObjectModelDescriber.php + - message: "#^Method Nelmio\\\\ApiDocBundle\\\\PropertyDescriber\\\\PropertyDescriberInterface\\:\\:describe\\(\\) invoked with 5 parameters, 2\\-3 required\\.$#" count: 1 From 54599b860acc159967044bfba667d5f2ad29bc90 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 13:50:39 +0200 Subject: [PATCH 21/78] Model new model describers --- config/services.xml | 8 ----- src/ModelDescriber/DateTimeModelDescriber.php | 31 ------------------ src/ModelDescriber/UuidModelDescriber.php | 32 ------------------- src/SchemaDescriber/ObjectClassDescriber.php | 15 +++++++++ 4 files changed, 15 insertions(+), 71 deletions(-) delete mode 100644 src/ModelDescriber/DateTimeModelDescriber.php delete mode 100644 src/ModelDescriber/UuidModelDescriber.php diff --git a/config/services.xml b/config/services.xml index 93c4515d6..55db81ba3 100644 --- a/config/services.xml +++ b/config/services.xml @@ -96,14 +96,6 @@ - - - - - - - - diff --git a/src/ModelDescriber/DateTimeModelDescriber.php b/src/ModelDescriber/DateTimeModelDescriber.php deleted file mode 100644 index 8e8e2397a..000000000 --- a/src/ModelDescriber/DateTimeModelDescriber.php +++ /dev/null @@ -1,31 +0,0 @@ -type = 'string'; - $schema->format = 'date-time'; - } - - public function supports(Model $model): bool - { - return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() - && is_a($model->getType()->getClassName(), \DateTimeInterface::class, true); - } -} diff --git a/src/ModelDescriber/UuidModelDescriber.php b/src/ModelDescriber/UuidModelDescriber.php deleted file mode 100644 index c712177cd..000000000 --- a/src/ModelDescriber/UuidModelDescriber.php +++ /dev/null @@ -1,32 +0,0 @@ -type = 'string'; - $schema->format = 'uuid'; - } - - public function supports(Model $model): bool - { - return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() - && is_a($model->getType()->getClassName(), AbstractUid::class, true); - } -} diff --git a/src/SchemaDescriber/ObjectClassDescriber.php b/src/SchemaDescriber/ObjectClassDescriber.php index 5d7f73745..de3b64f57 100644 --- a/src/SchemaDescriber/ObjectClassDescriber.php +++ b/src/SchemaDescriber/ObjectClassDescriber.php @@ -19,6 +19,7 @@ use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\TypeIdentifier; +use Symfony\Component\Uid\AbstractUid; /** * @implements SchemaDescriberInterface @@ -31,6 +32,20 @@ final class ObjectClassDescriber implements SchemaDescriberInterface, ModelRegis public function describe(Type $type, Schema $schema, array $context = []): void { + if (is_a($type->getClassName(), AbstractUid::class, true)) { + $schema->type = 'string'; + $schema->format = 'uuid'; + + return; + } + + if (is_a($type->getClassName(), \DateTimeInterface::class, true)) { + $schema->type = 'string'; + $schema->format = 'date-time'; + + return; + } + $schema->ref = $this->modelRegistry->register( new Model(new LegacyType('object', false, $type->getClassName()), null, null, $context) ); From 48df032ffcc08fce3fa56528aa8c383b8f70fb1e Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 13:53:48 +0200 Subject: [PATCH 22/78] Rename to TypeDescriber --- config/services.xml | 26 +++++++++---------- src/ModelDescriber/ObjectModelDescriber.php | 14 +++++----- .../BoolDescriber.php | 6 ++--- .../ChainDescriber.php | 12 ++++----- .../DictionaryDescriber.php | 6 ++--- .../FloatDescriber.php | 6 ++--- .../IntegerDescriber.php | 6 ++--- .../IntersectionDescriber.php | 6 ++--- .../ListDescriber.php | 6 ++--- .../MixedDescriber.php | 6 ++--- .../NullableDescriber.php | 6 ++--- .../ObjectClassDescriber.php | 6 ++--- .../ObjectDescriber.php | 6 ++--- .../StringDescriber.php | 6 ++--- .../TypeDescriberAwareInterface.php} | 6 ++--- .../TypeDescriberAwareTrait.php} | 8 +++--- .../TypeDescriberInterface.php} | 4 +-- .../UnionDescriber.php | 6 ++--- 18 files changed, 71 insertions(+), 71 deletions(-) rename src/{SchemaDescriber => TypeDescriber}/BoolDescriber.php (80%) rename src/{SchemaDescriber => TypeDescriber}/ChainDescriber.php (80%) rename src/{SchemaDescriber => TypeDescriber}/DictionaryDescriber.php (83%) rename src/{SchemaDescriber => TypeDescriber}/FloatDescriber.php (80%) rename src/{SchemaDescriber => TypeDescriber}/IntegerDescriber.php (79%) rename src/{SchemaDescriber => TypeDescriber}/IntersectionDescriber.php (88%) rename src/{SchemaDescriber => TypeDescriber}/ListDescriber.php (83%) rename src/{SchemaDescriber => TypeDescriber}/MixedDescriber.php (80%) rename src/{SchemaDescriber => TypeDescriber}/NullableDescriber.php (76%) rename src/{SchemaDescriber => TypeDescriber}/ObjectClassDescriber.php (88%) rename src/{SchemaDescriber => TypeDescriber}/ObjectDescriber.php (82%) rename src/{SchemaDescriber => TypeDescriber}/StringDescriber.php (80%) rename src/{SchemaDescriber/SchemaDescriberAwareInterface.php => TypeDescriber/TypeDescriberAwareInterface.php} (60%) rename src/{SchemaDescriber/SchemaDescriberAwareTrait.php => TypeDescriber/TypeDescriberAwareTrait.php} (59%) rename src/{SchemaDescriber/SchemaDescriberInterface.php => TypeDescriber/TypeDescriberInterface.php} (90%) rename src/{SchemaDescriber => TypeDescriber}/UnionDescriber.php (89%) diff --git a/config/services.xml b/config/services.xml index 55db81ba3..df284c903 100644 --- a/config/services.xml +++ b/config/services.xml @@ -163,57 +163,57 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index c92ae770b..04ff0cbc8 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -18,7 +18,7 @@ use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use Nelmio\ApiDocBundle\PropertyDescriber\PropertyDescriberInterface; -use Nelmio\ApiDocBundle\SchemaDescriber\SchemaDescriberInterface; +use Nelmio\ApiDocBundle\TypeDescriber\TypeDescriberInterface; use OpenApi\Annotations as OA; use OpenApi\Generator; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; @@ -35,7 +35,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar private PropertyInfoExtractorInterface $propertyInfo; private ?ClassMetadataFactoryInterface $classMetadataFactory; private ?Reader $doctrineReader; - /** @var PropertyDescriberInterface|PropertyDescriberInterface[]|SchemaDescriberInterface */ + /** @var PropertyDescriberInterface|PropertyDescriberInterface[]|TypeDescriberInterface */ private $propertyDescriber; /** @var string[] */ private array $mediaTypes; @@ -44,9 +44,9 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar private bool $useValidationGroups; /** - * @param PropertyDescriberInterface|PropertyDescriberInterface[]|SchemaDescriberInterface $propertyDescribers - * @param (NameConverterInterface&AdvancedNameConverterInterface)|null $nameConverter - * @param string[] $mediaTypes + * @param PropertyDescriberInterface|PropertyDescriberInterface[]|TypeDescriberInterface $propertyDescribers + * @param (NameConverterInterface&AdvancedNameConverterInterface)|null $nameConverter + * @param string[] $mediaTypes */ public function __construct( PropertyInfoExtractorInterface $propertyInfo, @@ -60,7 +60,7 @@ public function __construct( if (is_iterable($propertyDescribers)) { trigger_deprecation('nelmio/api-doc-bundle', '4.17', 'Passing an array of PropertyDescriberInterface to %s() is deprecated. Pass a single PropertyDescriberInterface instead.', __METHOD__); } else { - if (!$propertyDescribers instanceof PropertyDescriberInterface && !$propertyDescribers instanceof SchemaDescriberInterface) { + if (!$propertyDescribers instanceof PropertyDescriberInterface && !$propertyDescribers instanceof TypeDescriberInterface) { throw new \InvalidArgumentException(sprintf('Argument 3 passed to %s() must be an array of %s or a single %s.', __METHOD__, PropertyDescriberInterface::class, PropertyDescriberInterface::class)); } } @@ -179,7 +179,7 @@ public function describe(Model $model, OA\Schema $schema) /* * @experimental */ - if ($this->propertyDescriber instanceof SchemaDescriberInterface) { + if ($this->propertyDescriber instanceof TypeDescriberInterface) { if (false === method_exists($this->propertyInfo, 'getType')) { throw new \RuntimeException('The PropertyInfo component is missing the "getType" method. Are you running on version 7.1?'); } diff --git a/src/SchemaDescriber/BoolDescriber.php b/src/TypeDescriber/BoolDescriber.php similarity index 80% rename from src/SchemaDescriber/BoolDescriber.php rename to src/TypeDescriber/BoolDescriber.php index f62c22422..2646928ec 100644 --- a/src/SchemaDescriber/BoolDescriber.php +++ b/src/TypeDescriber/BoolDescriber.php @@ -9,18 +9,18 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class BoolDescriber implements SchemaDescriberInterface +final class BoolDescriber implements TypeDescriberInterface { public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/SchemaDescriber/ChainDescriber.php b/src/TypeDescriber/ChainDescriber.php similarity index 80% rename from src/SchemaDescriber/ChainDescriber.php rename to src/TypeDescriber/ChainDescriber.php index 394d178c4..56ea55ee1 100644 --- a/src/SchemaDescriber/ChainDescriber.php +++ b/src/TypeDescriber/ChainDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; @@ -17,19 +17,19 @@ use Symfony\Component\TypeInfo\Type; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class ChainDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface +final class ChainDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface { use ModelRegistryAwareTrait; - /** @var iterable */ + /** @var iterable */ private iterable $describers; /** - * @param iterable $describers + * @param iterable $describers */ public function __construct( iterable $describers @@ -49,7 +49,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void $describer->setModelRegistry($this->modelRegistry); } - if ($describer instanceof SchemaDescriberAwareInterface) { + if ($describer instanceof TypeDescriberAwareInterface) { $describer->setDescriber($this); } diff --git a/src/SchemaDescriber/DictionaryDescriber.php b/src/TypeDescriber/DictionaryDescriber.php similarity index 83% rename from src/SchemaDescriber/DictionaryDescriber.php rename to src/TypeDescriber/DictionaryDescriber.php index c3bcf9ccf..24c6f2ac3 100644 --- a/src/SchemaDescriber/DictionaryDescriber.php +++ b/src/TypeDescriber/DictionaryDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; @@ -19,11 +19,11 @@ use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class DictionaryDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +final class DictionaryDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { use SchemaDescriberAwareTrait; diff --git a/src/SchemaDescriber/FloatDescriber.php b/src/TypeDescriber/FloatDescriber.php similarity index 80% rename from src/SchemaDescriber/FloatDescriber.php rename to src/TypeDescriber/FloatDescriber.php index 2f37f5c9f..c2dc6a0ef 100644 --- a/src/SchemaDescriber/FloatDescriber.php +++ b/src/TypeDescriber/FloatDescriber.php @@ -9,18 +9,18 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class FloatDescriber implements SchemaDescriberInterface +final class FloatDescriber implements TypeDescriberInterface { public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/SchemaDescriber/IntegerDescriber.php b/src/TypeDescriber/IntegerDescriber.php similarity index 79% rename from src/SchemaDescriber/IntegerDescriber.php rename to src/TypeDescriber/IntegerDescriber.php index 6241df3b8..ae297a318 100644 --- a/src/SchemaDescriber/IntegerDescriber.php +++ b/src/TypeDescriber/IntegerDescriber.php @@ -9,18 +9,18 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class IntegerDescriber implements SchemaDescriberInterface +final class IntegerDescriber implements TypeDescriberInterface { public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/SchemaDescriber/IntersectionDescriber.php b/src/TypeDescriber/IntersectionDescriber.php similarity index 88% rename from src/SchemaDescriber/IntersectionDescriber.php rename to src/TypeDescriber/IntersectionDescriber.php index f5104224b..052ee82ed 100644 --- a/src/SchemaDescriber/IntersectionDescriber.php +++ b/src/TypeDescriber/IntersectionDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations\Schema; @@ -19,11 +19,11 @@ use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class IntersectionDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +final class IntersectionDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { use SchemaDescriberAwareTrait; diff --git a/src/SchemaDescriber/ListDescriber.php b/src/TypeDescriber/ListDescriber.php similarity index 83% rename from src/SchemaDescriber/ListDescriber.php rename to src/TypeDescriber/ListDescriber.php index 5c1b35bcf..e262fd3c2 100644 --- a/src/SchemaDescriber/ListDescriber.php +++ b/src/TypeDescriber/ListDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; @@ -19,11 +19,11 @@ use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class ListDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +final class ListDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { use SchemaDescriberAwareTrait; diff --git a/src/SchemaDescriber/MixedDescriber.php b/src/TypeDescriber/MixedDescriber.php similarity index 80% rename from src/SchemaDescriber/MixedDescriber.php rename to src/TypeDescriber/MixedDescriber.php index 6c3860300..fe06f7a67 100644 --- a/src/SchemaDescriber/MixedDescriber.php +++ b/src/TypeDescriber/MixedDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use OpenApi\Annotations\Schema; use OpenApi\Generator; @@ -17,11 +17,11 @@ use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class MixedDescriber implements SchemaDescriberInterface +final class MixedDescriber implements TypeDescriberInterface { public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/SchemaDescriber/NullableDescriber.php b/src/TypeDescriber/NullableDescriber.php similarity index 76% rename from src/SchemaDescriber/NullableDescriber.php rename to src/TypeDescriber/NullableDescriber.php index 3b46ae981..a76fa72a5 100644 --- a/src/SchemaDescriber/NullableDescriber.php +++ b/src/TypeDescriber/NullableDescriber.php @@ -9,17 +9,17 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class NullableDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +final class NullableDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { use SchemaDescriberAwareTrait; diff --git a/src/SchemaDescriber/ObjectClassDescriber.php b/src/TypeDescriber/ObjectClassDescriber.php similarity index 88% rename from src/SchemaDescriber/ObjectClassDescriber.php rename to src/TypeDescriber/ObjectClassDescriber.php index de3b64f57..b709fe319 100644 --- a/src/SchemaDescriber/ObjectClassDescriber.php +++ b/src/TypeDescriber/ObjectClassDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; @@ -22,11 +22,11 @@ use Symfony\Component\Uid\AbstractUid; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class ObjectClassDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface +final class ObjectClassDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface { use ModelRegistryAwareTrait; diff --git a/src/SchemaDescriber/ObjectDescriber.php b/src/TypeDescriber/ObjectDescriber.php similarity index 82% rename from src/SchemaDescriber/ObjectDescriber.php rename to src/TypeDescriber/ObjectDescriber.php index 193e03b0d..b0c8d83dc 100644 --- a/src/SchemaDescriber/ObjectDescriber.php +++ b/src/TypeDescriber/ObjectDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; @@ -19,11 +19,11 @@ use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class ObjectDescriber implements SchemaDescriberInterface, ModelRegistryAwareInterface +final class ObjectDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface { use ModelRegistryAwareTrait; diff --git a/src/SchemaDescriber/StringDescriber.php b/src/TypeDescriber/StringDescriber.php similarity index 80% rename from src/SchemaDescriber/StringDescriber.php rename to src/TypeDescriber/StringDescriber.php index e9cdf157c..9bc5a8799 100644 --- a/src/SchemaDescriber/StringDescriber.php +++ b/src/TypeDescriber/StringDescriber.php @@ -9,18 +9,18 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class StringDescriber implements SchemaDescriberInterface +final class StringDescriber implements TypeDescriberInterface { public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/SchemaDescriber/SchemaDescriberAwareInterface.php b/src/TypeDescriber/TypeDescriberAwareInterface.php similarity index 60% rename from src/SchemaDescriber/SchemaDescriberAwareInterface.php rename to src/TypeDescriber/TypeDescriberAwareInterface.php index beb7f8759..318cc29b3 100644 --- a/src/SchemaDescriber/SchemaDescriberAwareInterface.php +++ b/src/TypeDescriber/TypeDescriberAwareInterface.php @@ -9,12 +9,12 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; /** * @experimental */ -interface SchemaDescriberAwareInterface +interface TypeDescriberAwareInterface { - public function setDescriber(SchemaDescriberInterface $describer): void; + public function setDescriber(TypeDescriberInterface $describer): void; } diff --git a/src/SchemaDescriber/SchemaDescriberAwareTrait.php b/src/TypeDescriber/TypeDescriberAwareTrait.php similarity index 59% rename from src/SchemaDescriber/SchemaDescriberAwareTrait.php rename to src/TypeDescriber/TypeDescriberAwareTrait.php index cf51b56ad..760efcda3 100644 --- a/src/SchemaDescriber/SchemaDescriberAwareTrait.php +++ b/src/TypeDescriber/TypeDescriberAwareTrait.php @@ -9,16 +9,16 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; /** * @experimental */ -trait SchemaDescriberAwareTrait +trait TypeDescriberAwareTrait { - protected SchemaDescriberInterface $describer; + protected TypeDescriberInterface $describer; - public function setDescriber(SchemaDescriberInterface $describer): void + public function setDescriber(TypeDescriberInterface $describer): void { $this->describer = $describer; } diff --git a/src/SchemaDescriber/SchemaDescriberInterface.php b/src/TypeDescriber/TypeDescriberInterface.php similarity index 90% rename from src/SchemaDescriber/SchemaDescriberInterface.php rename to src/TypeDescriber/TypeDescriberInterface.php index 060fa9e9f..f2c824872 100644 --- a/src/SchemaDescriber/SchemaDescriberInterface.php +++ b/src/TypeDescriber/TypeDescriberInterface.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; @@ -19,7 +19,7 @@ * * @experimental */ -interface SchemaDescriberInterface +interface TypeDescriberInterface { /** * @param T $type diff --git a/src/SchemaDescriber/UnionDescriber.php b/src/TypeDescriber/UnionDescriber.php similarity index 89% rename from src/SchemaDescriber/UnionDescriber.php rename to src/TypeDescriber/UnionDescriber.php index f64cafafa..54c9d1b9b 100644 --- a/src/SchemaDescriber/UnionDescriber.php +++ b/src/TypeDescriber/UnionDescriber.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\SchemaDescriber; +namespace Nelmio\ApiDocBundle\TypeDescriber; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations\Schema; @@ -19,11 +19,11 @@ use Symfony\Component\TypeInfo\TypeIdentifier; /** - * @implements SchemaDescriberInterface + * @implements TypeDescriberInterface * * @experimental */ -final class UnionDescriber implements SchemaDescriberInterface, SchemaDescriberAwareInterface +final class UnionDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { use SchemaDescriberAwareTrait; From 7c045d729e718e1f13bd698ee430796080885ab2 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 15:20:06 +0200 Subject: [PATCH 23/78] Mark experimental describers as internal --- src/TypeDescriber/BoolDescriber.php | 2 ++ src/TypeDescriber/ChainDescriber.php | 2 ++ src/TypeDescriber/DictionaryDescriber.php | 4 +++- src/TypeDescriber/FloatDescriber.php | 2 ++ src/TypeDescriber/IntegerDescriber.php | 2 ++ src/TypeDescriber/IntersectionDescriber.php | 4 +++- src/TypeDescriber/ListDescriber.php | 4 +++- src/TypeDescriber/MixedDescriber.php | 2 ++ src/TypeDescriber/NullableDescriber.php | 4 +++- src/TypeDescriber/ObjectClassDescriber.php | 2 ++ src/TypeDescriber/ObjectDescriber.php | 2 ++ src/TypeDescriber/StringDescriber.php | 2 ++ src/TypeDescriber/TypeDescriberAwareInterface.php | 2 ++ src/TypeDescriber/TypeDescriberAwareTrait.php | 2 ++ src/TypeDescriber/TypeDescriberInterface.php | 2 ++ src/TypeDescriber/UnionDescriber.php | 4 +++- 16 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/TypeDescriber/BoolDescriber.php b/src/TypeDescriber/BoolDescriber.php index 2646928ec..aac3471dd 100644 --- a/src/TypeDescriber/BoolDescriber.php +++ b/src/TypeDescriber/BoolDescriber.php @@ -19,6 +19,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class BoolDescriber implements TypeDescriberInterface { diff --git a/src/TypeDescriber/ChainDescriber.php b/src/TypeDescriber/ChainDescriber.php index 56ea55ee1..7cc5f6297 100644 --- a/src/TypeDescriber/ChainDescriber.php +++ b/src/TypeDescriber/ChainDescriber.php @@ -20,6 +20,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class ChainDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface { diff --git a/src/TypeDescriber/DictionaryDescriber.php b/src/TypeDescriber/DictionaryDescriber.php index 24c6f2ac3..42c14337a 100644 --- a/src/TypeDescriber/DictionaryDescriber.php +++ b/src/TypeDescriber/DictionaryDescriber.php @@ -22,10 +22,12 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class DictionaryDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { - use SchemaDescriberAwareTrait; + use TypeDescriberAwareTrait; public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/TypeDescriber/FloatDescriber.php b/src/TypeDescriber/FloatDescriber.php index c2dc6a0ef..2ae68b848 100644 --- a/src/TypeDescriber/FloatDescriber.php +++ b/src/TypeDescriber/FloatDescriber.php @@ -19,6 +19,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class FloatDescriber implements TypeDescriberInterface { diff --git a/src/TypeDescriber/IntegerDescriber.php b/src/TypeDescriber/IntegerDescriber.php index ae297a318..e94f81a4e 100644 --- a/src/TypeDescriber/IntegerDescriber.php +++ b/src/TypeDescriber/IntegerDescriber.php @@ -19,6 +19,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class IntegerDescriber implements TypeDescriberInterface { diff --git a/src/TypeDescriber/IntersectionDescriber.php b/src/TypeDescriber/IntersectionDescriber.php index 052ee82ed..24a01a475 100644 --- a/src/TypeDescriber/IntersectionDescriber.php +++ b/src/TypeDescriber/IntersectionDescriber.php @@ -22,10 +22,12 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class IntersectionDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { - use SchemaDescriberAwareTrait; + use TypeDescriberAwareTrait; public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/TypeDescriber/ListDescriber.php b/src/TypeDescriber/ListDescriber.php index e262fd3c2..830dc73e5 100644 --- a/src/TypeDescriber/ListDescriber.php +++ b/src/TypeDescriber/ListDescriber.php @@ -22,10 +22,12 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class ListDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { - use SchemaDescriberAwareTrait; + use TypeDescriberAwareTrait; public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/TypeDescriber/MixedDescriber.php b/src/TypeDescriber/MixedDescriber.php index fe06f7a67..a0c993e63 100644 --- a/src/TypeDescriber/MixedDescriber.php +++ b/src/TypeDescriber/MixedDescriber.php @@ -20,6 +20,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class MixedDescriber implements TypeDescriberInterface { diff --git a/src/TypeDescriber/NullableDescriber.php b/src/TypeDescriber/NullableDescriber.php index a76fa72a5..342113a1e 100644 --- a/src/TypeDescriber/NullableDescriber.php +++ b/src/TypeDescriber/NullableDescriber.php @@ -18,10 +18,12 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class NullableDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { - use SchemaDescriberAwareTrait; + use TypeDescriberAwareTrait; public function describe(Type $type, Schema $schema, array $context = []): void { diff --git a/src/TypeDescriber/ObjectClassDescriber.php b/src/TypeDescriber/ObjectClassDescriber.php index b709fe319..7c38af9b8 100644 --- a/src/TypeDescriber/ObjectClassDescriber.php +++ b/src/TypeDescriber/ObjectClassDescriber.php @@ -25,6 +25,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class ObjectClassDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface { diff --git a/src/TypeDescriber/ObjectDescriber.php b/src/TypeDescriber/ObjectDescriber.php index b0c8d83dc..7ac0c967e 100644 --- a/src/TypeDescriber/ObjectDescriber.php +++ b/src/TypeDescriber/ObjectDescriber.php @@ -22,6 +22,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class ObjectDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface { diff --git a/src/TypeDescriber/StringDescriber.php b/src/TypeDescriber/StringDescriber.php index 9bc5a8799..c84ece84a 100644 --- a/src/TypeDescriber/StringDescriber.php +++ b/src/TypeDescriber/StringDescriber.php @@ -19,6 +19,8 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class StringDescriber implements TypeDescriberInterface { diff --git a/src/TypeDescriber/TypeDescriberAwareInterface.php b/src/TypeDescriber/TypeDescriberAwareInterface.php index 318cc29b3..94f581b43 100644 --- a/src/TypeDescriber/TypeDescriberAwareInterface.php +++ b/src/TypeDescriber/TypeDescriberAwareInterface.php @@ -13,6 +13,8 @@ /** * @experimental + * + * @internal */ interface TypeDescriberAwareInterface { diff --git a/src/TypeDescriber/TypeDescriberAwareTrait.php b/src/TypeDescriber/TypeDescriberAwareTrait.php index 760efcda3..7141d7fa4 100644 --- a/src/TypeDescriber/TypeDescriberAwareTrait.php +++ b/src/TypeDescriber/TypeDescriberAwareTrait.php @@ -13,6 +13,8 @@ /** * @experimental + * + * @internal */ trait TypeDescriberAwareTrait { diff --git a/src/TypeDescriber/TypeDescriberInterface.php b/src/TypeDescriber/TypeDescriberInterface.php index f2c824872..25c43c158 100644 --- a/src/TypeDescriber/TypeDescriberInterface.php +++ b/src/TypeDescriber/TypeDescriberInterface.php @@ -18,6 +18,8 @@ * @template T of Type * * @experimental + * + * @internal */ interface TypeDescriberInterface { diff --git a/src/TypeDescriber/UnionDescriber.php b/src/TypeDescriber/UnionDescriber.php index 54c9d1b9b..168821f67 100644 --- a/src/TypeDescriber/UnionDescriber.php +++ b/src/TypeDescriber/UnionDescriber.php @@ -22,10 +22,12 @@ * @implements TypeDescriberInterface * * @experimental + * + * @internal */ final class UnionDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface { - use SchemaDescriberAwareTrait; + use TypeDescriberAwareTrait; public function describe(Type $type, Schema $schema, array $context = []): void { From 79b1bdc9ba67ed35eeb78a48c6fbaa9edb8d3093 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 15:20:14 +0200 Subject: [PATCH 24/78] Remove deprecation warning --- src/PropertyDescriber/DateTimePropertyDescriber.php | 4 ---- src/PropertyDescriber/UuidPropertyDescriber.php | 3 --- 2 files changed, 7 deletions(-) diff --git a/src/PropertyDescriber/DateTimePropertyDescriber.php b/src/PropertyDescriber/DateTimePropertyDescriber.php index c36a90b70..8b15f8ad4 100644 --- a/src/PropertyDescriber/DateTimePropertyDescriber.php +++ b/src/PropertyDescriber/DateTimePropertyDescriber.php @@ -11,13 +11,9 @@ namespace Nelmio\ApiDocBundle\PropertyDescriber; -use Nelmio\ApiDocBundle\ModelDescriber\DateTimeModelDescriber; use OpenApi\Annotations as OA; use Symfony\Component\PropertyInfo\Type; -/** - * @deprecated in favor of a model describer {@see DateTimeModelDescriber} - */ class DateTimePropertyDescriber implements PropertyDescriberInterface { /** diff --git a/src/PropertyDescriber/UuidPropertyDescriber.php b/src/PropertyDescriber/UuidPropertyDescriber.php index 05b6ab81c..843d4ef63 100644 --- a/src/PropertyDescriber/UuidPropertyDescriber.php +++ b/src/PropertyDescriber/UuidPropertyDescriber.php @@ -15,9 +15,6 @@ use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Uid\AbstractUid; -/** - * @deprecated in favor of a model describer {@see UuidModelDescriber} - */ final class UuidPropertyDescriber implements PropertyDescriberInterface { /** From 223954dddadaa5e6138cfd93adc6eb18d78fb811 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 15:56:36 +0200 Subject: [PATCH 25/78] Remove call to markRequiredProperties --- src/ModelDescriber/ObjectModelDescriber.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index 04ff0cbc8..b7af0c223 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -206,7 +206,6 @@ public function describe(Model $model, OA\Schema $schema) $this->describeProperty($types, $model, $property, $propertyName, $schema); } - $this->markRequiredProperties($schema); } $this->markRequiredProperties($schema); From 03271053b1ad0b890bf83c8f74981015c4c4540c Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 18 Oct 2024 16:06:34 +0200 Subject: [PATCH 26/78] fix invalid context being passed --- src/ModelDescriber/ObjectModelDescriber.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index b7af0c223..e135ee832 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -184,7 +184,7 @@ public function describe(Model $model, OA\Schema $schema) throw new \RuntimeException('The PropertyInfo component is missing the "getType" method. Are you running on version 7.1?'); } - $type = $this->propertyInfo->getType($class, $propertyName, $context); + $type = $this->propertyInfo->getType($class, $propertyName); if (null === $type) { throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); } @@ -193,11 +193,11 @@ public function describe(Model $model, OA\Schema $schema) $this->propertyDescriber->setModelRegistry($this->modelRegistry); } - if (!$this->propertyDescriber->supports($type, $context)) { - throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $type->__toString(), $model->getType()->getClassName(), $propertyName)); + if (!$this->propertyDescriber->supports($type, $model->getSerializationContext())) { + throw new \Exception(sprintf('Type "%s" $model->getSerializationContext() not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $type->__toString(), $model->getType()->getClassName(), $propertyName)); } - $this->propertyDescriber->describe($type, $property, $context); + $this->propertyDescriber->describe($type, $property, $model->getSerializationContext()); } else { $types = $this->propertyInfo->getTypes($class, $propertyName); if (null === $types || 0 === count($types)) { From 4fe66c9e6c59d9dd772722f7c858e542dd422798 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 7 Jan 2025 09:41:39 +0100 Subject: [PATCH 27/78] baseline --- phpstan-baseline.neon | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e6f2a0b4e..cabb935e2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -25,6 +25,11 @@ parameters: count: 1 path: src/Describer/ExternalDocDescriber.php + - + message: "#^Call to function method_exists\\(\\) with Symfony\\\\Component\\\\PropertyInfo\\\\PropertyInfoExtractorInterface and 'getType' will always evaluate to true\\.$#" + count: 1 + path: src/ModelDescriber/ObjectModelDescriber.php + - message: "#^Method Nelmio\\\\ApiDocBundle\\\\PropertyDescriber\\\\ArrayPropertyDescriber\\:\\:supports\\(\\) has parameter \\$context with no value type specified in iterable type array\\.$#" count: 1 @@ -159,3 +164,8 @@ parameters: message: "#^Call to function method_exists\\(\\) with 'Hateoas\\\\\\\\Configuration\\\\\\\\Embedded' and 'getType' will always evaluate to true\\.$#" count: 1 path: tests/Functional/BazingaFunctionalTest.php + + - + message: "#^Call to static method getVersion\\(\\) on an unknown class PackageVersions\\\\Versions\\.$#" + count: 1 + path: tests/Helper.php From 90db7460dbb9fee17beb87a9bb0c01d68b344b4e Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 7 Jan 2025 16:16:52 +0100 Subject: [PATCH 28/78] update methods to 7.2 --- src/TypeDescriber/BoolDescriber.php | 2 +- src/TypeDescriber/DictionaryDescriber.php | 2 +- src/TypeDescriber/FloatDescriber.php | 2 +- src/TypeDescriber/IntegerDescriber.php | 2 +- src/TypeDescriber/ListDescriber.php | 2 +- src/TypeDescriber/MixedDescriber.php | 2 +- src/TypeDescriber/ObjectClassDescriber.php | 4 +--- src/TypeDescriber/ObjectDescriber.php | 2 +- src/TypeDescriber/StringDescriber.php | 2 +- 9 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/TypeDescriber/BoolDescriber.php b/src/TypeDescriber/BoolDescriber.php index aac3471dd..eef143025 100644 --- a/src/TypeDescriber/BoolDescriber.php +++ b/src/TypeDescriber/BoolDescriber.php @@ -32,6 +32,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof Type\BuiltinType - && $type->isA(TypeIdentifier::BOOL); + && TypeIdentifier::BOOL === $type->getTypeIdentifier(); } } diff --git a/src/TypeDescriber/DictionaryDescriber.php b/src/TypeDescriber/DictionaryDescriber.php index 42c14337a..5d6c5de9e 100644 --- a/src/TypeDescriber/DictionaryDescriber.php +++ b/src/TypeDescriber/DictionaryDescriber.php @@ -40,6 +40,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType - && $type->getCollectionKeyType()->isA(TypeIdentifier::STRING); + && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::STRING); } } diff --git a/src/TypeDescriber/FloatDescriber.php b/src/TypeDescriber/FloatDescriber.php index 2ae68b848..b4fa6ecf6 100644 --- a/src/TypeDescriber/FloatDescriber.php +++ b/src/TypeDescriber/FloatDescriber.php @@ -33,6 +33,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof Type\BuiltinType - && $type->isA(TypeIdentifier::FLOAT); + && TypeIdentifier::FLOAT === $type->getTypeIdentifier(); } } diff --git a/src/TypeDescriber/IntegerDescriber.php b/src/TypeDescriber/IntegerDescriber.php index e94f81a4e..81150ba7f 100644 --- a/src/TypeDescriber/IntegerDescriber.php +++ b/src/TypeDescriber/IntegerDescriber.php @@ -32,6 +32,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof Type\BuiltinType - && $type->isA(TypeIdentifier::INT); + && TypeIdentifier::INT === $type->getTypeIdentifier(); } } diff --git a/src/TypeDescriber/ListDescriber.php b/src/TypeDescriber/ListDescriber.php index 830dc73e5..d697e6982 100644 --- a/src/TypeDescriber/ListDescriber.php +++ b/src/TypeDescriber/ListDescriber.php @@ -40,6 +40,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType - && $type->getCollectionKeyType()->isA(TypeIdentifier::INT); + && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::INT); } } diff --git a/src/TypeDescriber/MixedDescriber.php b/src/TypeDescriber/MixedDescriber.php index a0c993e63..1fa4ce6d8 100644 --- a/src/TypeDescriber/MixedDescriber.php +++ b/src/TypeDescriber/MixedDescriber.php @@ -33,6 +33,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof Type\BuiltinType - && $type->isA(TypeIdentifier::MIXED); + && TypeIdentifier::MIXED === $type->getTypeIdentifier(); } } diff --git a/src/TypeDescriber/ObjectClassDescriber.php b/src/TypeDescriber/ObjectClassDescriber.php index 7c38af9b8..38c670d6d 100644 --- a/src/TypeDescriber/ObjectClassDescriber.php +++ b/src/TypeDescriber/ObjectClassDescriber.php @@ -18,7 +18,6 @@ use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\ObjectType; -use Symfony\Component\TypeInfo\TypeIdentifier; use Symfony\Component\Uid\AbstractUid; /** @@ -55,7 +54,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { - return $type instanceof ObjectType - && $type->isA(TypeIdentifier::OBJECT); + return $type instanceof ObjectType; } } diff --git a/src/TypeDescriber/ObjectDescriber.php b/src/TypeDescriber/ObjectDescriber.php index 7ac0c967e..e53a02af6 100644 --- a/src/TypeDescriber/ObjectDescriber.php +++ b/src/TypeDescriber/ObjectDescriber.php @@ -38,6 +38,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof BuiltinType - && $type->isA(TypeIdentifier::OBJECT); + && TypeIdentifier::OBJECT === $type->getTypeIdentifier(); } } diff --git a/src/TypeDescriber/StringDescriber.php b/src/TypeDescriber/StringDescriber.php index c84ece84a..456d566a9 100644 --- a/src/TypeDescriber/StringDescriber.php +++ b/src/TypeDescriber/StringDescriber.php @@ -32,6 +32,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof Type\BuiltinType - && $type->isA(TypeIdentifier::STRING); + && TypeIdentifier::STRING === $type->getTypeIdentifier(); } } From cde4e64f4bc261b6184d32b2c5707b4d3a3ad0c6 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 7 Jan 2025 16:17:52 +0100 Subject: [PATCH 29/78] simplify config --- src/DependencyInjection/Configuration.php | 2 +- src/DependencyInjection/NelmioApiDocExtension.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 3ff0d16f9..ee9103213 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -25,7 +25,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() - ->booleanNode('experimental_type_info') + ->booleanNode('type_info') ->info('Use the symfony/type-info component for determining types. This is experimental and could be changed at any time without prior notice.') ->defaultFalse() ->end() diff --git a/src/DependencyInjection/NelmioApiDocExtension.php b/src/DependencyInjection/NelmioApiDocExtension.php index 9e3f597e3..14e33bbba 100644 --- a/src/DependencyInjection/NelmioApiDocExtension.php +++ b/src/DependencyInjection/NelmioApiDocExtension.php @@ -164,7 +164,7 @@ public function load(array $configs, ContainerBuilder $container): void array_map(function ($area) { return new Reference(sprintf('nelmio_api_doc.generator.%s', $area)); }, array_keys($config['areas'])) )); - if (true === $config['experimental_type_info']) { + if (true === $config['type_info']) { $container->getDefinition('nelmio_api_doc.model_describers.object') ->setArgument(2, new Reference('nelmio_api_doc.schema_describer.chain')); } From 7608790ef16dc4dca3327ca327aa7b73e324a6b2 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 7 Jan 2025 16:52:24 +0100 Subject: [PATCH 30/78] fix unknown method call --- src/ModelDescriber/ObjectModelDescriber.php | 2 +- src/TypeDescriber/IntersectionDescriber.php | 2 +- src/TypeDescriber/UnionDescriber.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index c6568832c..a076f26a9 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -170,7 +170,7 @@ public function describe(Model $model, OA\Schema $schema) } if (!$this->propertyDescriber->supports($type, $model->getSerializationContext())) { - throw new \Exception(sprintf('Type "%s" $model->getSerializationContext() not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $type->__toString(), $model->getType()->getClassName(), $propertyName)); + throw new \Exception(sprintf('Type "%s" not supported in %s::$%s. You may need to use the `@OA\Property(type="")` annotation to specify it manually.', $type->__toString(), $model->getType()->getClassName(), $propertyName)); } $this->propertyDescriber->describe($type, $property, $model->getSerializationContext()); diff --git a/src/TypeDescriber/IntersectionDescriber.php b/src/TypeDescriber/IntersectionDescriber.php index 24a01a475..fe23a7185 100644 --- a/src/TypeDescriber/IntersectionDescriber.php +++ b/src/TypeDescriber/IntersectionDescriber.php @@ -32,7 +32,7 @@ final class IntersectionDescriber implements TypeDescriberInterface, TypeDescrib public function describe(Type $type, Schema $schema, array $context = []): void { $innerTypes = array_values(array_filter($type->getTypes(), function (Type $innerType) { - return !$innerType->isA(TypeIdentifier::NULL); + return !$innerType->isIdentifiedBy(TypeIdentifier::NULL); })); // Ensure that non $ref schemas are not described in allOf diff --git a/src/TypeDescriber/UnionDescriber.php b/src/TypeDescriber/UnionDescriber.php index 168821f67..ea4447c92 100644 --- a/src/TypeDescriber/UnionDescriber.php +++ b/src/TypeDescriber/UnionDescriber.php @@ -32,7 +32,7 @@ final class UnionDescriber implements TypeDescriberInterface, TypeDescriberAware public function describe(Type $type, Schema $schema, array $context = []): void { $innerTypes = array_values(array_filter($type->getTypes(), function (Type $innerType) { - return !$innerType->isA(TypeIdentifier::NULL); + return !$innerType->isIdentifiedBy(TypeIdentifier::NULL); })); // Ensure that non $ref schemas are not described in oneOf From 3b69f624ea5350f7c0447ffa605931c4c039d2c4 Mon Sep 17 00:00:00 2001 From: djordy Date: Tue, 7 Jan 2025 23:57:50 +0100 Subject: [PATCH 31/78] update bool check to also document true and false --- src/TypeDescriber/BoolDescriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TypeDescriber/BoolDescriber.php b/src/TypeDescriber/BoolDescriber.php index eef143025..c144413fe 100644 --- a/src/TypeDescriber/BoolDescriber.php +++ b/src/TypeDescriber/BoolDescriber.php @@ -32,6 +32,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof Type\BuiltinType - && TypeIdentifier::BOOL === $type->getTypeIdentifier(); + && $type->getTypeIdentifier()->isBool(); } } From 6417521afb8ddbeeaebd9604aa860b8a179f16b1 Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 00:12:43 +0100 Subject: [PATCH 32/78] document generic arrays --- config/services.xml | 4 ++ src/TypeDescriber/ArrayDescriber.php | 61 +++++++++++++++++++++++ src/TypeDescriber/DictionaryDescriber.php | 1 + src/TypeDescriber/ListDescriber.php | 1 + 4 files changed, 67 insertions(+) create mode 100644 src/TypeDescriber/ArrayDescriber.php diff --git a/config/services.xml b/config/services.xml index 5dc8457f6..6232ad049 100644 --- a/config/services.xml +++ b/config/services.xml @@ -169,6 +169,10 @@ + + + + diff --git a/src/TypeDescriber/ArrayDescriber.php b/src/TypeDescriber/ArrayDescriber.php new file mode 100644 index 000000000..1eb6e8245 --- /dev/null +++ b/src/TypeDescriber/ArrayDescriber.php @@ -0,0 +1,61 @@ + + * + * @experimental + * + * @internal + */ +final class ArrayDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface +{ + use TypeDescriberAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + if (!$type->getCollectionKeyType() instanceof Type\CompositeTypeInterface) { + return; + } + + $collections = array_map( + fn (Type $keyType): CollectionType => TypeFactoryTrait::collection($type->getWrappedType(), $type->getCollectionValueType(), $keyType), + $type->getCollectionKeyType()->getTypes(), + ); + + if ($type->getCollectionKeyType() instanceof Type\UnionType) { + $describeType = Type::union(...$collections); + } + + if ($type->getCollectionKeyType() instanceof Type\IntersectionType) { + $describeType = Type::intersection(...$collections); + } + + if (!isset($describeType)) { + return; + } + + $this->describer->describe($describeType, $schema, $context); + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof CollectionType + && $type->getCollectionKeyType() instanceof Type\CompositeTypeInterface; + } +} diff --git a/src/TypeDescriber/DictionaryDescriber.php b/src/TypeDescriber/DictionaryDescriber.php index 5d6c5de9e..cc63ed06c 100644 --- a/src/TypeDescriber/DictionaryDescriber.php +++ b/src/TypeDescriber/DictionaryDescriber.php @@ -40,6 +40,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType + && false === $type->getCollectionKeyType() instanceof Type\UnionType && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::STRING); } } diff --git a/src/TypeDescriber/ListDescriber.php b/src/TypeDescriber/ListDescriber.php index d697e6982..74a94fb7e 100644 --- a/src/TypeDescriber/ListDescriber.php +++ b/src/TypeDescriber/ListDescriber.php @@ -40,6 +40,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType + && false === $type->getCollectionKeyType() instanceof Type\UnionType && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::INT); } } From 1efbf2683f909ab197cf8d3452f40d613f8b4dad Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 00:24:20 +0100 Subject: [PATCH 33/78] Revert "document generic arrays" This reverts commit 6417521afb8ddbeeaebd9604aa860b8a179f16b1. --- config/services.xml | 4 -- src/TypeDescriber/ArrayDescriber.php | 61 ----------------------- src/TypeDescriber/DictionaryDescriber.php | 1 - src/TypeDescriber/ListDescriber.php | 1 - 4 files changed, 67 deletions(-) delete mode 100644 src/TypeDescriber/ArrayDescriber.php diff --git a/config/services.xml b/config/services.xml index 6232ad049..5dc8457f6 100644 --- a/config/services.xml +++ b/config/services.xml @@ -169,10 +169,6 @@ - - - - diff --git a/src/TypeDescriber/ArrayDescriber.php b/src/TypeDescriber/ArrayDescriber.php deleted file mode 100644 index 1eb6e8245..000000000 --- a/src/TypeDescriber/ArrayDescriber.php +++ /dev/null @@ -1,61 +0,0 @@ - - * - * @experimental - * - * @internal - */ -final class ArrayDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface -{ - use TypeDescriberAwareTrait; - - public function describe(Type $type, Schema $schema, array $context = []): void - { - if (!$type->getCollectionKeyType() instanceof Type\CompositeTypeInterface) { - return; - } - - $collections = array_map( - fn (Type $keyType): CollectionType => TypeFactoryTrait::collection($type->getWrappedType(), $type->getCollectionValueType(), $keyType), - $type->getCollectionKeyType()->getTypes(), - ); - - if ($type->getCollectionKeyType() instanceof Type\UnionType) { - $describeType = Type::union(...$collections); - } - - if ($type->getCollectionKeyType() instanceof Type\IntersectionType) { - $describeType = Type::intersection(...$collections); - } - - if (!isset($describeType)) { - return; - } - - $this->describer->describe($describeType, $schema, $context); - } - - public function supports(Type $type, array $context = []): bool - { - return $type instanceof CollectionType - && $type->getCollectionKeyType() instanceof Type\CompositeTypeInterface; - } -} diff --git a/src/TypeDescriber/DictionaryDescriber.php b/src/TypeDescriber/DictionaryDescriber.php index cc63ed06c..5d6c5de9e 100644 --- a/src/TypeDescriber/DictionaryDescriber.php +++ b/src/TypeDescriber/DictionaryDescriber.php @@ -40,7 +40,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType - && false === $type->getCollectionKeyType() instanceof Type\UnionType && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::STRING); } } diff --git a/src/TypeDescriber/ListDescriber.php b/src/TypeDescriber/ListDescriber.php index 74a94fb7e..d697e6982 100644 --- a/src/TypeDescriber/ListDescriber.php +++ b/src/TypeDescriber/ListDescriber.php @@ -40,7 +40,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType - && false === $type->getCollectionKeyType() instanceof Type\UnionType && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::INT); } } From 93236dc719372fdea2fca3b408841dadbe97bfd8 Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 00:27:36 +0100 Subject: [PATCH 34/78] remove @internal from trait & interfaces --- src/TypeDescriber/TypeDescriberAwareInterface.php | 2 -- src/TypeDescriber/TypeDescriberAwareTrait.php | 2 -- src/TypeDescriber/TypeDescriberInterface.php | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/TypeDescriber/TypeDescriberAwareInterface.php b/src/TypeDescriber/TypeDescriberAwareInterface.php index 94f581b43..318cc29b3 100644 --- a/src/TypeDescriber/TypeDescriberAwareInterface.php +++ b/src/TypeDescriber/TypeDescriberAwareInterface.php @@ -13,8 +13,6 @@ /** * @experimental - * - * @internal */ interface TypeDescriberAwareInterface { diff --git a/src/TypeDescriber/TypeDescriberAwareTrait.php b/src/TypeDescriber/TypeDescriberAwareTrait.php index 7141d7fa4..760efcda3 100644 --- a/src/TypeDescriber/TypeDescriberAwareTrait.php +++ b/src/TypeDescriber/TypeDescriberAwareTrait.php @@ -13,8 +13,6 @@ /** * @experimental - * - * @internal */ trait TypeDescriberAwareTrait { diff --git a/src/TypeDescriber/TypeDescriberInterface.php b/src/TypeDescriber/TypeDescriberInterface.php index 25c43c158..f2c824872 100644 --- a/src/TypeDescriber/TypeDescriberInterface.php +++ b/src/TypeDescriber/TypeDescriberInterface.php @@ -18,8 +18,6 @@ * @template T of Type * * @experimental - * - * @internal */ interface TypeDescriberInterface { From 9c7909eb43b91950cf277fb7b1d9233df783adff Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 00:30:06 +0100 Subject: [PATCH 35/78] remove trait usage --- src/TypeDescriber/NullableDescriber.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/TypeDescriber/NullableDescriber.php b/src/TypeDescriber/NullableDescriber.php index 342113a1e..e4446b4be 100644 --- a/src/TypeDescriber/NullableDescriber.php +++ b/src/TypeDescriber/NullableDescriber.php @@ -21,10 +21,8 @@ * * @internal */ -final class NullableDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface +final class NullableDescriber implements TypeDescriberInterface { - use TypeDescriberAwareTrait; - public function describe(Type $type, Schema $schema, array $context = []): void { $schema->nullable = true; From bed716e3dac9364eaae3ac3f8edf2c68c31cab69 Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 10:19:25 +0100 Subject: [PATCH 36/78] add functional tests for class to schema --- src/TypeDescriber/BoolDescriber.php | 1 - .../ModelDescriber/Fixtures/ArrayOfInt.json | 33 ++++++ .../ModelDescriber/Fixtures/ArrayOfInt.php | 32 ++++++ .../Fixtures/ArrayOfString.json | 33 ++++++ .../ModelDescriber/Fixtures/ArrayOfString.php | 32 ++++++ .../ModelDescriber/Fixtures/ComplexArray.json | 30 ++++++ .../ModelDescriber/Fixtures/ComplexArray.php | 22 ++++ .../ModelDescriber/Fixtures/SimpleClass.json | 39 +++++++ .../ModelDescriber/Fixtures/SimpleClass.php | 24 +++++ .../Fixtures/TypeInfo/ArrayMixedKeys.json | 24 +++++ .../Fixtures/TypeInfo/ArrayMixedKeys.php | 20 ++++ .../ObjectModelDescriberTest.php | 101 ++++++++++++++++++ .../ObjectModelDescriberTestTypeInfo.php | 39 +++++++ tests/Functional/TestKernel.php | 2 + 14 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/ArrayOfString.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/ComplexArray.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/ComplexArray.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/SimpleClass.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/SimpleClass.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayMixedKeys.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayMixedKeys.php create mode 100644 tests/Functional/ModelDescriber/ObjectModelDescriberTest.php create mode 100644 tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php diff --git a/src/TypeDescriber/BoolDescriber.php b/src/TypeDescriber/BoolDescriber.php index c144413fe..70981fac6 100644 --- a/src/TypeDescriber/BoolDescriber.php +++ b/src/TypeDescriber/BoolDescriber.php @@ -13,7 +13,6 @@ use OpenApi\Annotations\Schema; use Symfony\Component\TypeInfo\Type; -use Symfony\Component\TypeInfo\TypeIdentifier; /** * @implements TypeDescriberInterface diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json new file mode 100644 index 000000000..96d91db65 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json @@ -0,0 +1,33 @@ +{ + "required": [ + "untypedArray", + "arrayOfIntegers", + "listOfIntegers", + "shortArrayOfIntegers" + ], + "properties": { + "untypedArray": { + "type": "array", + "items": {} + }, + "arrayOfIntegers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "listOfIntegers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "shortArrayOfIntegers": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php new file mode 100644 index 000000000..ed694fb34 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php @@ -0,0 +1,32 @@ + + */ + public array $arrayOfIntegers; + + /** + * @var list + */ + public array $listOfIntegers; + + /** + * @var int[] + */ + public array $shortArrayOfIntegers; +} diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.json b/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.json new file mode 100644 index 000000000..5847ea992 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.json @@ -0,0 +1,33 @@ +{ + "required": [ + "untypedArray", + "arrayOfStrings", + "listOfStrings", + "shortArrayOfStrings" + ], + "properties": { + "untypedArray": { + "type": "array", + "items": {} + }, + "arrayOfStrings": { + "type": "array", + "items": { + "type": "string" + } + }, + "listOfStrings": { + "type": "array", + "items": { + "type": "string" + } + }, + "shortArrayOfStrings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php b/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php new file mode 100644 index 000000000..f9ae2adea --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php @@ -0,0 +1,32 @@ + + */ + public array $arrayOfStrings; + + /** + * @var list + */ + public array $listOfStrings; + + /** + * @var string[] + */ + public array $shortArrayOfStrings; +} diff --git a/tests/Functional/ModelDescriber/Fixtures/ComplexArray.json b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.json new file mode 100644 index 000000000..a0394d2d1 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.json @@ -0,0 +1,30 @@ +{ + "required": [ + "untypedArray", + "listOrDict" + ], + "properties": { + "untypedArray": { + "type": "array", + "items": {} + }, + "listOrDict": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "number", + "format": "float" + } + } + ] + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/ComplexArray.php b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.php new file mode 100644 index 000000000..55d7cbc40 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.php @@ -0,0 +1,22 @@ +|array + */ + public array $listOrDict; +} diff --git a/tests/Functional/ModelDescriber/Fixtures/SimpleClass.json b/tests/Functional/ModelDescriber/Fixtures/SimpleClass.json new file mode 100644 index 000000000..84c5c7c12 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/SimpleClass.json @@ -0,0 +1,39 @@ +{ + "required": [ + "name", + "age" + ], + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "integer" + }, + "description": { + "type": "string", + "nullable": true + }, + "height": { + "type": "integer", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/SimpleClass.php b/tests/Functional/ModelDescriber/Fixtures/SimpleClass.php new file mode 100644 index 000000000..c08827217 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/SimpleClass.php @@ -0,0 +1,24 @@ + + */ + public array $mixedArray; +} diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php new file mode 100644 index 000000000..0e6802e65 --- /dev/null +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php @@ -0,0 +1,101 @@ + '3.0.0']); + $openApi = new OpenApi(['_context' => $context]); + $this->modelDescriber = $this->getContainer()->get('nelmio_api_doc.model_describers.object'); + + $modelRegistry = new ModelRegistry([$this->modelDescriber], $openApi); + + $this->modelDescriber->setModelRegistry($modelRegistry); + } + + /** + * @dataProvider provideFixtures + */ + public function testItDescribes(string $class): void + { + $model = new Model(new LegacyType('object', false, $class)); + $schema = new OA\Schema([ + 'type' => 'object', + ]); + + $this->modelDescriber->describe($model, $schema); + + $reflect = new \ReflectionClass($class); + + if (!file_exists($fixtureDir = dirname($reflect->getFileName()).'/'.$reflect->getShortName().'.json')) { + file_put_contents($fixtureDir, $schema->toJson()); + } + + self::assertSame( + self::getFixture($fixtureDir), + $schema->toJson(), + ); + } + + public static function provideFixtures(): \Generator + { + // yield [ + // SimpleClass::class, + // ]; + + yield [ + ArrayOfInt::class, + ]; + + // yield [ + // ArrayOfString::class, + // ]; + // + // yield [ + // ComplexArray::class + // ]; + } + + private static function getFixture(string $fixture): string + { + if (!file_exists($fixture)) { + self::fail(sprintf('The fixture file "%s" does not exist.', $fixture)); + } + + $content = file_get_contents($fixture); + + if (false === $content) { + self::fail(sprintf('Failed to read the fixture file "%s".', $fixture)); + } + + return $content; + } +} diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php new file mode 100644 index 000000000..10630c2ba --- /dev/null +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php @@ -0,0 +1,39 @@ +loadFromExtension('nelmio_api_doc', [ + 'type_info' => self::USE_TYPE_INFO === $this->flag, 'html_config' => [ 'assets_mode' => AssetsMode::BUNDLE, ], From f22d7021c6ee49376ad456cbf31f7d24c4a3a318 Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 16:43:35 +0100 Subject: [PATCH 37/78] fix ChainDescriber always returning true --- src/TypeDescriber/ChainDescriber.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/TypeDescriber/ChainDescriber.php b/src/TypeDescriber/ChainDescriber.php index 7cc5f6297..572d0ad35 100644 --- a/src/TypeDescriber/ChainDescriber.php +++ b/src/TypeDescriber/ChainDescriber.php @@ -63,6 +63,17 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { - return true; + foreach ($this->describers as $describer) { + /* BC layer for Symfony < 6.3 @see https://symfony.com/doc/6.3/service_container/tags.html#reference-tagged-services */ + if ($describer instanceof self) { + continue; + } + + if ($describer->supports($type, $context)) { + return true; + } + } + + return false; } } From cc6c42f28763f39c455400110d8d76f602a9c86d Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 16:43:57 +0100 Subject: [PATCH 38/78] prevent list & dictionary from merging results --- src/TypeDescriber/DictionaryDescriber.php | 3 ++- src/TypeDescriber/ListDescriber.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/TypeDescriber/DictionaryDescriber.php b/src/TypeDescriber/DictionaryDescriber.php index 5d6c5de9e..a8820166f 100644 --- a/src/TypeDescriber/DictionaryDescriber.php +++ b/src/TypeDescriber/DictionaryDescriber.php @@ -40,6 +40,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType - && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::STRING); + && $type->getCollectionKeyType() instanceof Type\BuiltinType + && TypeIdentifier::STRING === $type->getCollectionKeyType()->getTypeIdentifier(); } } diff --git a/src/TypeDescriber/ListDescriber.php b/src/TypeDescriber/ListDescriber.php index d697e6982..25202073a 100644 --- a/src/TypeDescriber/ListDescriber.php +++ b/src/TypeDescriber/ListDescriber.php @@ -40,6 +40,7 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { return $type instanceof CollectionType - && $type->getCollectionKeyType()->isIdentifiedBy(TypeIdentifier::INT); + && $type->getCollectionKeyType() instanceof Type\BuiltinType + && TypeIdentifier::INT === $type->getCollectionKeyType()->getTypeIdentifier(); } } From 2f37e2ca2f9fb4aa1818e7b349010d352f4e54a9 Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 17:03:16 +0100 Subject: [PATCH 39/78] Describe generic array keys --- config/services.xml | 4 +++ src/TypeDescriber/ArrayDescriber.php | 52 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/TypeDescriber/ArrayDescriber.php diff --git a/config/services.xml b/config/services.xml index 5dc8457f6..6232ad049 100644 --- a/config/services.xml +++ b/config/services.xml @@ -169,6 +169,10 @@ + + + + diff --git a/src/TypeDescriber/ArrayDescriber.php b/src/TypeDescriber/ArrayDescriber.php new file mode 100644 index 000000000..00a8bf68e --- /dev/null +++ b/src/TypeDescriber/ArrayDescriber.php @@ -0,0 +1,52 @@ + + * + * @experimental + * + * @internal + */ +final class ArrayDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface +{ + use TypeDescriberAwareTrait; + + public function describe(Type $type, Schema $schema, array $context = []): void + { + if (!$type instanceof CollectionType) { + throw new \LogicException('This describer only supports '.CollectionType::class); + } + + $arrayTypes = array_map( + fn (Type $keyType): Type => Type::array($type->getCollectionValueType(), $keyType), + $type->getCollectionKeyType()->getTypes() + ); + + $union = Type::union( + ...$arrayTypes + ); + + $this->describer->describe($union, $schema, $context); + } + + public function supports(Type $type, array $context = []): bool + { + return $type instanceof CollectionType + && $type->getCollectionKeyType() instanceof Type\UnionType; + } +} From e1c886b309b2483fc5495423d061e95950046255 Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 17:06:43 +0100 Subject: [PATCH 40/78] Describe generic array keys --- src/TypeDescriber/ArrayDescriber.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TypeDescriber/ArrayDescriber.php b/src/TypeDescriber/ArrayDescriber.php index 00a8bf68e..d5b670920 100644 --- a/src/TypeDescriber/ArrayDescriber.php +++ b/src/TypeDescriber/ArrayDescriber.php @@ -28,8 +28,8 @@ final class ArrayDescriber implements TypeDescriberInterface, TypeDescriberAware public function describe(Type $type, Schema $schema, array $context = []): void { - if (!$type instanceof CollectionType) { - throw new \LogicException('This describer only supports '.CollectionType::class); + if (!$type instanceof CollectionType || !$type->getCollectionKeyType() instanceof Type\UnionType) { + throw new \LogicException('This describer only supports '.CollectionType::class.' with '.Type\UnionType::class.' as key type.'); } $arrayTypes = array_map( From e76be968e9ce10503ab303ae306a3dfe3cce47de Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 17:07:03 +0100 Subject: [PATCH 41/78] Schema tests --- phpstan-baseline.neon | 5 -- .../ModelDescriber/Fixtures/ArrayOfInt.php | 1 + .../ModelDescriber/Fixtures/ArrayOfString.php | 1 + .../ModelDescriber/Fixtures/ComplexArray.json | 5 -- .../ModelDescriber/Fixtures/ComplexArray.php | 2 - .../Fixtures/TypeInfo/ArrayOfInt.json | 55 +++++++++++++++++++ .../Fixtures/TypeInfo/ArrayOfString.json | 55 +++++++++++++++++++ .../ObjectModelDescriberTest.php | 26 ++++----- .../ObjectModelDescriberTestTypeInfo.php | 31 +++++++---- 9 files changed, 146 insertions(+), 35 deletions(-) create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfString.json diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index cabb935e2..fbb45d83c 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -164,8 +164,3 @@ parameters: message: "#^Call to function method_exists\\(\\) with 'Hateoas\\\\\\\\Configuration\\\\\\\\Embedded' and 'getType' will always evaluate to true\\.$#" count: 1 path: tests/Functional/BazingaFunctionalTest.php - - - - message: "#^Call to static method getVersion\\(\\) on an unknown class PackageVersions\\\\Versions\\.$#" - count: 1 - path: tests/Helper.php diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php index ed694fb34..bf1e178ff 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php @@ -13,6 +13,7 @@ class ArrayOfInt { + /** @phpstan-ignore-next-line This value type is missing on purpose */ public array $untypedArray; /** diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php b/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php index f9ae2adea..b9a7d50f4 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfString.php @@ -13,6 +13,7 @@ class ArrayOfString { + /** @phpstan-ignore-next-line This value type is missing on purpose */ public array $untypedArray; /** diff --git a/tests/Functional/ModelDescriber/Fixtures/ComplexArray.json b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.json index a0394d2d1..495617130 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ComplexArray.json +++ b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.json @@ -1,13 +1,8 @@ { "required": [ - "untypedArray", "listOrDict" ], "properties": { - "untypedArray": { - "type": "array", - "items": {} - }, "listOrDict": { "oneOf": [ { diff --git a/tests/Functional/ModelDescriber/Fixtures/ComplexArray.php b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.php index 55d7cbc40..12449463d 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ComplexArray.php +++ b/tests/Functional/ModelDescriber/Fixtures/ComplexArray.php @@ -13,8 +13,6 @@ class ComplexArray { - public array $untypedArray; - /** * @var list|array */ diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json new file mode 100644 index 000000000..981ef9981 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json @@ -0,0 +1,55 @@ +{ + "required": [ + "untypedArray", + "arrayOfIntegers", + "listOfIntegers", + "shortArrayOfIntegers" + ], + "properties": { + "untypedArray": { + "oneOf": [ + { + "type": "array", + "items": { + "nullable": true + } + }, + { + "type": "object", + "additionalProperties": { + "nullable": true + } + } + ] + }, + "arrayOfIntegers": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "integer" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + ] + }, + "listOfIntegers": { + "type": "array", + "items": { + "type": "integer" + } + }, + "shortArrayOfIntegers": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfString.json b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfString.json new file mode 100644 index 000000000..771100795 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfString.json @@ -0,0 +1,55 @@ +{ + "required": [ + "untypedArray", + "arrayOfStrings", + "listOfStrings", + "shortArrayOfStrings" + ], + "properties": { + "untypedArray": { + "oneOf": [ + { + "type": "array", + "items": { + "nullable": true + } + }, + { + "type": "object", + "additionalProperties": { + "nullable": true + } + } + ] + }, + "arrayOfStrings": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, + "listOfStrings": { + "type": "array", + "items": { + "type": "string" + } + }, + "shortArrayOfStrings": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php index 0e6802e65..c9a220299 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php @@ -34,7 +34,7 @@ protected function setUp(): void $context = Util::createContext(['version' => '3.0.0']); $openApi = new OpenApi(['_context' => $context]); - $this->modelDescriber = $this->getContainer()->get('nelmio_api_doc.model_describers.object'); + $this->modelDescriber = self::getContainer()->get('nelmio_api_doc.model_describers.object'); $modelRegistry = new ModelRegistry([$this->modelDescriber], $openApi); @@ -44,7 +44,7 @@ protected function setUp(): void /** * @dataProvider provideFixtures */ - public function testItDescribes(string $class): void + public function testItDescribes(string $class, ?string $fixtureDir = null): void { $model = new Model(new LegacyType('object', false, $class)); $schema = new OA\Schema([ @@ -55,7 +55,7 @@ public function testItDescribes(string $class): void $reflect = new \ReflectionClass($class); - if (!file_exists($fixtureDir = dirname($reflect->getFileName()).'/'.$reflect->getShortName().'.json')) { + if (!file_exists($fixtureDir ??= dirname($reflect->getFileName()).'/'.$reflect->getShortName().'.json')) { file_put_contents($fixtureDir, $schema->toJson()); } @@ -67,21 +67,21 @@ public function testItDescribes(string $class): void public static function provideFixtures(): \Generator { - // yield [ - // SimpleClass::class, - // ]; + yield [ + SimpleClass::class, + ]; yield [ ArrayOfInt::class, ]; - // yield [ - // ArrayOfString::class, - // ]; - // - // yield [ - // ComplexArray::class - // ]; + yield [ + ArrayOfString::class, + ]; + + yield [ + ComplexArray::class + ]; } private static function getFixture(string $fixture): string diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php index 10630c2ba..92c37eec7 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php @@ -14,24 +14,35 @@ use Nelmio\ApiDocBundle\Tests\Functional\TestKernel; use Symfony\Component\HttpKernel\KernelInterface; -class ObjectModelDescriberTestTypeInfo extends ObjectModelDescriberTest +final class ObjectModelDescriberTestTypeInfo extends ObjectModelDescriberTest { protected static function createKernel(array $options = []): KernelInterface { return new TestKernel(TestKernel::USE_TYPE_INFO); } - /** - * @dataProvider provideFixtures - * // * @dataProvider provideTypeInfoFixtures - */ - public function testItDescribes(string $class): void + public static function provideFixtures(): \Generator { - parent::testItDescribes($class); - } + /* + * Checks if there is a replacement json file for the fixture + * This can be done in cases where the TypeInfo components is able to provide a better schema + */ + foreach (parent::provideFixtures() as $fixture) { + $class = $fixture[0]; + + $reflect = new \ReflectionClass($class); + if (file_exists($fixtureDir = dirname($reflect->getFileName()).'/TypeInfo/'.$reflect->getShortName().'.json')) { + yield [ + $class, + $fixtureDir + ]; + + continue; + } + + yield $fixture; + } - public static function provideTypeInfoFixtures(): \Generator - { yield [ Fixtures\TypeInfo\ArrayMixedKeys::class ]; From bea102159f5340b215fd55d8df562b172a5fb366 Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 17:18:23 +0100 Subject: [PATCH 42/78] ignore fixtures for phpcs --- .php-cs-fixer.dist.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index ee4c1157c..9f192617e 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,10 +2,20 @@ declare(strict_types=1); +/* + * This file is part of the NelmioApiDocBundle package. + * + * (c) Nelmio + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + $finder = (new PhpCsFixer\Finder()) ->in(__DIR__) ->exclude('var') - ->exclude('tests/Functional/cache'); + ->exclude('tests/Functional/cache') + ->exclude('tests/Functional/ModelDescriber/Fixtures'); return (new PhpCsFixer\Config()) ->setRules([ @@ -13,13 +23,13 @@ '@PHP84Migration' => true, 'header_comment' => [ 'header' => <<
false, ]) From dd54133a53b3c72a2f7ade6def4f39c2543d476a Mon Sep 17 00:00:00 2001 From: djordy Date: Wed, 8 Jan 2025 17:18:47 +0100 Subject: [PATCH 43/78] test scalar properties --- .../Fixtures/NullableScalar.json | 22 +++++++++++++++++ .../Fixtures/NullableScalar.php | 20 ++++++++++++++++ .../ModelDescriber/Fixtures/ScalarTypes.json | 24 +++++++++++++++++++ .../ModelDescriber/Fixtures/ScalarTypes.php | 20 ++++++++++++++++ .../ObjectModelDescriberTest.php | 20 +++++++++------- 5 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 tests/Functional/ModelDescriber/Fixtures/NullableScalar.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/NullableScalar.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/ScalarTypes.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/ScalarTypes.php diff --git a/tests/Functional/ModelDescriber/Fixtures/NullableScalar.json b/tests/Functional/ModelDescriber/Fixtures/NullableScalar.json new file mode 100644 index 000000000..d840fb22d --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/NullableScalar.json @@ -0,0 +1,22 @@ +{ + "properties": { + "int": { + "type": "integer", + "nullable": true + }, + "float": { + "type": "number", + "format": "float", + "nullable": true + }, + "string": { + "type": "string", + "nullable": true + }, + "bool": { + "type": "boolean", + "nullable": true + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/NullableScalar.php b/tests/Functional/ModelDescriber/Fixtures/NullableScalar.php new file mode 100644 index 000000000..3f7efbd2a --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/NullableScalar.php @@ -0,0 +1,20 @@ + Date: Wed, 8 Jan 2025 17:18:54 +0100 Subject: [PATCH 44/78] test mixed property --- .../Fixtures/TypeInfo/MixedTypes.json | 8 ++++++++ .../Fixtures/TypeInfo/MixedTypes.php | 20 +++++++++++++++++++ .../ObjectModelDescriberTestTypeInfo.php | 4 ++++ 3 files changed, 32 insertions(+) create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.php diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.json b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.json new file mode 100644 index 000000000..713ce5374 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.json @@ -0,0 +1,8 @@ +{ + "properties": { + "mixedAnnotation": { + "nullable": true + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.php b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.php new file mode 100644 index 000000000..8dae01836 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/MixedTypes.php @@ -0,0 +1,20 @@ + Date: Fri, 17 Jan 2025 13:33:49 +0100 Subject: [PATCH 45/78] add test method for coverage --- .../ModelDescriber/ObjectModelDescriberTestTypeInfo.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php index ed01dbfa5..a8160d223 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php @@ -21,6 +21,14 @@ protected static function createKernel(array $options = []): KernelInterface return new TestKernel(TestKernel::USE_TYPE_INFO); } + /** + * @dataProvider provideFixtures + */ + public function testItDescribes(string $class, ?string $fixtureDir = null): void + { + parent::testItDescribes($class, $fixtureDir); + } + public static function provideFixtures(): \Generator { /* From e9dbd52d0dbefaf09e5458a164e88ec59c234063 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 14:13:47 +0100 Subject: [PATCH 46/78] fix coverage --- ...peInfo.php => ObjectModelDescriberTypeInfoTest.php} | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) rename tests/Functional/ModelDescriber/{ObjectModelDescriberTestTypeInfo.php => ObjectModelDescriberTypeInfoTest.php} (84%) diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php similarity index 84% rename from tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php rename to tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php index a8160d223..dcba45288 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTestTypeInfo.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php @@ -14,21 +14,13 @@ use Nelmio\ApiDocBundle\Tests\Functional\TestKernel; use Symfony\Component\HttpKernel\KernelInterface; -final class ObjectModelDescriberTestTypeInfo extends ObjectModelDescriberTest +final class ObjectModelDescriberTypeInfoTest extends ObjectModelDescriberTest { protected static function createKernel(array $options = []): KernelInterface { return new TestKernel(TestKernel::USE_TYPE_INFO); } - /** - * @dataProvider provideFixtures - */ - public function testItDescribes(string $class, ?string $fixtureDir = null): void - { - parent::testItDescribes($class, $fixtureDir); - } - public static function provideFixtures(): \Generator { /* From 152a695942ae25dde0e65905c81e6fc4c080d144 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 14:16:58 +0100 Subject: [PATCH 47/78] skip tests if not available --- .../ObjectModelDescriberTypeInfoTest.php | 10 +++ tests/TypeDescriber/BoolDescriberTest.php | 73 ++++++++++++++++++ tests/TypeDescriber/FloatDescriberTest.php | 74 +++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 tests/TypeDescriber/BoolDescriberTest.php create mode 100644 tests/TypeDescriber/FloatDescriberTest.php diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php index dcba45288..4a48800bf 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php @@ -13,6 +13,7 @@ use Nelmio\ApiDocBundle\Tests\Functional\TestKernel; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\TypeInfo\Type; final class ObjectModelDescriberTypeInfoTest extends ObjectModelDescriberTest { @@ -21,6 +22,15 @@ protected static function createKernel(array $options = []): KernelInterface return new TestKernel(TestKernel::USE_TYPE_INFO); } + protected function setUp(): void + { + if (!class_exists(Type::class)) { + self::markTestSkipped('The "symfony/type-info" package is not available.'); + } + + parent::setUp(); // TODO: Change the autogenerated stub + } + public static function provideFixtures(): \Generator { /* diff --git a/tests/TypeDescriber/BoolDescriberTest.php b/tests/TypeDescriber/BoolDescriberTest.php new file mode 100644 index 000000000..aeb1a048b --- /dev/null +++ b/tests/TypeDescriber/BoolDescriberTest.php @@ -0,0 +1,73 @@ +typeDescriber = new BoolDescriber(); + } + + /** + * @dataProvider provideTypes + * + * @param array $context + */ + public function testDescribe(Type $type, array $context = []): void + { + $schema = new OA\Schema([]); + + self::assertTrue($this->typeDescriber->supports($type, $context)); + + $this->typeDescriber->describe($type, $schema, $context); + + self::assertSame([ + 'type' => 'boolean', + ], json_decode($schema->toJson(), true)); + } + + public static function provideTypes(): \Generator + { + $typeResolver = TypeResolver::create(); + + yield [ + Type::bool(), + ]; + + yield [ + $typeResolver->resolve(new \ReflectionProperty(new class { + public bool $property; + }, 'property')), + ]; + + yield [ + $typeResolver->resolve(new \ReflectionMethod(new class { + public function method(): bool + { + } + }, 'method')), + ]; + } +} diff --git a/tests/TypeDescriber/FloatDescriberTest.php b/tests/TypeDescriber/FloatDescriberTest.php new file mode 100644 index 000000000..30ad8dd4d --- /dev/null +++ b/tests/TypeDescriber/FloatDescriberTest.php @@ -0,0 +1,74 @@ +typeDescriber = new FloatDescriber(); + } + + /** + * @dataProvider provideTypes + * + * @param array $context + */ + public function testDescribe(Type $type, array $context = []): void + { + $schema = new OA\Schema([]); + + self::assertTrue($this->typeDescriber->supports($type, $context)); + + $this->typeDescriber->describe($type, $schema, $context); + + self::assertSame([ + 'type' => 'number', + 'format' => 'float', + ], json_decode($schema->toJson(), true)); + } + + public static function provideTypes(): \Generator + { + $typeResolver = TypeResolver::create(); + + yield [ + Type::float(), + ]; + + yield [ + $typeResolver->resolve(new \ReflectionProperty(new class { + public float $property; + }, 'property')), + ]; + + yield [ + $typeResolver->resolve(new \ReflectionMethod(new class { + public function method(): float + { + } + }, 'method')), + ]; + } +} From 44d762914dbe93d186b72004172db11a8f55f2fa Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 14:18:45 +0100 Subject: [PATCH 48/78] remove unit test --- tests/TypeDescriber/BoolDescriberTest.php | 73 --------------------- tests/TypeDescriber/FloatDescriberTest.php | 74 ---------------------- 2 files changed, 147 deletions(-) delete mode 100644 tests/TypeDescriber/BoolDescriberTest.php delete mode 100644 tests/TypeDescriber/FloatDescriberTest.php diff --git a/tests/TypeDescriber/BoolDescriberTest.php b/tests/TypeDescriber/BoolDescriberTest.php deleted file mode 100644 index aeb1a048b..000000000 --- a/tests/TypeDescriber/BoolDescriberTest.php +++ /dev/null @@ -1,73 +0,0 @@ -typeDescriber = new BoolDescriber(); - } - - /** - * @dataProvider provideTypes - * - * @param array $context - */ - public function testDescribe(Type $type, array $context = []): void - { - $schema = new OA\Schema([]); - - self::assertTrue($this->typeDescriber->supports($type, $context)); - - $this->typeDescriber->describe($type, $schema, $context); - - self::assertSame([ - 'type' => 'boolean', - ], json_decode($schema->toJson(), true)); - } - - public static function provideTypes(): \Generator - { - $typeResolver = TypeResolver::create(); - - yield [ - Type::bool(), - ]; - - yield [ - $typeResolver->resolve(new \ReflectionProperty(new class { - public bool $property; - }, 'property')), - ]; - - yield [ - $typeResolver->resolve(new \ReflectionMethod(new class { - public function method(): bool - { - } - }, 'method')), - ]; - } -} diff --git a/tests/TypeDescriber/FloatDescriberTest.php b/tests/TypeDescriber/FloatDescriberTest.php deleted file mode 100644 index 30ad8dd4d..000000000 --- a/tests/TypeDescriber/FloatDescriberTest.php +++ /dev/null @@ -1,74 +0,0 @@ -typeDescriber = new FloatDescriber(); - } - - /** - * @dataProvider provideTypes - * - * @param array $context - */ - public function testDescribe(Type $type, array $context = []): void - { - $schema = new OA\Schema([]); - - self::assertTrue($this->typeDescriber->supports($type, $context)); - - $this->typeDescriber->describe($type, $schema, $context); - - self::assertSame([ - 'type' => 'number', - 'format' => 'float', - ], json_decode($schema->toJson(), true)); - } - - public static function provideTypes(): \Generator - { - $typeResolver = TypeResolver::create(); - - yield [ - Type::float(), - ]; - - yield [ - $typeResolver->resolve(new \ReflectionProperty(new class { - public float $property; - }, 'property')), - ]; - - yield [ - $typeResolver->resolve(new \ReflectionMethod(new class { - public function method(): float - { - } - }, 'method')), - ]; - } -} From b953c202a28fdf1e73526b146bce7b2b6703adc0 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 14:32:44 +0100 Subject: [PATCH 49/78] fix version compare --- .../ModelDescriber/ObjectModelDescriberTypeInfoTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php index 4a48800bf..f87494d37 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php @@ -12,8 +12,8 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber; use Nelmio\ApiDocBundle\Tests\Functional\TestKernel; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelInterface; -use Symfony\Component\TypeInfo\Type; final class ObjectModelDescriberTypeInfoTest extends ObjectModelDescriberTest { @@ -24,11 +24,11 @@ protected static function createKernel(array $options = []): KernelInterface protected function setUp(): void { - if (!class_exists(Type::class)) { - self::markTestSkipped('The "symfony/type-info" package is not available.'); + if (!version_compare(Kernel::VERSION, '7.2.0', '>=')) { + self::markTestSkipped('TypeInfo component is only available in Symfony 7.2 and later'); } - parent::setUp(); // TODO: Change the autogenerated stub + parent::setUp(); } public static function provideFixtures(): \Generator From 296b3c98827dac2236aa3261aba68f1243abf80f Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 14:40:52 +0100 Subject: [PATCH 50/78] remove BC layer --- src/TypeDescriber/ChainDescriber.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/TypeDescriber/ChainDescriber.php b/src/TypeDescriber/ChainDescriber.php index 572d0ad35..0a4d521e2 100644 --- a/src/TypeDescriber/ChainDescriber.php +++ b/src/TypeDescriber/ChainDescriber.php @@ -42,11 +42,6 @@ public function __construct( public function describe(Type $type, Schema $schema, array $context = []): void { foreach ($this->describers as $describer) { - /* BC layer for Symfony < 6.3 @see https://symfony.com/doc/6.3/service_container/tags.html#reference-tagged-services */ - if ($describer instanceof self) { - continue; - } - if ($describer instanceof ModelRegistryAwareInterface) { $describer->setModelRegistry($this->modelRegistry); } @@ -64,11 +59,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void public function supports(Type $type, array $context = []): bool { foreach ($this->describers as $describer) { - /* BC layer for Symfony < 6.3 @see https://symfony.com/doc/6.3/service_container/tags.html#reference-tagged-services */ - if ($describer instanceof self) { - continue; - } - if ($describer->supports($type, $context)) { return true; } From 98413ea6adbd1805ef0a753673fa0d6bc4dbdc45 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 14:57:33 +0100 Subject: [PATCH 51/78] add intersection & plain object test --- .../Fixtures/ClassWithIntersection.json | 18 +++++++++++++ .../Fixtures/ClassWithIntersection.php | 25 +++++++++++++++++++ .../Fixtures/ClassWithObject.json | 11 ++++++++ .../Fixtures/ClassWithObject.php | 17 +++++++++++++ .../TypeInfo/ClassWithIntersection.json | 18 +++++++++++++ .../ClassWithIntersectionNullable.json | 16 ++++++++++++ .../ClassWithIntersectionNullable.php | 25 +++++++++++++++++++ .../Fixtures/TypeInfo/ClassWithObject.json | 12 +++++++++ .../ObjectModelDescriberTest.php | 8 ++++++ .../ObjectModelDescriberTypeInfoTest.php | 4 +++ 10 files changed, 154 insertions(+) create mode 100644 tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/ClassWithObject.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithIntersection.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithIntersectionNullable.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithIntersectionNullable.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithObject.json diff --git a/tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.json b/tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.json new file mode 100644 index 000000000..a694bc444 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.json @@ -0,0 +1,18 @@ +{ + "required": [ + "simpleClass" + ], + "properties": { + "simpleClass": { + "oneOf": [ + { + "$ref": "#/components/schemas/X" + }, + { + "$ref": "#/components/schemas/Y" + } + ] + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.php b/tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.php new file mode 100644 index 000000000..3cf849af5 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/ClassWithIntersection.php @@ -0,0 +1,25 @@ + Date: Fri, 17 Jan 2025 15:03:07 +0100 Subject: [PATCH 52/78] fix intersection test on = 80100) { + yield [ + Fixtures\ClassWithIntersection::class + ]; + } + } private static function getFixture(string $fixture): string From c3a919320159e22a1dc9f2d6ed0ed1b1651645d5 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 15:10:50 +0100 Subject: [PATCH 53/78] fix plain object not describing --- src/PropertyDescriber/ObjectPropertyDescriber.php | 7 +++++++ .../Fixtures/TypeInfo/ClassWithObject.json | 12 ------------ .../ModelDescriber/ObjectModelDescriberTest.php | 1 - 3 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithObject.json diff --git a/src/PropertyDescriber/ObjectPropertyDescriber.php b/src/PropertyDescriber/ObjectPropertyDescriber.php index 5e28272a3..abbcbc21f 100644 --- a/src/PropertyDescriber/ObjectPropertyDescriber.php +++ b/src/PropertyDescriber/ObjectPropertyDescriber.php @@ -54,6 +54,13 @@ public function describe(array $types, OA\Schema $property, ?array $groups = nul $types[0]->getCollectionValueTypes()[0] ?? null, ); // ignore nullable field + if (!$types[0]->getClassName()) { + $property->type = 'object'; + $property->additionalProperties = true; + + return; + } + if ($types[0]->isNullable()) { $weakContext = Util::createWeakContext($property->_context); $schemas = [new OA\Schema(['ref' => $this->modelRegistry->register(new Model($type, $groups, [], $context)), '_context' => $weakContext])]; diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithObject.json b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithObject.json deleted file mode 100644 index 107370be0..000000000 --- a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ClassWithObject.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "required": [ - "name" - ], - "properties": { - "name": { - "type": "object", - "additionalProperties": true - } - }, - "type": "object" -} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php index 0d5339357..01eb865d2 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php @@ -96,7 +96,6 @@ public static function provideFixtures(): \Generator Fixtures\ClassWithIntersection::class ]; } - } private static function getFixture(string $fixture): string From 668f09127b4ff2447de2d74b2c91a7193ae366e9 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 15:10:57 +0100 Subject: [PATCH 54/78] fix plain object not describing --- src/PropertyDescriber/ObjectPropertyDescriber.php | 2 +- tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PropertyDescriber/ObjectPropertyDescriber.php b/src/PropertyDescriber/ObjectPropertyDescriber.php index abbcbc21f..46ec33894 100644 --- a/src/PropertyDescriber/ObjectPropertyDescriber.php +++ b/src/PropertyDescriber/ObjectPropertyDescriber.php @@ -54,7 +54,7 @@ public function describe(array $types, OA\Schema $property, ?array $groups = nul $types[0]->getCollectionValueTypes()[0] ?? null, ); // ignore nullable field - if (!$types[0]->getClassName()) { + if (null === $types[0]->getClassName()) { $property->type = 'object'; $property->additionalProperties = true; diff --git a/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json b/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json index aa6a00dd5..107370be0 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json +++ b/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json @@ -4,7 +4,8 @@ ], "properties": { "name": { - "$ref": "#/components/schemas/" + "type": "object", + "additionalProperties": true } }, "type": "object" From b38e8f088e2e492cd7bc76d4e6b956f8066de8d7 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 21:22:20 +0100 Subject: [PATCH 55/78] remove intersection logic for handling non-objects --- src/TypeDescriber/IntersectionDescriber.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/TypeDescriber/IntersectionDescriber.php b/src/TypeDescriber/IntersectionDescriber.php index fe23a7185..f5bc1d467 100644 --- a/src/TypeDescriber/IntersectionDescriber.php +++ b/src/TypeDescriber/IntersectionDescriber.php @@ -35,13 +35,6 @@ public function describe(Type $type, Schema $schema, array $context = []): void return !$innerType->isIdentifiedBy(TypeIdentifier::NULL); })); - // Ensure that non $ref schemas are not described in allOf - if (1 === count($innerTypes) && !$innerTypes[0] instanceof Type\ObjectType) { - $this->describer->describe($innerTypes[0], $schema, $context); - - return; - } - $weakContext = Util::createWeakContext($schema->_context); foreach ($innerTypes as $innerType) { if (Generator::UNDEFINED === $schema->allOf) { From e8f7dc2587d475388e0ea8b562d48b8b18e52f71 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 21:30:42 +0100 Subject: [PATCH 56/78] remove $type check --- src/TypeDescriber/ArrayDescriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TypeDescriber/ArrayDescriber.php b/src/TypeDescriber/ArrayDescriber.php index d5b670920..ce07fb84c 100644 --- a/src/TypeDescriber/ArrayDescriber.php +++ b/src/TypeDescriber/ArrayDescriber.php @@ -28,7 +28,7 @@ final class ArrayDescriber implements TypeDescriberInterface, TypeDescriberAware public function describe(Type $type, Schema $schema, array $context = []): void { - if (!$type instanceof CollectionType || !$type->getCollectionKeyType() instanceof Type\UnionType) { + if (!$type->getCollectionKeyType() instanceof Type\UnionType) { throw new \LogicException('This describer only supports '.CollectionType::class.' with '.Type\UnionType::class.' as key type.'); } From 71fd87ec48c0cf045143b872574e4dc6b90b4ce7 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 21:30:56 +0100 Subject: [PATCH 57/78] add ArrayDescriberTest --- tests/TypeDescriber/ArrayDescriberTest.php | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/TypeDescriber/ArrayDescriberTest.php diff --git a/tests/TypeDescriber/ArrayDescriberTest.php b/tests/TypeDescriber/ArrayDescriberTest.php new file mode 100644 index 000000000..122caf10b --- /dev/null +++ b/tests/TypeDescriber/ArrayDescriberTest.php @@ -0,0 +1,53 @@ +=')) { + self::markTestSkipped('TypeInfo component is only available in Symfony 7.2 and later'); + } + + $this->describer = new ArrayDescriber(); + } + + /** + * @dataProvider provideInvalidCollectionTypes + */ + public function testDescribeHandlesInvalidKeyType(Type $type): void + { + self::expectException(\LogicException::class); + self::expectExceptionMessage('This describer only supports '.CollectionType::class.' with '.UnionType::class.' as key type.'); + + $this->describer->describe($type, new Schema([])); + } + + public static function provideInvalidCollectionTypes(): \Generator + { + yield [Type::array(Type::int(), Type::int())]; + yield [Type::array(Type::int(), Type::string())]; + yield [Type::list()]; + yield [Type::dict()]; + } +} From b6bdb8ecfddae2886f00d017d6b5c3b1021a84b1 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 21:38:23 +0100 Subject: [PATCH 58/78] test uuid & datetime --- .../Fixtures/TypeInfo/DateTimeClass.json | 22 ++++++++ .../Fixtures/TypeInfo/DateTimeClass.php | 19 +++++++ .../Fixtures/TypeInfo/UuidClass.json | 52 +++++++++++++++++++ .../Fixtures/TypeInfo/UuidClass.php | 35 +++++++++++++ .../ObjectModelDescriberTypeInfoTest.php | 8 +++ 5 files changed, 136 insertions(+) create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.php create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/UuidClass.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/TypeInfo/UuidClass.php diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.json b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.json new file mode 100644 index 000000000..630f8fe80 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.json @@ -0,0 +1,22 @@ +{ + "required": [ + "dateTime", + "dateTimeImmutable", + "dateTimeInterface" + ], + "properties": { + "dateTime": { + "type": "string", + "format": "date-time" + }, + "dateTimeImmutable": { + "type": "string", + "format": "date-time" + }, + "dateTimeInterface": { + "type": "string", + "format": "date-time" + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.php b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.php new file mode 100644 index 000000000..a539c73c1 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.php @@ -0,0 +1,19 @@ + Date: Fri, 17 Jan 2025 21:41:24 +0100 Subject: [PATCH 59/78] phpstan --- tests/TypeDescriber/ArrayDescriberTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TypeDescriber/ArrayDescriberTest.php b/tests/TypeDescriber/ArrayDescriberTest.php index 122caf10b..4375eaf28 100644 --- a/tests/TypeDescriber/ArrayDescriberTest.php +++ b/tests/TypeDescriber/ArrayDescriberTest.php @@ -35,7 +35,7 @@ protected function setUp(): void /** * @dataProvider provideInvalidCollectionTypes */ - public function testDescribeHandlesInvalidKeyType(Type $type): void + public function testDescribeHandlesInvalidKeyType(CollectionType $type): void { self::expectException(\LogicException::class); self::expectExceptionMessage('This describer only supports '.CollectionType::class.' with '.UnionType::class.' as key type.'); From 37a4aa8df01683a641770acd9c021ef5e9fc7dd5 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 21:44:19 +0100 Subject: [PATCH 60/78] fix phpunit --- tests/TypeDescriber/ArrayDescriberTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/TypeDescriber/ArrayDescriberTest.php b/tests/TypeDescriber/ArrayDescriberTest.php index 4375eaf28..2117b890b 100644 --- a/tests/TypeDescriber/ArrayDescriberTest.php +++ b/tests/TypeDescriber/ArrayDescriberTest.php @@ -34,8 +34,10 @@ protected function setUp(): void /** * @dataProvider provideInvalidCollectionTypes + * + * @param CollectionType $type */ - public function testDescribeHandlesInvalidKeyType(CollectionType $type): void + public function testDescribeHandlesInvalidKeyType($type): void { self::expectException(\LogicException::class); self::expectExceptionMessage('This describer only supports '.CollectionType::class.' with '.UnionType::class.' as key type.'); @@ -45,6 +47,10 @@ public function testDescribeHandlesInvalidKeyType(CollectionType $type): void public static function provideInvalidCollectionTypes(): \Generator { + if (!version_compare(Kernel::VERSION, '7.2.0', '>=')) { + return yield [false]; + } + yield [Type::array(Type::int(), Type::int())]; yield [Type::array(Type::int(), Type::string())]; yield [Type::list()]; From a3692c847ba72fab0e618d1615600b1051e09ce2 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 22:16:58 +0100 Subject: [PATCH 61/78] test invalid types --- src/ModelDescriber/ObjectModelDescriber.php | 2 +- .../ObjectModelDescriberTest.php | 2 +- .../ObjectModelDescriberTypeInfoTest.php | 43 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index a076f26a9..da5a64361 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -162,7 +162,7 @@ public function describe(Model $model, OA\Schema $schema) $type = $this->propertyInfo->getType($class, $propertyName); if (null === $type) { - throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); + throw new \LogicException(sprintf('The TypeInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); } if ($this->propertyDescriber instanceof ModelRegistryAwareInterface) { diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php index 01eb865d2..895cd8546 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTest.php @@ -22,7 +22,7 @@ class ObjectModelDescriberTest extends WebTestCase { - private ObjectModelDescriber $modelDescriber; + protected ObjectModelDescriber $modelDescriber; protected function setUp(): void { diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php index 20771851e..5001d4dff 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php @@ -11,9 +11,12 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber; +use Nelmio\ApiDocBundle\Model\Model; use Nelmio\ApiDocBundle\Tests\Functional\TestKernel; +use OpenApi\Annotations as OA; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\PropertyInfo\Type as LegacyType; final class ObjectModelDescriberTypeInfoTest extends ObjectModelDescriberTest { @@ -73,4 +76,44 @@ public static function provideFixtures(): \Generator Fixtures\TypeInfo\DateTimeClass::class ]; } + + /** + * @dataProvider provideInvalidTypes + */ + public function testInvalidType(object $class, string $expectedType, string $propertyName): void + { + $model = new Model(new LegacyType('object', false, get_class($class))); + $schema = new OA\Schema([ + 'type' => 'object', + ]); + + self::expectException(\Exception::class); + self::expectExceptionMessage(sprintf('Type "%s" not supported in %s::%s. You may need to use the `@OA\Property(type="")` annotation to specify it manually.', $expectedType, get_class($class), $propertyName)); + + $this->modelDescriber->describe($model, $schema); + } + + public static function provideInvalidTypes(): \Generator + { + yield 'never' => [ + new class { + public function getNever(): never + { + throw new \Exception('This method should never be called'); + } + }, + 'never', + '$never', + ]; + + yield 'void' => [ + new class { + public function getVoid(): void + { + } + }, + 'void', + '$void', + ]; + } } From c6f3bde5a7a8cf7b30844490609cc3256c267d8d Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 22:49:14 +0100 Subject: [PATCH 62/78] simplify object model describer --- phpstan-baseline.neon | 2 +- src/ModelDescriber/ObjectModelDescriber.php | 48 +++++++------------ .../ObjectModelDescriberTypeInfoTest.php | 2 +- 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fbb45d83c..512479676 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -26,7 +26,7 @@ parameters: path: src/Describer/ExternalDocDescriber.php - - message: "#^Call to function method_exists\\(\\) with Symfony\\\\Component\\\\PropertyInfo\\\\PropertyInfoExtractorInterface and 'getType' will always evaluate to true\\.$#" + message: "#^Method Nelmio\\\\ApiDocBundle\\\\PropertyDescriber\\\\PropertyDescriberInterface\\:\\:describe\\(\\) invoked with 5 parameters, 2\\-3 required\\.$#" count: 1 path: src/ModelDescriber/ObjectModelDescriber.php diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index da5a64361..38f0ce7f0 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -22,10 +22,11 @@ use OpenApi\Annotations as OA; use OpenApi\Generator; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; -use Symfony\Component\PropertyInfo\Type; +use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\TypeInfo\Type; class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwareInterface { @@ -152,36 +153,17 @@ public function describe(Model $model, OA\Schema $schema) continue; } - /* - * @experimental - */ if ($this->propertyDescriber instanceof TypeDescriberInterface) { - if (false === method_exists($this->propertyInfo, 'getType')) { - throw new \RuntimeException('The PropertyInfo component is missing the "getType" method. Are you running on version 7.1?'); - } - - $type = $this->propertyInfo->getType($class, $propertyName); - if (null === $type) { - throw new \LogicException(sprintf('The TypeInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); - } - - if ($this->propertyDescriber instanceof ModelRegistryAwareInterface) { - $this->propertyDescriber->setModelRegistry($this->modelRegistry); - } - - if (!$this->propertyDescriber->supports($type, $model->getSerializationContext())) { - throw new \Exception(sprintf('Type "%s" not supported in %s::$%s. You may need to use the `@OA\Property(type="")` annotation to specify it manually.', $type->__toString(), $model->getType()->getClassName(), $propertyName)); - } - - $this->propertyDescriber->describe($type, $property, $model->getSerializationContext()); + $types = $this->propertyInfo->getType($class, $propertyName); } else { $types = $this->propertyInfo->getTypes($class, $propertyName); - if (null === $types || 0 === count($types)) { - throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); - } + } - $this->describeProperty($types, $model, $property, $propertyName, $schema); + if (null === $types) { + throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName)); } + + $this->describeProperty($types, $model, $property, $propertyName, $schema); } $this->markRequiredProperties($schema); @@ -216,9 +198,9 @@ private function camelize(string $string): string } /** - * @param Type[] $types + * @param LegacyType[]|Type $types */ - private function describeProperty(array $types, Model $model, OA\Schema $property, string $propertyName, OA\Schema $schema): void + private function describeProperty($types, Model $model, OA\Schema $property, string $propertyName, OA\Schema $schema): void { $propertyDescribers = is_iterable($this->propertyDescriber) ? $this->propertyDescriber : [$this->propertyDescriber]; @@ -227,13 +209,17 @@ private function describeProperty(array $types, Model $model, OA\Schema $propert $propertyDescriber->setModelRegistry($this->modelRegistry); } if ($propertyDescriber->supports($types, $model->getSerializationContext())) { - $propertyDescriber->describe($types, $property, $model->getGroups(), $schema, $model->getSerializationContext()); + if ($propertyDescriber instanceof PropertyDescriberInterface) { + $propertyDescriber->describe($types, $property, $model->getGroups(), $schema, $model->getSerializationContext()); + } else { + $propertyDescriber->describe($types, $property, $model->getSerializationContext()); + } return; } } - throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may use the `@OA\Property(type="")` annotation to specify it manually.', $types[0]->getBuiltinType(), $model->getType()->getClassName(), $propertyName)); + throw new \Exception(sprintf('Type "%s" is not supported in %s::$%s. You may need to use the `@OA\Property(type="")` annotation to specify it manually.', is_array($types) ? $types[0]->getBuiltinType() : $types, $model->getType()->getClassName(), $propertyName)); } /** @@ -268,7 +254,7 @@ private function markRequiredProperties(OA\Schema $schema): void public function supports(Model $model): bool { - return Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() + return LegacyType::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() && (class_exists($model->getType()->getClassName()) || interface_exists($model->getType()->getClassName())); } } diff --git a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php index 5001d4dff..e2492a789 100644 --- a/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php +++ b/tests/Functional/ModelDescriber/ObjectModelDescriberTypeInfoTest.php @@ -88,7 +88,7 @@ public function testInvalidType(object $class, string $expectedType, string $pro ]); self::expectException(\Exception::class); - self::expectExceptionMessage(sprintf('Type "%s" not supported in %s::%s. You may need to use the `@OA\Property(type="")` annotation to specify it manually.', $expectedType, get_class($class), $propertyName)); + self::expectExceptionMessage(sprintf('Type "%s" is not supported in %s::%s. You may need to use the `@OA\Property(type="")` annotation to specify it manually.', $expectedType, get_class($class), $propertyName)); $this->modelDescriber->describe($model, $schema); } From 14c2b0f11e164116af9fa926db8d48544da0224a Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 22:57:28 +0100 Subject: [PATCH 63/78] update config description --- src/DependencyInjection/Configuration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index ee9103213..22ee2341f 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -26,7 +26,7 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() ->booleanNode('type_info') - ->info('Use the symfony/type-info component for determining types. This is experimental and could be changed at any time without prior notice.') + ->info('Use the symfony/type-info component for determining types.') ->defaultFalse() ->end() ->booleanNode('use_validation_groups') From 9fb0a63960b6f40c925adcaaf1991a348c1f3fe4 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 22:59:02 +0100 Subject: [PATCH 64/78] changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc5b94c5..23834174f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## 4.35.0 +* Added support for the symfony/type-info component +```yaml +nelmio_api_doc: + type_info: true +``` + ## 4.34.0 * Changed minimum Symfony version for 7.x from 7.0 to 7.1 From 3756775aaeb2b716c30c09f0990f1f3598348550 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 23:11:48 +0100 Subject: [PATCH 65/78] move faq to bottom --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index deb90afc9..d34407a51 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -601,9 +601,9 @@ If you need more complex features, take a look at: alternative_names customization commands - faq security symfony_attributes + faq .. _`SwaggerPHP examples`: https://github.com/zircote/swagger-php/tree/master/Examples .. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html From 46aa7f3f3fb62440cbbcbd8a336c0c33160ba039 Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 13:23:17 +0100 Subject: [PATCH 66/78] bump versionadded --- .doctor-rst.yaml | 8 ++++---- docs/symfony_attributes.rst | 14 -------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index ae39fbe61..72fd4a295 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -53,16 +53,16 @@ rules: # master versionadded_directive_major_version: - major_version: 6 + major_version: 7 versionadded_directive_min_version: - min_version: '6.0' + min_version: '7.0' deprecated_directive_major_version: - major_version: 5 + major_version: 6 deprecated_directive_min_version: - min_version: '5.0' + min_version: '6.0' # do not report as violation whitelist: diff --git a/docs/symfony_attributes.rst b/docs/symfony_attributes.rst index fa149a074..8efe9a411 100644 --- a/docs/symfony_attributes.rst +++ b/docs/symfony_attributes.rst @@ -8,10 +8,6 @@ MapQueryString Using the `Symfony MapQueryString`_ attribute allows NelmioApiDocBundle to automatically generate your query parameter documentation for your endpoint from your object. -.. versionadded:: 6.3 - - The :class:`Symfony\\Component\\HttpKernel\\Attribute\\MapQueryString` attribute was introduced in Symfony 6.3. - Modify generated documentation ~~~~~~~ @@ -35,11 +31,6 @@ MapQueryParameter Using the `Symfony MapQueryParameter`_ attribute allows NelmioApiDocBundle to automatically generate your query parameter documentation for your endpoint. -.. versionadded:: 6.3 - - The :class:`Symfony\\Component\\HttpKernel\\Attribute\\MapQueryParameter` attribute was introduced in Symfony 6.3. - - Modify generated documentation ~~~~~~~ @@ -59,11 +50,6 @@ MapRequestPayload Using the `Symfony MapRequestPayload`_ attribute allows NelmioApiDocBundle to automatically generate your request body documentation for your endpoint. -.. versionadded:: 6.3 - - The :class:`Symfony\\Component\\HttpKernel\\Attribute\\MapRequestPayload` attribute was introduced in Symfony 6.3. - - Modify generated documentation ~~~~~~~ From 54dd7671c8902e914693a48a20e9d2649e7bc9fb Mon Sep 17 00:00:00 2001 From: djordy Date: Fri, 17 Jan 2025 23:53:26 +0100 Subject: [PATCH 67/78] docs --- docs/faq.rst | 83 +++++++++++++++++++++++++++++++++++++++++++++++++- docs/index.rst | 15 +++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/docs/faq.rst b/docs/faq.rst index 496eb03be..5393dc493 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -262,4 +262,85 @@ Note, however, that a ``type="object"`` will still read all a models properties. class SomeCollection implements \IteratorAggregate { // ... - } \ No newline at end of file + } + +PropertyInfo component was unable to guess the type +--------------------------------------------------- + +Q: I have a property that is not recognized. How can I specify the type? + +.. tip:: + + Enable the `TypeInfo component`_ in your configuration to improve automatic type guessing: + + .. code-block:: yaml + + nelmio_api_doc: + type_info: true + # ... + +.. versionadded:: 7.2 + + The `TypeInfo component`_ was introduced as a stable feature in Symfony 7.2. + +A: If you want to customize the documentation of an object's property, you can use the ``#[OA\Property]`` attribute or annotate the property with ``@var``:: + +.. configuration-block:: + + .. code-block:: php-annotations + + use Nelmio\ApiDocBundle\Attribute\Model; + use OpenApi\Annotations as OA; + + class User + { + /** + * @var int + * @OA\Property(description="The unique identifier of the user.") + */ + public $id; + + /** + * @OA\Property(type="string", maxLength=255) + */ + public $username; + + /** + * @OA\Property(ref=@Model(type=User::class)) + */ + public $friend; + + /** + * @OA\Property(description="This is my coworker!") + */ + public setCoworker(User $coworker) { + // ... + } + } + + .. code-block:: php-attributes + + use Nelmio\ApiDocBundle\Attribute\Model; + use OpenApi\Attributes as OA; + + class User + { + /** + * @var int + */ + #[OA\Property(description: 'The unique identifier of the user.')] + public $id; + + #[OA\Property(type: 'string', maxLength: 255)] + public $username; + + #[OA\Property(ref: new Model(type: User::class))] + public $friend; + + #[OA\Property(description: 'This is my coworker!')] + public setCoworker(User $coworker) { + // ... + } + } + +.. _`TypeInfo component`: https://symfony.com/doc/current/components/type_info.html diff --git a/docs/index.rst b/docs/index.rst index d34407a51..9f3e2c381 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -524,6 +524,20 @@ General PHP objects When using the JMS serializer combined with `willdurand/Hateoas`_ (and the `BazingaHateoasBundle`_), HATEOAS metadata are automatically extracted +.. tip:: + + Enable the `TypeInfo component`_ in your configuration to improve automatic type guessing: + + .. code-block:: yaml + + nelmio_api_doc: + type_info: true + # ... + +.. versionadded:: 7.2 + + The `TypeInfo component`_ was introduced as a stable feature in Symfony 7.2. + If you want to customize the documentation of an object's property, you can use ``#[OA\Property]``:: @@ -607,6 +621,7 @@ If you need more complex features, take a look at: .. _`SwaggerPHP examples`: https://github.com/zircote/swagger-php/tree/master/Examples .. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html +.. _`TypeInfo component`: https://symfony.com/doc/current/components/type_info.html .. _`willdurand/Hateoas`: https://github.com/willdurand/Hateoas .. _`BazingaHateoasBundle`: https://github.com/willdurand/BazingaHateoasBundle .. _`JMS serializer`: https://jmsyst.com/libs/serializer From 19730aec53cb0002dbcc7d85d92ed40d4719d7f1 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 15:02:59 +0100 Subject: [PATCH 68/78] expand test cases --- .../Fixtures/ClassWithObject.json | 5 +++++ .../Fixtures/ClassWithObject.php | 1 + .../{TypeInfo => }/DateTimeClass.json | 5 +++++ .../Fixtures/{TypeInfo => }/DateTimeClass.php | 4 +++- .../ModelDescriber/Fixtures/Refs.json | 19 ++++++++++++++++ .../ModelDescriber/Fixtures/Refs.php | 22 +++++++++++++++++++ .../Fixtures/{TypeInfo => }/UuidClass.json | 5 +++++ .../Fixtures/{TypeInfo => }/UuidClass.php | 4 +++- .../ObjectModelDescriberTest.php | 12 ++++++++++ .../ObjectModelDescriberTypeInfoTest.php | 8 ------- 10 files changed, 75 insertions(+), 10 deletions(-) rename tests/Functional/ModelDescriber/Fixtures/{TypeInfo => }/DateTimeClass.json (77%) rename tests/Functional/ModelDescriber/Fixtures/{TypeInfo => }/DateTimeClass.php (89%) create mode 100644 tests/Functional/ModelDescriber/Fixtures/Refs.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/Refs.php rename tests/Functional/ModelDescriber/Fixtures/{TypeInfo => }/UuidClass.json (89%) rename tests/Functional/ModelDescriber/Fixtures/{TypeInfo => }/UuidClass.php (95%) diff --git a/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json b/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json index 107370be0..be2c684d4 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json +++ b/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.json @@ -6,6 +6,11 @@ "name": { "type": "object", "additionalProperties": true + }, + "nullableObject": { + "type": "object", + "nullable": true, + "additionalProperties": true } }, "type": "object" diff --git a/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.php b/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.php index b9af84665..eef98be18 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.php +++ b/tests/Functional/ModelDescriber/Fixtures/ClassWithObject.php @@ -14,4 +14,5 @@ class ClassWithObject { public object $name; + public ?object $nullableObject; } diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.json b/tests/Functional/ModelDescriber/Fixtures/DateTimeClass.json similarity index 77% rename from tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.json rename to tests/Functional/ModelDescriber/Fixtures/DateTimeClass.json index 630f8fe80..e633cef2b 100644 --- a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.json +++ b/tests/Functional/ModelDescriber/Fixtures/DateTimeClass.json @@ -16,6 +16,11 @@ "dateTimeInterface": { "type": "string", "format": "date-time" + }, + "nullableDateTime": { + "type": "string", + "format": "date-time", + "nullable": true } }, "type": "object" diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.php b/tests/Functional/ModelDescriber/Fixtures/DateTimeClass.php similarity index 89% rename from tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.php rename to tests/Functional/ModelDescriber/Fixtures/DateTimeClass.php index a539c73c1..3607972f3 100644 --- a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/DateTimeClass.php +++ b/tests/Functional/ModelDescriber/Fixtures/DateTimeClass.php @@ -9,11 +9,13 @@ * file that was distributed with this source code. */ -namespace Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\Fixtures\TypeInfo; +namespace Nelmio\ApiDocBundle\Tests\Functional\ModelDescriber\Fixtures; class DateTimeClass { public \DateTime $dateTime; public \DateTimeImmutable $dateTimeImmutable; public \DateTimeInterface $dateTimeInterface; + + public ?\DateTime $nullableDateTime; } diff --git a/tests/Functional/ModelDescriber/Fixtures/Refs.json b/tests/Functional/ModelDescriber/Fixtures/Refs.json new file mode 100644 index 000000000..f3349c790 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/Refs.json @@ -0,0 +1,19 @@ +{ + "required": [ + "someRefClass" + ], + "properties": { + "someRefClass": { + "$ref": "#/components/schemas/SomeRefClass" + }, + "someRefClassNullable": { + "oneOf": [ + { + "$ref": "#/components/schemas/SomeRefClass" + } + ], + "nullable": true + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/Refs.php b/tests/Functional/ModelDescriber/Fixtures/Refs.php new file mode 100644 index 000000000..68e95a6b6 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/Refs.php @@ -0,0 +1,22 @@ + Date: Sat, 18 Jan 2025 15:13:16 +0100 Subject: [PATCH 69/78] update service definition --- config/services.xml | 60 +++++++++---------- .../NelmioApiDocExtension.php | 2 +- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/config/services.xml b/config/services.xml index 6232ad049..099469be5 100644 --- a/config/services.xml +++ b/config/services.xml @@ -162,63 +162,63 @@ - - - + + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/DependencyInjection/NelmioApiDocExtension.php b/src/DependencyInjection/NelmioApiDocExtension.php index 5c46eabf8..d79d7c27e 100644 --- a/src/DependencyInjection/NelmioApiDocExtension.php +++ b/src/DependencyInjection/NelmioApiDocExtension.php @@ -167,7 +167,7 @@ public function load(array $configs, ContainerBuilder $container): void if (true === $config['type_info']) { $container->getDefinition('nelmio_api_doc.model_describers.object') - ->setArgument(2, new Reference('nelmio_api_doc.schema_describer.chain')); + ->setArgument(2, new Reference('nelmio_api_doc.type_describer.chain')); } $container->getDefinition('nelmio_api_doc.model_describers.object') From 3271f35e6ff603f2a201d80b7276c7bffd10a4fd Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 15:23:48 +0100 Subject: [PATCH 70/78] add priority to allow better customization --- config/services.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/config/services.xml b/config/services.xml index 099469be5..2cdb98287 100644 --- a/config/services.xml +++ b/config/services.xml @@ -170,39 +170,39 @@ - + - + - + - + - + - + - + - + - + @@ -210,15 +210,15 @@ - + - + - + From e3a5dcd8d55ba2605ee812066e1ba25f2e11b74b Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 15:28:58 +0100 Subject: [PATCH 71/78] remove null type handling from IntersectionDescriber --- src/TypeDescriber/IntersectionDescriber.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/TypeDescriber/IntersectionDescriber.php b/src/TypeDescriber/IntersectionDescriber.php index f5bc1d467..be36d7317 100644 --- a/src/TypeDescriber/IntersectionDescriber.php +++ b/src/TypeDescriber/IntersectionDescriber.php @@ -16,7 +16,6 @@ use OpenApi\Generator; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\IntersectionType; -use Symfony\Component\TypeInfo\TypeIdentifier; /** * @implements TypeDescriberInterface @@ -31,12 +30,8 @@ final class IntersectionDescriber implements TypeDescriberInterface, TypeDescrib public function describe(Type $type, Schema $schema, array $context = []): void { - $innerTypes = array_values(array_filter($type->getTypes(), function (Type $innerType) { - return !$innerType->isIdentifiedBy(TypeIdentifier::NULL); - })); - $weakContext = Util::createWeakContext($schema->_context); - foreach ($innerTypes as $innerType) { + foreach ($type->getTypes() as $innerType) { if (Generator::UNDEFINED === $schema->allOf) { $schema->allOf = []; } From 3bc93d7843c1f7a25271aa7b296918ff532b0f2c Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 15:54:20 +0100 Subject: [PATCH 72/78] increase nullable priority --- config/services.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/services.xml b/config/services.xml index 2cdb98287..a26211e17 100644 --- a/config/services.xml +++ b/config/services.xml @@ -202,7 +202,7 @@ - + From 87f5643db85aa16a8cd59c2fdecdff8570f2b991 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 15:54:41 +0100 Subject: [PATCH 73/78] Do not check for object type in union --- src/TypeDescriber/UnionDescriber.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TypeDescriber/UnionDescriber.php b/src/TypeDescriber/UnionDescriber.php index ea4447c92..a754fbb53 100644 --- a/src/TypeDescriber/UnionDescriber.php +++ b/src/TypeDescriber/UnionDescriber.php @@ -35,8 +35,8 @@ public function describe(Type $type, Schema $schema, array $context = []): void return !$innerType->isIdentifiedBy(TypeIdentifier::NULL); })); - // Ensure that non $ref schemas are not described in oneOf - if (1 === count($innerTypes) && !$innerTypes[0] instanceof Type\ObjectType) { + // Ensure that union types of a single type are not described in oneOf + if (1 === count($innerTypes)) { $this->describer->describe($innerTypes[0], $schema, $context); return; From eb17f113ca9d718ef32c1ddb89a5bb4f343fbce7 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 15:55:10 +0100 Subject: [PATCH 74/78] test nullable array --- tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json | 7 +++++++ tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php | 5 +++++ tests/Functional/ModelDescriber/Fixtures/Refs.json | 4 ++-- .../ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json | 7 +++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json index 96d91db65..064e309e7 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.json @@ -27,6 +27,13 @@ "items": { "type": "integer" } + }, + "shortArrayOfIntegersNullable": { + "type": "array", + "items": { + "type": "integer" + }, + "nullable": true } }, "type": "object" diff --git a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php index bf1e178ff..241cc420e 100644 --- a/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php +++ b/tests/Functional/ModelDescriber/Fixtures/ArrayOfInt.php @@ -30,4 +30,9 @@ class ArrayOfInt * @var int[] */ public array $shortArrayOfIntegers; + + /** + * @var int[]|null + */ + public ?array $shortArrayOfIntegersNullable; } diff --git a/tests/Functional/ModelDescriber/Fixtures/Refs.json b/tests/Functional/ModelDescriber/Fixtures/Refs.json index f3349c790..75b87c55e 100644 --- a/tests/Functional/ModelDescriber/Fixtures/Refs.json +++ b/tests/Functional/ModelDescriber/Fixtures/Refs.json @@ -7,12 +7,12 @@ "$ref": "#/components/schemas/SomeRefClass" }, "someRefClassNullable": { + "nullable": true, "oneOf": [ { "$ref": "#/components/schemas/SomeRefClass" } - ], - "nullable": true + ] } }, "type": "object" diff --git a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json index 981ef9981..e6e12c675 100644 --- a/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json +++ b/tests/Functional/ModelDescriber/Fixtures/TypeInfo/ArrayOfInt.json @@ -49,6 +49,13 @@ "items": { "type": "integer" } + }, + "shortArrayOfIntegersNullable": { + "type": "array", + "items": { + "type": "integer" + }, + "nullable": true } }, "type": "object" From 52792eebb14aa7d7ef84aac5639dfbadf0cdf74a Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 16:02:32 +0100 Subject: [PATCH 75/78] ensure refs are valid --- src/TypeDescriber/ObjectClassDescriber.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/TypeDescriber/ObjectClassDescriber.php b/src/TypeDescriber/ObjectClassDescriber.php index 38c670d6d..22781006f 100644 --- a/src/TypeDescriber/ObjectClassDescriber.php +++ b/src/TypeDescriber/ObjectClassDescriber.php @@ -14,7 +14,9 @@ use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; use Nelmio\ApiDocBundle\Model\Model; +use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations\Schema; +use OpenApi\Generator; use Symfony\Component\PropertyInfo\Type as LegacyType; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\ObjectType; @@ -47,6 +49,18 @@ public function describe(Type $type, Schema $schema, array $context = []): void return; } + // Ensure that the schema gets describe in oneOf for nullable objects + if (true === $schema->nullable) { + $weakContext = Util::createWeakContext($schema->_context); + if (Generator::UNDEFINED === $schema->oneOf) { + $schema->oneOf = []; + } + + $schema = $schema->oneOf[] = new Schema([ + '_context' => $weakContext + ]); + } + $schema->ref = $this->modelRegistry->register( new Model(new LegacyType('object', false, $type->getClassName()), null, null, $context) ); From 5589601c7fce64f5f402e9839b084695593d1a14 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 16:09:25 +0100 Subject: [PATCH 76/78] rename to ClassDescriber --- config/services.xml | 8 ++++---- .../{ObjectClassDescriber.php => ClassDescriber.php} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/TypeDescriber/{ObjectClassDescriber.php => ClassDescriber.php} (95%) diff --git a/config/services.xml b/config/services.xml index a26211e17..f99aacaf3 100644 --- a/config/services.xml +++ b/config/services.xml @@ -177,6 +177,10 @@ + + + + @@ -205,10 +209,6 @@ - - - - diff --git a/src/TypeDescriber/ObjectClassDescriber.php b/src/TypeDescriber/ClassDescriber.php similarity index 95% rename from src/TypeDescriber/ObjectClassDescriber.php rename to src/TypeDescriber/ClassDescriber.php index 22781006f..f228f91b0 100644 --- a/src/TypeDescriber/ObjectClassDescriber.php +++ b/src/TypeDescriber/ClassDescriber.php @@ -29,7 +29,7 @@ * * @internal */ -final class ObjectClassDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface +final class ClassDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface { use ModelRegistryAwareTrait; From 38517532340ddbf94f5ea923868391b241f7a01e Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 16:24:49 +0100 Subject: [PATCH 77/78] fix non-existing uuid 7 & 8 in Symfony 5.4 --- .../ModelDescriber/Fixtures/UuidClass.json | 12 +--- .../ModelDescriber/Fixtures/UuidClass.php | 4 -- .../Fixtures/UuidClass7And8.json | 57 +++++++++++++++++++ .../Fixtures/UuidClass7And8.php | 37 ++++++++++++ .../ObjectModelDescriberTest.php | 7 +++ 5 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.json create mode 100644 tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.php diff --git a/tests/Functional/ModelDescriber/Fixtures/UuidClass.json b/tests/Functional/ModelDescriber/Fixtures/UuidClass.json index 02dc90fcb..a8aef559e 100644 --- a/tests/Functional/ModelDescriber/Fixtures/UuidClass.json +++ b/tests/Functional/ModelDescriber/Fixtures/UuidClass.json @@ -6,9 +6,7 @@ "uuidV3", "uuidV4", "uuidV5", - "uuidV6", - "uuidV7", - "uuidV8" + "uuidV6" ], "properties": { "uuid": { @@ -39,14 +37,6 @@ "type": "string", "format": "uuid" }, - "uuidV7": { - "type": "string", - "format": "uuid" - }, - "uuidV8": { - "type": "string", - "format": "uuid" - }, "nullableUuid": { "type": "string", "format": "uuid", diff --git a/tests/Functional/ModelDescriber/Fixtures/UuidClass.php b/tests/Functional/ModelDescriber/Fixtures/UuidClass.php index 231079307..7a833d5bf 100644 --- a/tests/Functional/ModelDescriber/Fixtures/UuidClass.php +++ b/tests/Functional/ModelDescriber/Fixtures/UuidClass.php @@ -18,8 +18,6 @@ use Symfony\Component\Uid\UuidV4; use Symfony\Component\Uid\UuidV5; use Symfony\Component\Uid\UuidV6; -use Symfony\Component\Uid\UuidV7; -use Symfony\Component\Uid\UuidV8; class UuidClass { @@ -30,8 +28,6 @@ class UuidClass public UuidV4 $uuidV4; public UuidV5 $uuidV5; public UuidV6 $uuidV6; - public UuidV7 $uuidV7; - public UuidV8 $uuidV8; public ?Uuid $nullableUuid; } diff --git a/tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.json b/tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.json new file mode 100644 index 000000000..02dc90fcb --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.json @@ -0,0 +1,57 @@ +{ + "required": [ + "uuid", + "ulid", + "uuidV1", + "uuidV3", + "uuidV4", + "uuidV5", + "uuidV6", + "uuidV7", + "uuidV8" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "ulid": { + "type": "string", + "format": "uuid" + }, + "uuidV1": { + "type": "string", + "format": "uuid" + }, + "uuidV3": { + "type": "string", + "format": "uuid" + }, + "uuidV4": { + "type": "string", + "format": "uuid" + }, + "uuidV5": { + "type": "string", + "format": "uuid" + }, + "uuidV6": { + "type": "string", + "format": "uuid" + }, + "uuidV7": { + "type": "string", + "format": "uuid" + }, + "uuidV8": { + "type": "string", + "format": "uuid" + }, + "nullableUuid": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "type": "object" +} \ No newline at end of file diff --git a/tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.php b/tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.php new file mode 100644 index 000000000..bc3e37813 --- /dev/null +++ b/tests/Functional/ModelDescriber/Fixtures/UuidClass7And8.php @@ -0,0 +1,37 @@ +=')) { + yield [ + Fixtures\UuidClass7And8::class + ]; + } + yield [ Fixtures\Refs::class ]; From 5a76a85fd97b9c9be4058f76bab99481074bf1e9 Mon Sep 17 00:00:00 2001 From: djordy Date: Sat, 18 Jan 2025 16:41:08 +0100 Subject: [PATCH 78/78] remove @experimental --- src/TypeDescriber/ArrayDescriber.php | 2 -- src/TypeDescriber/BoolDescriber.php | 2 -- src/TypeDescriber/ChainDescriber.php | 2 -- src/TypeDescriber/ClassDescriber.php | 2 -- src/TypeDescriber/DictionaryDescriber.php | 2 -- src/TypeDescriber/FloatDescriber.php | 2 -- src/TypeDescriber/IntegerDescriber.php | 2 -- src/TypeDescriber/IntersectionDescriber.php | 2 -- src/TypeDescriber/ListDescriber.php | 2 -- src/TypeDescriber/MixedDescriber.php | 2 -- src/TypeDescriber/NullableDescriber.php | 2 -- src/TypeDescriber/ObjectDescriber.php | 2 -- src/TypeDescriber/StringDescriber.php | 2 -- src/TypeDescriber/TypeDescriberAwareInterface.php | 3 --- src/TypeDescriber/TypeDescriberAwareTrait.php | 3 --- src/TypeDescriber/TypeDescriberInterface.php | 2 -- src/TypeDescriber/UnionDescriber.php | 2 -- 17 files changed, 36 deletions(-) diff --git a/src/TypeDescriber/ArrayDescriber.php b/src/TypeDescriber/ArrayDescriber.php index ce07fb84c..d1c3f70ef 100644 --- a/src/TypeDescriber/ArrayDescriber.php +++ b/src/TypeDescriber/ArrayDescriber.php @@ -18,8 +18,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class ArrayDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface diff --git a/src/TypeDescriber/BoolDescriber.php b/src/TypeDescriber/BoolDescriber.php index 70981fac6..7bea6593b 100644 --- a/src/TypeDescriber/BoolDescriber.php +++ b/src/TypeDescriber/BoolDescriber.php @@ -17,8 +17,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class BoolDescriber implements TypeDescriberInterface diff --git a/src/TypeDescriber/ChainDescriber.php b/src/TypeDescriber/ChainDescriber.php index 0a4d521e2..6386ce589 100644 --- a/src/TypeDescriber/ChainDescriber.php +++ b/src/TypeDescriber/ChainDescriber.php @@ -19,8 +19,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class ChainDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface diff --git a/src/TypeDescriber/ClassDescriber.php b/src/TypeDescriber/ClassDescriber.php index f228f91b0..8cf085135 100644 --- a/src/TypeDescriber/ClassDescriber.php +++ b/src/TypeDescriber/ClassDescriber.php @@ -25,8 +25,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class ClassDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface diff --git a/src/TypeDescriber/DictionaryDescriber.php b/src/TypeDescriber/DictionaryDescriber.php index a8820166f..aab49aa8c 100644 --- a/src/TypeDescriber/DictionaryDescriber.php +++ b/src/TypeDescriber/DictionaryDescriber.php @@ -21,8 +21,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class DictionaryDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface diff --git a/src/TypeDescriber/FloatDescriber.php b/src/TypeDescriber/FloatDescriber.php index b4fa6ecf6..8d044b8b2 100644 --- a/src/TypeDescriber/FloatDescriber.php +++ b/src/TypeDescriber/FloatDescriber.php @@ -18,8 +18,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class FloatDescriber implements TypeDescriberInterface diff --git a/src/TypeDescriber/IntegerDescriber.php b/src/TypeDescriber/IntegerDescriber.php index 81150ba7f..038f5f488 100644 --- a/src/TypeDescriber/IntegerDescriber.php +++ b/src/TypeDescriber/IntegerDescriber.php @@ -18,8 +18,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class IntegerDescriber implements TypeDescriberInterface diff --git a/src/TypeDescriber/IntersectionDescriber.php b/src/TypeDescriber/IntersectionDescriber.php index be36d7317..9e15db7a1 100644 --- a/src/TypeDescriber/IntersectionDescriber.php +++ b/src/TypeDescriber/IntersectionDescriber.php @@ -20,8 +20,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class IntersectionDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface diff --git a/src/TypeDescriber/ListDescriber.php b/src/TypeDescriber/ListDescriber.php index 25202073a..c9061b150 100644 --- a/src/TypeDescriber/ListDescriber.php +++ b/src/TypeDescriber/ListDescriber.php @@ -21,8 +21,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class ListDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface diff --git a/src/TypeDescriber/MixedDescriber.php b/src/TypeDescriber/MixedDescriber.php index 1fa4ce6d8..59a19472c 100644 --- a/src/TypeDescriber/MixedDescriber.php +++ b/src/TypeDescriber/MixedDescriber.php @@ -19,8 +19,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class MixedDescriber implements TypeDescriberInterface diff --git a/src/TypeDescriber/NullableDescriber.php b/src/TypeDescriber/NullableDescriber.php index e4446b4be..c3de6ed0c 100644 --- a/src/TypeDescriber/NullableDescriber.php +++ b/src/TypeDescriber/NullableDescriber.php @@ -17,8 +17,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class NullableDescriber implements TypeDescriberInterface diff --git a/src/TypeDescriber/ObjectDescriber.php b/src/TypeDescriber/ObjectDescriber.php index e53a02af6..2c71ff14c 100644 --- a/src/TypeDescriber/ObjectDescriber.php +++ b/src/TypeDescriber/ObjectDescriber.php @@ -21,8 +21,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class ObjectDescriber implements TypeDescriberInterface, ModelRegistryAwareInterface diff --git a/src/TypeDescriber/StringDescriber.php b/src/TypeDescriber/StringDescriber.php index 456d566a9..80a355f1f 100644 --- a/src/TypeDescriber/StringDescriber.php +++ b/src/TypeDescriber/StringDescriber.php @@ -18,8 +18,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class StringDescriber implements TypeDescriberInterface diff --git a/src/TypeDescriber/TypeDescriberAwareInterface.php b/src/TypeDescriber/TypeDescriberAwareInterface.php index 318cc29b3..d0c394b84 100644 --- a/src/TypeDescriber/TypeDescriberAwareInterface.php +++ b/src/TypeDescriber/TypeDescriberAwareInterface.php @@ -11,9 +11,6 @@ namespace Nelmio\ApiDocBundle\TypeDescriber; -/** - * @experimental - */ interface TypeDescriberAwareInterface { public function setDescriber(TypeDescriberInterface $describer): void; diff --git a/src/TypeDescriber/TypeDescriberAwareTrait.php b/src/TypeDescriber/TypeDescriberAwareTrait.php index 760efcda3..8bbd1bb25 100644 --- a/src/TypeDescriber/TypeDescriberAwareTrait.php +++ b/src/TypeDescriber/TypeDescriberAwareTrait.php @@ -11,9 +11,6 @@ namespace Nelmio\ApiDocBundle\TypeDescriber; -/** - * @experimental - */ trait TypeDescriberAwareTrait { protected TypeDescriberInterface $describer; diff --git a/src/TypeDescriber/TypeDescriberInterface.php b/src/TypeDescriber/TypeDescriberInterface.php index f2c824872..80df2b7cc 100644 --- a/src/TypeDescriber/TypeDescriberInterface.php +++ b/src/TypeDescriber/TypeDescriberInterface.php @@ -16,8 +16,6 @@ /** * @template T of Type - * - * @experimental */ interface TypeDescriberInterface { diff --git a/src/TypeDescriber/UnionDescriber.php b/src/TypeDescriber/UnionDescriber.php index a754fbb53..12abc0a81 100644 --- a/src/TypeDescriber/UnionDescriber.php +++ b/src/TypeDescriber/UnionDescriber.php @@ -21,8 +21,6 @@ /** * @implements TypeDescriberInterface * - * @experimental - * * @internal */ final class UnionDescriber implements TypeDescriberInterface, TypeDescriberAwareInterface