diff --git a/.doctor-rst.yaml b/.doctor-rst.yaml index b22ff8c10..ae39fbe61 100644 --- a/.doctor-rst.yaml +++ b/.doctor-rst.yaml @@ -53,10 +53,10 @@ rules: # master versionadded_directive_major_version: - major_version: 5 + major_version: 6 versionadded_directive_min_version: - min_version: '5.0' + min_version: '6.0' deprecated_directive_major_version: major_version: 5 @@ -71,4 +71,4 @@ whitelist: lines: - '.. code-block:: twig' - '// bin/console' - - '.. code-block:: php' \ No newline at end of file + - '.. code-block:: php' diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index bf93ef8c2..5dc202738 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -21,6 +21,13 @@ use Nelmio\ApiDocBundle\ModelDescriber\BazingaHateoasModelDescriber; use Nelmio\ApiDocBundle\ModelDescriber\JMSModelDescriber; use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface; +use Nelmio\ApiDocBundle\Processors\MapQueryStringProcessor; +use Nelmio\ApiDocBundle\Processors\MapRequestPayloadProcessor; +use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber; +use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\RouteArgumentDescriberInterface; +use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapQueryParameterDescriber; +use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapQueryStringDescriber; +use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapRequestPayloadDescriber; use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use OpenApi\Generator; use Symfony\Component\Config\FileLocator; @@ -32,6 +39,9 @@ use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Routing\RouteCollection; @@ -170,6 +180,47 @@ public function load(array $configs, ContainerBuilder $container): void ->setArgument(1, $config['media_types']); } + if (PHP_VERSION_ID > 80100) { + // Add autoconfiguration for route argument describer + $container->registerForAutoconfiguration(RouteArgumentDescriberInterface::class) + ->addTag('nelmio_api_doc.route_argument_describer'); + + $container->register('nelmio_api_doc.route_describers.route_argument', RouteArgumentDescriber::class) + ->setPublic(false) + ->addTag('nelmio_api_doc.route_describer', ['priority' => -225]) + ->setArguments([ + new Reference('argument_metadata_factory'), + new TaggedIteratorArgument('nelmio_api_doc.route_argument_describer'), + ]) + ; + + if (class_exists(MapQueryString::class)) { + $container->register('nelmio_api_doc.route_argument_describer.map_query_string', SymfonyMapQueryStringDescriber::class) + ->setPublic(false) + ->addTag('nelmio_api_doc.route_argument_describer', ['priority' => 0]); + + $container->register('nelmio_api_doc.swagger.processor.map_query_string', MapQueryStringProcessor::class) + ->setPublic(false) + ->addTag('nelmio_api_doc.swagger.processor', ['priority' => 0]); + } + + if (class_exists(MapRequestPayload::class)) { + $container->register('nelmio_api_doc.route_argument_describer.map_request_payload', SymfonyMapRequestPayloadDescriber::class) + ->setPublic(false) + ->addTag('nelmio_api_doc.route_argument_describer', ['priority' => 0]); + + $container->register('nelmio_api_doc.swagger.processor.map_request_payload', MapRequestPayloadProcessor::class) + ->setPublic(false) + ->addTag('nelmio_api_doc.swagger.processor', ['priority' => 0]); + } + + if (class_exists(MapQueryParameter::class)) { + $container->register('nelmio_api_doc.route_argument_describer.map_query_parameter', SymfonyMapQueryParameterDescriber::class) + ->setPublic(false) + ->addTag('nelmio_api_doc.route_argument_describer', ['priority' => 0]); + } + } + $bundles = $container->getParameter('kernel.bundles'); if (!isset($bundles['TwigBundle']) || !class_exists('Symfony\Component\Asset\Packages')) { $container->removeDefinition('nelmio_api_doc.controller.swagger_ui'); diff --git a/OpenApiPhp/Util.php b/OpenApiPhp/Util.php index 8a3537f56..b03727a47 100644 --- a/OpenApiPhp/Util.php +++ b/OpenApiPhp/Util.php @@ -504,4 +504,16 @@ function ($value) { $class::$_nested )); } + + /** + * Helper method to modify an annotation value only if its value has not yet been set. + */ + public static function modifyAnnotationValue(OA\AbstractAnnotation $parameter, string $property, $value): void + { + if (!Generator::isDefault($parameter->{$property})) { + return; + } + + $parameter->{$property} = $value; + } } diff --git a/Processors/MapQueryStringProcessor.php b/Processors/MapQueryStringProcessor.php new file mode 100644 index 000000000..5a419df75 --- /dev/null +++ b/Processors/MapQueryStringProcessor.php @@ -0,0 +1,80 @@ +getAnnotationsOfType(OA\Operation::class); + + foreach ($operations as $operation) { + if (!isset($operation->_context->{SymfonyMapQueryStringDescriber::CONTEXT_ARGUMENT_METADATA})) { + continue; + } + + $argumentMetaData = $operation->_context->{SymfonyMapQueryStringDescriber::CONTEXT_ARGUMENT_METADATA}; + if (!$argumentMetaData instanceof ArgumentMetadata) { + throw new \LogicException(sprintf('MapQueryString ArgumentMetaData not found for operation "%s"', $operation->operationId)); + } + + $modelRef = $operation->_context->{SymfonyMapQueryStringDescriber::CONTEXT_MODEL_REF}; + if (!isset($modelRef)) { + throw new \LogicException(sprintf('MapQueryString Model reference not found for operation "%s"', $operation->operationId)); + } + + $nativeModelName = str_replace(OA\Components::SCHEMA_REF, '', $modelRef); + + $schemaModel = Util::getSchema($analysis->openapi, $nativeModelName); + + // There are no properties to map to query parameters + if (Generator::UNDEFINED === $schemaModel->properties) { + return; + } + + $isModelOptional = $argumentMetaData->hasDefaultValue() || $argumentMetaData->isNullable(); + + foreach ($schemaModel->properties as $property) { + $operationParameter = Util::getOperationParameter($operation, $property->property, 'query'); + + // Remove incompatible properties + $propertyVars = get_object_vars($property); + unset($propertyVars['property']); + + $schema = new OA\Schema($propertyVars); + + Util::modifyAnnotationValue($operationParameter, 'schema', $schema); + Util::modifyAnnotationValue($operationParameter, 'name', $property->property); + Util::modifyAnnotationValue($operationParameter, 'description', $schema->description); + Util::modifyAnnotationValue($operationParameter, 'required', $schema->required); + Util::modifyAnnotationValue($operationParameter, 'deprecated', $schema->deprecated); + Util::modifyAnnotationValue($operationParameter, 'example', $schema->example); + + if ($isModelOptional) { + Util::modifyAnnotationValue($operationParameter, 'required', false); + } elseif (is_array($schemaModel->required) && in_array($property->property, $schemaModel->required, true)) { + Util::modifyAnnotationValue($operationParameter, 'required', true); + } else { + Util::modifyAnnotationValue($operationParameter, 'required', false); + } + } + } + } +} diff --git a/Processors/MapRequestPayloadProcessor.php b/Processors/MapRequestPayloadProcessor.php new file mode 100644 index 000000000..b524f9836 --- /dev/null +++ b/Processors/MapRequestPayloadProcessor.php @@ -0,0 +1,99 @@ +getAnnotationsOfType(OA\Operation::class); + + foreach ($operations as $operation) { + if (!isset($operation->_context->{SymfonyMapRequestPayloadDescriber::CONTEXT_ARGUMENT_METADATA})) { + continue; + } + + $argumentMetaData = $operation->_context->{SymfonyMapRequestPayloadDescriber::CONTEXT_ARGUMENT_METADATA}; + if (!$argumentMetaData instanceof ArgumentMetadata) { + throw new \LogicException(sprintf('MapRequestPayload ArgumentMetaData not found for operation "%s"', $operation->operationId)); + } + + /** @var MapRequestPayload $attribute */ + if (!$attribute = $argumentMetaData->getAttributes(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null) { + throw new \LogicException(sprintf('Operation "%s" does not contain attribute of "%s', $operation->operationId, MapRequestPayload::class)); + } + + $modelRef = $operation->_context->{SymfonyMapRequestPayloadDescriber::CONTEXT_MODEL_REF}; + if (!isset($modelRef)) { + throw new \LogicException(sprintf('MapRequestPayload Model reference not found for operation "%s"', $operation->operationId)); + } + + /** @var OA\RequestBody $requestBody */ + $requestBody = Util::getChild($operation, OA\RequestBody::class); + Util::modifyAnnotationValue($requestBody, 'required', !($argumentMetaData->hasDefaultValue() || $argumentMetaData->isNullable())); + + $formats = $attribute->acceptFormat; + if (!is_array($formats)) { + $formats = [$attribute->acceptFormat ?? 'json']; + } + + foreach ($formats as $format) { + $contentSchema = $this->getContentSchemaForType($requestBody, $format); + Util::modifyAnnotationValue($contentSchema, 'ref', $modelRef); + + if ($argumentMetaData->isNullable()) { + $contentSchema->nullable = true; + } + } + } + } + + private function getContentSchemaForType(OA\RequestBody $requestBody, string $type): OA\Schema + { + Util::modifyAnnotationValue($requestBody, 'content', []); + switch ($type) { + case 'json': + $contentType = 'application/json'; + + break; + case 'xml': + $contentType = 'application/xml'; + + break; + default: + throw new \InvalidArgumentException('Unsupported media type'); + } + + if (!isset($requestBody->content[$contentType])) { + $weakContext = Util::createWeakContext($requestBody->_context); + $requestBody->content[$contentType] = new OA\MediaType( + [ + 'mediaType' => $contentType, + '_context' => $weakContext, + ] + ); + } + + return Util::getChild( + $requestBody->content[$contentType], + OA\Schema::class + ); + } +} diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index 3b21567c3..bd9800115 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -7,7 +7,7 @@ OpenAPI (Swagger) format and provides a sandbox to interactively experiment with What's supported? ----------------- -This bundle supports *Symfony* route requirements, PHP annotations, `Swagger-Php`_ annotations, +This bundle supports *Symfony* route requirements, *Symfony* request mapping (:doc:`symfony_attributes`), PHP annotations, `Swagger-Php`_ annotations, `FOSRestBundle`_ annotations and applications using `Api-Platform`_. .. _`Swagger-Php`: https://github.com/zircote/swagger-php @@ -239,6 +239,12 @@ The normal PHPDoc block on the controller method is used for the summary and des However, unlike in those examples, when using this bundle you don't need to specify paths and you can easily document models as well as some other properties described below as they can be automatically be documented using the Symfony integration. +.. tip:: + + **NelmioApiDocBundle** understands **symfony's** controller attributes. + Using these attributes inside your controller allows this bundle to automatically create the necessary documentation. + More information can be found here: :doc:`symfony_attributes`. + Use Models ---------- @@ -576,6 +582,7 @@ If you need more complex features, take a look at: commands faq security + symfony_attributes .. _`SwaggerPHP examples`: https://github.com/zircote/swagger-php/tree/master/Examples .. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html diff --git a/Resources/doc/symfony_attributes.rst b/Resources/doc/symfony_attributes.rst new file mode 100644 index 000000000..0f0c77594 --- /dev/null +++ b/Resources/doc/symfony_attributes.rst @@ -0,0 +1,200 @@ +Symfony attributes +================================ + +NelmioApiDocBundle has the ability to automatically create documentation from **symfony** controller attributes. + +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 +~~~~~~~ + +Modifying the generated documentation can easily by done in two ways, by: +* Customizing the documentation of an object's property (``#[OA\Property]`` attribute) +* Customizing the documentation of a query parameter (``#[OA\Parameter]`` attribute) + +Customizing the documentation of a specific query parameter can be done by adding the ``#[OA\Parameter]`` attribute to your controller method. +Make sure that the ``in`` property is set to ``'query'`` and that the ``name`` property is set to the object's property name which you want to customize. + + .. code-block:: php-attributes + + #[OA\Parameter( + name: 'id', + description: 'Some additional parameter description', + in: 'query', + )] + +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 +~~~~~~~ + +Customizing the documentation of the query parameter can be done by adding the ``#[OA\Parameter]`` attribute to your controller method. +Make sure that the ``in`` property is set to ``'query'`` and that the ``name`` property is set to the name of the controller method parameter. + + .. code-block:: php-attributes + + #[OA\Parameter( + name: 'id', + description: 'Some additional parameter description', + in: 'query', + )] + +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 +~~~~~~~ + +Customizing the documentation of the request body can be done by adding the ``#[OA\RequestBody]`` attribute to your controller method. + + .. code-block:: php-attributes + + #[OA\RequestBody( + groups: ["create"], + ) + +Complete example +---------------------- + + .. code-block:: php-attributes + + class UserQuery + { + public int $userId; + } + + .. code-block:: php-attributes + + use Symfony\Component\Serializer\Annotation\Groups; + use Symfony\Component\Validator\Constraints as Assert; + + class UserDto + { + #[Groups(["default", "create", "update"])] + #[Assert\NotBlank(groups: ["default", "create"])] + public string $username; + } + + .. code-block:: php-attributes + + namespace AppBundle\Controller; + + use AppBundle\UserDTO; + use AppBundle\UserQuery; + use OpenApi\Attributes as OA; + use Symfony\Component\Routing\Annotation\Route; + + class UserController + { + /** + * Find user with MapQueryString. + */ + #[Route('/api/users', methods: ['GET'])] + #[OA\Parameter( + name: 'userId', + description: 'Id of the user to find', + in: 'query', + )] + public function findUser(#[MapQueryString] UserQuery $userQuery) + { + // ... + } + + /** + * Find user with MapQueryParameter. + */ + #[Route('/api/users/v2', methods: ['GET'])] + #[OA\Parameter( + name: 'userId', + description: 'Id of the user to find', + in: 'query', + )] + public function findUserV2(#[MapQueryParameter] int $userId) + { + // ... + } + + /** + * Create a new user. + */ + #[Route('/api/users', methods: ['POST'])] + #[OA\RequestBody( + groups: ['create'], + )] + public function createUser(#[MapRequestPayload] UserDTO $user) + { + // ... + } + } + +Customization +---------------------- + +Imagine you want to add, modify, or remove some documentation for a route argument. For that you will have to create your own describer which implements the :class:`RouteArgumentDescriberInterface`_ interface. + +Register your route argument describer +~~~~~~~ + +Before you can use your custom describer you must register it in your route argument describer as a service and tag it with ``nelmio_api_doc.route_argument_describer``. +Services implementing the :class:`RouteArgumentDescriberInterface`_ interface are automatically detected and used by NelmioApiDocBundle. + +.. configuration-block:: + + .. code-block:: yaml + + # config/services.yaml + services: + App\Describer\CustomRouteArgumentDescriber: + tags: + - { name: nelmio_api_doc.route_argument_describer } + + .. code-block:: xml + + + + + + + .. code-block:: php + + // config/services.php + use App\Describer\CustomRouteArgumentDescriber; + + return function (ContainerConfigurator $container) { + $container->services() + ->set(CustomRouteArgumentDescriber::class) + ->tag('nelmio_api_doc.route_argument_describer') + ; + }; + +Disclaimer +---------------------- + +Make sure to use at least php 8.1 (attribute support) to make use of this functionality. + +.. _`Symfony MapQueryString`: https://symfony.com/doc/current/controller.html#mapping-the-whole-query-string +.. _`Symfony MapQueryParameter`: https://symfony.com/doc/current/controller.html#mapping-query-parameters-individually +.. _`Symfony MapRequestPayload`: https://symfony.com/doc/current/controller.html#mapping-request-payload +.. _`RouteArgumentDescriberInterface`: https://github.com/DjordyKoert/NelmioApiDocBundle/blob/master/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryParameterDescriber.php diff --git a/RouteDescriber/RouteArgumentDescriber.php b/RouteDescriber/RouteArgumentDescriber.php new file mode 100644 index 000000000..b95534ab7 --- /dev/null +++ b/RouteDescriber/RouteArgumentDescriber.php @@ -0,0 +1,55 @@ +getDefault('_controller'); + + try { + $argumentMetaDataList = $this->argumentMetadataFactory->createArgumentMetadata($controller, $reflectionMethod); + } catch (\ReflectionException) { + return; + } + + if (!$argumentMetaDataList) { + return; + } + + foreach ($this->getOperations($api, $route) as $operation) { + foreach ($argumentMetaDataList as $argumentMetadata) { + foreach ($this->inlineParameterDescribers as $inlineParameterDescriber) { + if ($inlineParameterDescriber instanceof ModelRegistryAwareInterface) { + $inlineParameterDescriber->setModelRegistry($this->modelRegistry); + } + + $inlineParameterDescriber->describe($argumentMetadata, $operation); + } + } + } + } +} diff --git a/RouteDescriber/RouteArgumentDescriber/RouteArgumentDescriberInterface.php b/RouteDescriber/RouteArgumentDescriber/RouteArgumentDescriberInterface.php new file mode 100644 index 000000000..7932addc6 --- /dev/null +++ b/RouteDescriber/RouteArgumentDescriber/RouteArgumentDescriberInterface.php @@ -0,0 +1,13 @@ +getAttributes(MapQueryParameter::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null) { + return; + } + + $operationParameter = Util::getOperationParameter($operation, $attribute->name ?? $argumentMetadata->getName(), 'query'); + + Util::modifyAnnotationValue($operationParameter, 'required', !($argumentMetadata->hasDefaultValue() || $argumentMetadata->isNullable())); + + /** @var OA\Schema $schema */ + $schema = Util::getChild($operationParameter, OA\Schema::class); + + if (FILTER_VALIDATE_REGEXP === $attribute->filter) { + Util::modifyAnnotationValue($schema, 'pattern', $attribute->options['regexp']); + } + + if ($argumentMetadata->hasDefaultValue()) { + Util::modifyAnnotationValue($schema, 'default', $argumentMetadata->getDefaultValue()); + } + + if (Generator::UNDEFINED === $schema->type) { + $this->mapNativeType($schema, $argumentMetadata->getType()); + } + + if (Generator::UNDEFINED === $schema->nullable && $argumentMetadata->isNullable()) { + Util::modifyAnnotationValue($schema, 'nullable', true); + } + } +} diff --git a/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryStringDescriber.php b/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryStringDescriber.php new file mode 100644 index 000000000..95e998a88 --- /dev/null +++ b/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryStringDescriber.php @@ -0,0 +1,37 @@ +getAttributes(MapQueryString::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null) { + return; + } + + $modelRef = $this->modelRegistry->register(new Model( + new Type(Type::BUILTIN_TYPE_OBJECT, $argumentMetadata->isNullable(), $argumentMetadata->getType()), + serializationContext: $attribute->serializationContext, + )); + + $operation->_context->{self::CONTEXT_ARGUMENT_METADATA} = $argumentMetadata; + $operation->_context->{self::CONTEXT_MODEL_REF} = $modelRef; + } +} diff --git a/RouteDescriber/RouteArgumentDescriber/SymfonyMapRequestPayloadDescriber.php b/RouteDescriber/RouteArgumentDescriber/SymfonyMapRequestPayloadDescriber.php new file mode 100644 index 000000000..5deb86744 --- /dev/null +++ b/RouteDescriber/RouteArgumentDescriber/SymfonyMapRequestPayloadDescriber.php @@ -0,0 +1,37 @@ +getAttributes(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null) { + return; + } + + $modelRef = $this->modelRegistry->register(new Model( + new Type(Type::BUILTIN_TYPE_OBJECT, false, $argumentMetadata->getType()), + serializationContext: $attribute->serializationContext, + )); + + $operation->_context->{self::CONTEXT_ARGUMENT_METADATA} = $argumentMetadata; + $operation->_context->{self::CONTEXT_MODEL_REF} = $modelRef; + } +} diff --git a/Tests/Functional/Controller/ApiController81.php b/Tests/Functional/Controller/ApiController81.php index f9d52acfe..3797cb343 100644 --- a/Tests/Functional/Controller/ApiController81.php +++ b/Tests/Functional/Controller/ApiController81.php @@ -30,6 +30,7 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups; use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator81; use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminatorFileMapping; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyMapQueryString; use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; use Nelmio\ApiDocBundle\Tests\Functional\EntityExcluded\Symfony7\SerializedNameEntity; use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType; @@ -38,6 +39,9 @@ use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithRefType; use Nelmio\ApiDocBundle\Tests\Functional\Form\UserType; use OpenApi\Attributes as OA; +use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; +use Symfony\Component\HttpKernel\Attribute\MapQueryString; +use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; use Symfony\Component\Routing\Annotation\Route; class ApiController81 @@ -459,4 +463,131 @@ public function nameConverterContext() public function arbitraryArray() { } + + #[Route('/article_map_query_string')] + #[OA\Response(response: '200', description: '')] + public function fetchArticleFromMapQueryString( + #[MapQueryString] SymfonyMapQueryString $article81Query + ) { + } + + #[Route('/article_map_query_string_nullable')] + #[OA\Response(response: '200', description: '')] + public function fetchArticleFromMapQueryStringNullable( + #[MapQueryString] ?SymfonyMapQueryString $article81Query + ) { + } + + #[Route('/article_map_query_string_overwrite_parameters')] + #[OA\Parameter( + name: 'id', + in: 'query', + schema: new OA\Schema(type: 'string', nullable: true), + description: 'Query parameter id description' + )] + #[OA\Parameter( + name: 'name', + in: 'query', + description: 'Query parameter name description' + )] + #[OA\Parameter( + name: 'nullableName', + in: 'query', + description: 'Query parameter nullableName description' + )] + #[OA\Parameter( + name: 'articleType81', + in: 'query', + description: 'Query parameter articleType81 description' + )] + #[OA\Parameter( + name: 'nullableArticleType81', + in: 'query', + description: 'Query parameter nullableArticleType81 description' + )] + #[OA\Response(response: '200', description: '')] + public function fetchArticleFromMapQueryStringOverwriteParameters( + #[MapQueryString] SymfonyMapQueryString $article81Query + ) { + } + + #[Route('/article_map_query_parameter')] + #[OA\Response(response: '200', description: '')] + public function fetchArticleFromMapQueryParameter( + #[MapQueryParameter] int $id, + ) { + } + + #[Route('/article_map_query_parameter_nullable')] + #[OA\Response(response: '200', description: '')] + public function fetchArticleFromMapQueryParameterNullable( + #[MapQueryParameter] ?int $id, + ) { + } + + #[Route('/article_map_query_parameter_default')] + #[OA\Response(response: '200', description: '')] + public function fetchArticleFromMapQueryParameterDefault( + #[MapQueryParameter] int $id = 123, + ) { + } + + #[Route('/article_map_query_parameter_overwrite_parameters')] + #[OA\Parameter( + name: 'id', + in: 'query', + description: 'Query parameter id description', + example: 123, + )] + #[OA\Parameter( + name: 'changedType', + in: 'query', + schema: new OA\Schema(type: 'int', nullable: false), + description: 'Incorrectly described query parameter', + example: 123, + )] + #[OA\Response(response: '200', description: '')] + public function fetchArticleFromMapQueryParameterOverwriteParameters( + #[MapQueryParameter] ?int $id, + #[MapQueryParameter] ?string $changedType, + ) { + } + + #[Route('/article_map_request_payload', methods: ['POST'])] + #[OA\Response(response: '200', description: '')] + public function createArticleFromMapRequestPayload( + #[MapRequestPayload] Article81 $article81, + ) { + } + + #[Route('/article_map_request_payload_nullable', methods: ['POST'])] + #[OA\Response(response: '200', description: '')] + public function createArticleFromMapRequestPayloadNullable( + #[MapRequestPayload] ?Article81 $article81, + ) { + } + + #[Route('/article_map_request_payload_overwrite', methods: ['POST'])] + #[OA\RequestBody( + description: 'Request body description', + content: new Model(type: EntityWithNullableSchemaSet::class), + )] + #[OA\Response(response: '200', description: '')] + public function createArticleFromMapRequestPayloadOverwrite( + #[MapRequestPayload] Article81 $article81, + ) { + } + + #[Route('/article_map_request_payload_handles_already_set_content', methods: ['POST'])] + #[OA\RequestBody( + description: 'Request body description', + content: new OA\JsonContent( + ref: new Model(type: Article81::class) + ), + )] + #[OA\Response(response: '200', description: '')] + public function createArticleFromMapRequestPayloadHandlesAlreadySetContent( + #[MapRequestPayload] Article81 $article81, + ) { + } } diff --git a/Tests/Functional/Entity/SymfonyMapQueryString.php b/Tests/Functional/Entity/SymfonyMapQueryString.php new file mode 100644 index 000000000..4f9713025 --- /dev/null +++ b/Tests/Functional/Entity/SymfonyMapQueryString.php @@ -0,0 +1,17 @@ + 'api.example.com']); + } + + public function testMapQueryStringModelGetsCreated(): void + { + if (!class_exists(MapQueryString::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryString attribute not found'); + } + + $expected = [ + 'schema' => 'SymfonyMapQueryString', + 'required' => [ + 'id', + 'name', + 'articleType81', + ], + 'properties' => [ + 'id' => [ + 'type' => 'integer', + ], + 'name' => [ + 'type' => 'string', + ], + 'nullableName' => [ + 'type' => 'string', + 'nullable' => true, + ], + 'articleType81' => [ + '$ref' => '#/components/schemas/ArticleType81', + ], + 'nullableArticleType81' => [ + 'nullable' => true, + 'allOf' => [ + ['$ref' => '#/components/schemas/ArticleType81'], + ], + ], + ], + 'type' => 'object', + ]; + + $this->assertSame($expected, json_decode($this->getModel('SymfonyMapQueryString')->toJson(), true)); + } + + public function testMapQueryString(): void + { + if (!class_exists(MapQueryString::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryString attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'get_api_nelmio_apidoc_tests_functional_api_fetcharticlefrommapquerystring', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'query', + 'required' => true, + 'schema' => [ + 'type' => 'integer', + ], + ], + [ + 'name' => 'name', + 'in' => 'query', + 'required' => true, + 'schema' => [ + 'type' => 'string', + ], + ], + [ + 'name' => 'nullableName', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'nullable' => true, + ], + ], + [ + 'name' => 'articleType81', + 'in' => 'query', + 'required' => true, + 'schema' => [ + '$ref' => '#/components/schemas/ArticleType81', + ], + ], + [ + 'name' => 'nullableArticleType81', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'nullable' => true, + 'allOf' => [ + ['$ref' => '#/components/schemas/ArticleType81'], + ], + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + ], json_decode($this->getOperation('/api/article_map_query_string', 'get')->toJson(), true)); + } + + public function testMapQueryStringParametersAreOptional(): void + { + if (!class_exists(MapQueryString::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryString attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'get_api_nelmio_apidoc_tests_functional_api_fetcharticlefrommapquerystringnullable', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'integer', + ], + ], + [ + 'name' => 'name', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'string', + ], + ], + [ + 'name' => 'nullableName', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'nullable' => true, + ], + ], + [ + 'name' => 'articleType81', + 'in' => 'query', + 'required' => false, + 'schema' => [ + '$ref' => '#/components/schemas/ArticleType81', + ], + ], + [ + 'name' => 'nullableArticleType81', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'nullable' => true, + 'allOf' => [ + ['$ref' => '#/components/schemas/ArticleType81'], + ], + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + ], json_decode($this->getOperation('/api/article_map_query_string_nullable', 'get')->toJson(), true)); + } + + public function testMapQueryStringParametersOverwriteParameters(): void + { + if (!class_exists(MapQueryString::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryString attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'get_api_nelmio_apidoc_tests_functional_api_fetcharticlefrommapquerystringoverwriteparameters', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'query', + 'required' => true, + 'schema' => [ + 'type' => 'string', + 'nullable' => true, + ], + 'description' => 'Query parameter id description', + ], + [ + 'name' => 'name', + 'in' => 'query', + 'required' => true, + 'schema' => [ + 'type' => 'string', + ], + 'description' => 'Query parameter name description', + ], + [ + 'name' => 'nullableName', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'nullable' => true, + ], + 'description' => 'Query parameter nullableName description', + ], + [ + 'name' => 'articleType81', + 'in' => 'query', + 'required' => true, + 'schema' => [ + '$ref' => '#/components/schemas/ArticleType81', + ], + 'description' => 'Query parameter articleType81 description', + ], + [ + 'name' => 'nullableArticleType81', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'nullable' => true, + 'allOf' => [ + ['$ref' => '#/components/schemas/ArticleType81'], + ], + ], + 'description' => 'Query parameter nullableArticleType81 description', + ], + ], + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + ], json_decode($this->getOperation('/api/article_map_query_string_overwrite_parameters', 'get')->toJson(), true)); + } + + public function testMapQueryParameter(): void + { + if (!class_exists(MapQueryParameter::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryParameter attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'get_api_nelmio_apidoc_tests_functional_api_fetcharticlefrommapqueryparameter', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'query', + 'required' => true, + 'schema' => [ + 'type' => 'integer', + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + ], json_decode($this->getOperation('/api/article_map_query_parameter', 'get')->toJson(), true)); + } + + public function testMapQueryParameterHandlesNullable(): void + { + if (!class_exists(MapQueryParameter::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryParameter attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'get_api_nelmio_apidoc_tests_functional_api_fetcharticlefrommapqueryparameternullable', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'integer', + 'nullable' => true, + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + ], json_decode($this->getOperation('/api/article_map_query_parameter_nullable', 'get')->toJson(), true)); + } + + public function testMapQueryParameterHandlesDefault(): void + { + if (!class_exists(MapQueryParameter::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryParameter attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'get_api_nelmio_apidoc_tests_functional_api_fetcharticlefrommapqueryparameterdefault', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'integer', + 'default' => 123, + ], + ], + ], + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + ], json_decode($this->getOperation('/api/article_map_query_parameter_default', 'get')->toJson(), true)); + } + + public function testMapQueryParameterOverwriteParameter(): void + { + if (!class_exists(MapQueryParameter::class)) { + self::markTestSkipped('Symfony 6.3 MapQueryParameter attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'get_api_nelmio_apidoc_tests_functional_api_fetcharticlefrommapqueryparameteroverwriteparameters', + 'parameters' => [ + [ + 'name' => 'id', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'integer', + 'nullable' => true, + ], + 'description' => 'Query parameter id description', + 'example' => 123, + ], + [ + 'name' => 'changedType', + 'in' => 'query', + 'required' => false, + 'schema' => [ + 'type' => 'int', + 'nullable' => false, + ], + 'description' => 'Incorrectly described query parameter', + 'example' => 123, + ], + ], + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + ], json_decode($this->getOperation('/api/article_map_query_parameter_overwrite_parameters', 'get')->toJson(), true)); + } + + public function testMapRequestPayload(): void + { + if (!class_exists(MapRequestPayload::class)) { + self::markTestSkipped('Symfony 6.3 MapRequestPayload attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'post_api_nelmio_apidoc_tests_functional_api_createarticlefrommaprequestpayload', + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + 'requestBody' => [ + 'content' => [ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/Article81', + ], + ], + ], + 'required' => true, + ], + ], json_decode($this->getOperation('/api/article_map_request_payload', 'post')->toJson(), true)); + } + + public function testMapRequestPayloadNullable(): void + { + if (!class_exists(MapRequestPayload::class)) { + self::markTestSkipped('Symfony 6.3 MapRequestPayload attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'post_api_nelmio_apidoc_tests_functional_api_createarticlefrommaprequestpayloadnullable', + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + 'requestBody' => [ + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'nullable' => true, + 'oneOf' => [ + ['$ref' => '#/components/schemas/Article81'], + ], + ], + ], + ], + 'required' => false, + ], + ], json_decode($this->getOperation('/api/article_map_request_payload_nullable', 'post')->toJson(), true)); + } + + public function testMapRequestPayloadOverwriteRequestBody(): void + { + if (!class_exists(MapRequestPayload::class)) { + self::markTestSkipped('Symfony 6.3 MapRequestPayload attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'post_api_nelmio_apidoc_tests_functional_api_createarticlefrommaprequestpayloadoverwrite', + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + 'requestBody' => [ + 'content' => [ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/EntityWithNullableSchemaSet', + ], + ], + ], + 'required' => true, + 'description' => 'Request body description', + ], + ], json_decode($this->getOperation('/api/article_map_request_payload_overwrite', 'post')->toJson(), true)); + } + + public function testMapRequestPayloadHandlesAlreadySetContent(): void + { + if (!class_exists(MapRequestPayload::class)) { + self::markTestSkipped('Symfony 6.3 MapRequestPayload attribute not found'); + } + + self::assertEquals([ + 'operationId' => 'post_api_nelmio_apidoc_tests_functional_api_createarticlefrommaprequestpayloadhandlesalreadysetcontent', + 'responses' => [ + '200' => [ + 'description' => '', + ], + ], + 'requestBody' => [ + 'content' => [ + 'application/json' => [ + 'schema' => [ + '$ref' => '#/components/schemas/Article81', + ], + ], + ], + 'required' => true, + 'description' => 'Request body description', + ], + ], json_decode($this->getOperation('/api/article_map_request_payload_handles_already_set_content', 'post')->toJson(), true)); + } +} diff --git a/phpunit-baseline.json b/phpunit-baseline.json index e5a54aaaf..7f1f2acfe 100644 --- a/phpunit-baseline.json +++ b/phpunit-baseline.json @@ -6963,5 +6963,185 @@ "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\FunctionalTest::testEntityWithFalsyDefaults", "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringModelGetsCreated", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringModelGetsCreated", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringModelGetsCreated", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryString", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryString", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryString", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringParametersAreOptional", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringParametersAreOptional", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringParametersAreOptional", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringParametersOverwriteParameters", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringParametersOverwriteParameters", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryStringParametersOverwriteParameters", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameter", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameter", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameter", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterHandlesNullable", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterHandlesNullable", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterHandlesNullable", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterHandlesDefault", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterHandlesDefault", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterHandlesDefault", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterOverwriteParameter", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterOverwriteParameter", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapQueryParameterOverwriteParameter", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayload", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayload", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayload", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadNullable", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadNullable", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadNullable", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadOverwriteRequestBody", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadOverwriteRequestBody", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadOverwriteRequestBody", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadHandlesAlreadySetContent", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_class\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadHandlesAlreadySetContent", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_dir\" service is deprecated since version 5.2", + "count": 1 + }, + { + "location": "Nelmio\\ApiDocBundle\\Tests\\Functional\\SymfonyFunctionalTest::testMapRequestPayloadHandlesAlreadySetContent", + "message": "Since sensio/framework-extra-bundle 5.2: The \"sensio_framework_extra.routing.loader.annot_file\" service is deprecated since version 5.2", + "count": 1 } ]