From d59dbbd859d9dc14aea0d62f27978cf6de9c9b6e Mon Sep 17 00:00:00 2001 From: Vladislav Date: Mon, 22 Nov 2021 22:18:16 +0300 Subject: [PATCH 1/9] Issue 1848 operation id by route name (#1907) * Fix #1885 update psr/log and psr/container * Issue #1848 operation id by route name Co-authored-by: Vlad Gaiduk --- Describer/OpenApiPhpDescriber.php | 10 ++++++--- Tests/Functional/Controller/ApiController.php | 21 ++++++++++++++++++- Tests/Functional/FunctionalTest.php | 20 +++++++++++++++++- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/Describer/OpenApiPhpDescriber.php b/Describer/OpenApiPhpDescriber.php index 4f03918..96aad2d 100644 --- a/Describer/OpenApiPhpDescriber.php +++ b/Describer/OpenApiPhpDescriber.php @@ -47,7 +47,7 @@ final class OpenApiPhpDescriber $classAnnotations = []; /** @var \ReflectionMethod $method */ - foreach ($this->getMethodsToParse() as $method => list($path, $httpMethods)) { + foreach ($this->getMethodsToParse() as $method => list($path, $httpMethods, $routeName)) { $declaringClass = $method->getDeclaringClass(); $path = Util::getPath($api, $path); @@ -134,6 +134,10 @@ final class OpenApiPhpDescriber $operation = Util::getOperation($path, $httpMethod); $operation->merge($implicitAnnotations); $operation->mergeProperties($mergeProperties); + + if (OA\UNDEFINED === $operation->operationId) { + $operation->operationId = $httpMethod.'_'.$routeName; + } } } @@ -143,7 +147,7 @@ final class OpenApiPhpDescriber private function getMethodsToParse(): \Generator { - foreach ($this->routeCollection->all() as $route) { + foreach ($this->routeCollection->all() as $routeName => $route) { if (!$route->hasDefault('_controller')) { continue; } @@ -161,7 +165,7 @@ final class OpenApiPhpDescriber continue; } - yield $reflectedMethod => [$path, $supportedHttpMethods]; + yield $reflectedMethod => [$path, $supportedHttpMethods, $routeName]; } } diff --git a/Tests/Functional/Controller/ApiController.php b/Tests/Functional/Controller/ApiController.php index 4a40897..69372d3 100644 --- a/Tests/Functional/Controller/ApiController.php +++ b/Tests/Functional/Controller/ApiController.php @@ -26,7 +26,7 @@ use OpenApi\Annotations as OA; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/api", host="api.example.com") + * @Route("/api", name="api_", host="api.example.com") */ class ApiController { @@ -232,4 +232,23 @@ class ApiController public function discriminatorMappingAction() { } + + /** + * @Route("/named_route-operation-id", name="named_route_operation_id", methods={"GET", "POST"}) + * + * @OA\Response(response=200, description="success") + */ + public function namedRouteOperationIdAction() + { + } + + /** + * @Route("/custom-operation-id", methods={"GET", "POST"}) + * + * @Operation(operationId="custom-operation-id") + * @OA\Response(response=200, description="success") + */ + public function customOperationIdAction() + { + } } diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index 8b2b372..6094466 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -506,7 +506,25 @@ class FunctionalTest extends WebTestCase public function testDefaultOperationId() { $operation = $this->getOperation('/api/article/{id}', 'get'); - $this->assertNull($operation->operationId); + $this->assertEquals('get_api_nelmio_apidoc_tests_functional_api_fetcharticle', $operation->operationId); + } + + public function testNamedRouteOperationId() + { + $operation = $this->getOperation('/api/named_route-operation-id', 'get'); + $this->assertEquals('get_api_named_route_operation_id', $operation->operationId); + + $operation = $this->getOperation('/api/named_route-operation-id', 'post'); + $this->assertEquals('post_api_named_route_operation_id', $operation->operationId); + } + + public function testCustomOperationId() + { + $operation = $this->getOperation('/api/custom-operation-id', 'get'); + $this->assertEquals('custom-operation-id', $operation->operationId); + + $operation = $this->getOperation('/api/custom-operation-id', 'post'); + $this->assertEquals('custom-operation-id', $operation->operationId); } /** From 82bb3cb916d6f7b8d3ffc99655a49f1b019f0d90 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Tue, 30 Nov 2021 13:06:32 +0100 Subject: [PATCH 2/9] Fix usage of getCollectionValueTypes and getCollectionKeyTypes (#1910) * Fix usage of getCollectionValueTypes and getCollectionKeyTypes * fix cs --- Model/ModelRegistry.php | 24 +++++++++++-- Tests/Model/ModelRegistryTest.php | 60 +++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/Model/ModelRegistry.php b/Model/ModelRegistry.php index 8926264..db346fb 100644 --- a/Model/ModelRegistry.php +++ b/Model/ModelRegistry.php @@ -144,8 +144,8 @@ final class ModelRegistry 'built_in_type' => $type->getBuiltinType(), 'nullable' => $type->isNullable(), 'collection' => $type->isCollection(), - 'collection_key_types' => $type->isCollection() ? array_map($getType, $type->getCollectionKeyTypes()) : null, - 'collection_value_types' => $type->isCollection() ? array_map($getType, $type->getCollectionValueTypes()) : null, + 'collection_key_types' => $type->isCollection() ? array_map($getType, $this->getCollectionKeyTypes($type)) : null, + 'collection_value_types' => $type->isCollection() ? array_map($getType, $this->getCollectionValueTypes($type)) : null, ]; }; @@ -186,6 +186,26 @@ final class ModelRegistry } } + private function getCollectionKeyTypes(Type $type): array + { + // BC layer, this condition should be removed after removing support for symfony < 5.3 + if (!method_exists($type, 'getCollectionKeyTypes')) { + return null !== $type->getCollectionKeyType() ? [$type->getCollectionKeyType()] : []; + } + + return $type->getCollectionKeyTypes(); + } + + private function getCollectionValueTypes(Type $type): array + { + // BC layer, this condition should be removed after removing support for symfony < 5.3 + if (!method_exists($type, 'getCollectionValueTypes')) { + return null !== $type->getCollectionValueType() ? [$type->getCollectionValueType()] : []; + } + + return $type->getCollectionValueTypes(); + } + private function getCollectionValueType(Type $type): ?Type { // BC layer, this condition should be removed after removing support for symfony < 5.3 diff --git a/Tests/Model/ModelRegistryTest.php b/Tests/Model/ModelRegistryTest.php index 66eb16c..ef7fd85 100644 --- a/Tests/Model/ModelRegistryTest.php +++ b/Tests/Model/ModelRegistryTest.php @@ -34,7 +34,10 @@ class ModelRegistryTest extends TestCase $this->assertEquals('#/components/schemas/array', $registry->register(new Model($type, ['group1']))); } - public function testNameCollisionsAreLogged() + /** + * @dataProvider provideNameCollisionsTypes + */ + public function testNameCollisionsAreLogged(Type $type, array $arrayType) { $logger = $this->createMock(LoggerInterface::class); $logger @@ -43,26 +46,12 @@ class ModelRegistryTest extends TestCase ->with( 'Can not assign a name for the model, the name "ModelRegistryTest" has already been taken.', [ 'model' => [ - 'type' => [ - 'class' => 'Nelmio\\ApiDocBundle\\Tests\\Model\\ModelRegistryTest', - 'built_in_type' => 'object', - 'nullable' => false, - 'collection' => false, - 'collection_key_types' => null, - 'collection_value_types' => null, - ], + 'type' => $arrayType, 'options' => null, 'groups' => ['group2'], ], 'taken_by' => [ - 'type' => [ - 'class' => 'Nelmio\\ApiDocBundle\\Tests\\Model\\ModelRegistryTest', - 'built_in_type' => 'object', - 'nullable' => false, - 'collection' => false, - 'collection_key_types' => null, - 'collection_value_types' => null, - ], + 'type' => $arrayType, 'options' => null, 'groups' => ['group1'], ], @@ -71,11 +60,46 @@ class ModelRegistryTest extends TestCase $registry = new ModelRegistry([], new OA\OpenApi([]), []); $registry->setLogger($logger); - $type = new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class); $registry->register(new Model($type, ['group1'])); $registry->register(new Model($type, ['group2'])); } + public function provideNameCollisionsTypes() + { + yield [ + new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class), + [ + 'class' => 'Nelmio\\ApiDocBundle\\Tests\\Model\\ModelRegistryTest', + 'built_in_type' => 'object', + 'nullable' => false, + 'collection' => false, + 'collection_key_types' => null, + 'collection_value_types' => null, + ], + ]; + + yield [ + new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class, true, new Type(Type::BUILTIN_TYPE_OBJECT)), + [ + 'class' => 'Nelmio\\ApiDocBundle\\Tests\\Model\\ModelRegistryTest', + 'built_in_type' => 'object', + 'nullable' => false, + 'collection' => true, + 'collection_key_types' => [ + [ + 'class' => null, + 'built_in_type' => 'object', + 'nullable' => false, + 'collection' => false, + 'collection_key_types' => null, + 'collection_value_types' => null, + ], + ], + 'collection_value_types' => [], + ], + ]; + } + public function testNameCollisionsAreLoggedWithAlternativeNames() { $ref = new \ReflectionClass(self::class); From 4148dfda2567f3514fa53fb7ca787878c6de2368 Mon Sep 17 00:00:00 2001 From: Encre Informatique Date: Sat, 11 Dec 2021 09:58:33 -0300 Subject: [PATCH 3/9] fixed : add doc blocks to silence Symfony deprecations (#1922) * fixed : add doc blocks to silence Symfony deprecations Symfony 5.4 returning deprecations about ``` User Deprecated: Method "Twig\Extension\ExtensionInterface::getFunctions()" might add "array" as a native return type declaration in the future. Do the same in implementation "Nelmio\ApiDocBundle\Render\Html\GetNelmioAsset" now to avoid errors or add an explicit @return annotation to suppress this message. ``` * fixed : native hint --- Render/Html/GetNelmioAsset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Render/Html/GetNelmioAsset.php b/Render/Html/GetNelmioAsset.php index be64283..0ec88f9 100644 --- a/Render/Html/GetNelmioAsset.php +++ b/Render/Html/GetNelmioAsset.php @@ -31,7 +31,7 @@ class GetNelmioAsset extends AbstractExtension $this->resourcesDir = __DIR__.'/../../Resources/public'; } - public function getFunctions() + public function getFunctions(): array { return [ new TwigFunction('nelmioAsset', $this, ['is_safe' => ['html']]), From a184cb8ef4853db21ee0be8d99f58ef8b13ccb67 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Sat, 11 Dec 2021 14:19:43 +0100 Subject: [PATCH 4/9] Fix deprecations (#1923) --- DependencyInjection/Configuration.php | 2 +- PropertyDescriber/ObjectPropertyDescriber.php | 2 +- Resources/doc/index.rst | 2 +- Tests/Functional/ArrayItemsErrorTest.php | 3 ++- Tests/Functional/BazingaFunctionalTest.php | 3 ++- Tests/Functional/JMSFunctionalTest.php | 4 +++- Tests/Functional/TestKernel.php | 6 +++--- Tests/Functional/WebTestCase.php | 3 ++- 8 files changed, 15 insertions(+), 10 deletions(-) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 5a4c2e3..e23380d 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -16,7 +16,7 @@ use Symfony\Component\Config\Definition\ConfigurationInterface; final class Configuration implements ConfigurationInterface { - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('nelmio_api_doc'); diff --git a/PropertyDescriber/ObjectPropertyDescriber.php b/PropertyDescriber/ObjectPropertyDescriber.php index ef1aabc..34361d8 100644 --- a/PropertyDescriber/ObjectPropertyDescriber.php +++ b/PropertyDescriber/ObjectPropertyDescriber.php @@ -28,8 +28,8 @@ class ObjectPropertyDescriber implements PropertyDescriberInterface, ModelRegist false, $types[0]->getClassName(), $types[0]->isCollection(), - $types[0]->getCollectionKeyType(), // BC layer for symfony < 5.3 + method_exists($types[0], 'getCollectionKeyTypes') ? $types[0]->getCollectionKeyTypes() : $types[0]->getCollectionKeyType(), method_exists($types[0], 'getCollectionValueTypes') ? ($types[0]->getCollectionValueTypes()[0] ?? null) : $types[0]->getCollectionValueType() diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index 9daa3ea..95e2944 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -39,7 +39,7 @@ Open a command console, enter your project directory and execute the following c class AppKernel extends Kernel { - public function registerBundles() + public function registerBundles(): iterable { $bundles = [ // ... diff --git a/Tests/Functional/ArrayItemsErrorTest.php b/Tests/Functional/ArrayItemsErrorTest.php index 57a0ea0..cfeaa6d 100644 --- a/Tests/Functional/ArrayItemsErrorTest.php +++ b/Tests/Functional/ArrayItemsErrorTest.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use Nelmio\ApiDocBundle\Exception\UndocumentedArrayItemsException; +use Symfony\Component\HttpKernel\KernelInterface; class ArrayItemsErrorTest extends WebTestCase { @@ -30,7 +31,7 @@ class ArrayItemsErrorTest extends WebTestCase $this->getOpenApiDefinition(); } - protected static function createKernel(array $options = []) + protected static function createKernel(array $options = []): KernelInterface { return new TestKernel(TestKernel::ERROR_ARRAY_ITEMS); } diff --git a/Tests/Functional/BazingaFunctionalTest.php b/Tests/Functional/BazingaFunctionalTest.php index ff46e8d..d7f9146 100644 --- a/Tests/Functional/BazingaFunctionalTest.php +++ b/Tests/Functional/BazingaFunctionalTest.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use Hateoas\Configuration\Embedded; +use Symfony\Component\HttpKernel\KernelInterface; class BazingaFunctionalTest extends WebTestCase { @@ -123,7 +124,7 @@ class BazingaFunctionalTest extends WebTestCase ], json_decode($this->getModel('BazingaUserTyped')->toJson(), true)); } - protected static function createKernel(array $options = []) + protected static function createKernel(array $options = []): KernelInterface { return new TestKernel(TestKernel::USE_JMS | TestKernel::USE_BAZINGA); } diff --git a/Tests/Functional/JMSFunctionalTest.php b/Tests/Functional/JMSFunctionalTest.php index 221800d..9bcc3c3 100644 --- a/Tests/Functional/JMSFunctionalTest.php +++ b/Tests/Functional/JMSFunctionalTest.php @@ -11,6 +11,8 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; +use Symfony\Component\HttpKernel\KernelInterface; + class JMSFunctionalTest extends WebTestCase { protected function setUp(): void @@ -333,7 +335,7 @@ class JMSFunctionalTest extends WebTestCase ], json_decode($this->getModel('JMSNamingStrategyConstraints')->toJson(), true)); } - protected static function createKernel(array $options = []) + protected static function createKernel(array $options = []): KernelInterface { return new TestKernel(TestKernel::USE_JMS); } diff --git a/Tests/Functional/TestKernel.php b/Tests/Functional/TestKernel.php index 5de564d..3f3f668 100644 --- a/Tests/Functional/TestKernel.php +++ b/Tests/Functional/TestKernel.php @@ -53,7 +53,7 @@ class TestKernel extends Kernel /** * {@inheritdoc} */ - public function registerBundles() + public function registerBundles(): iterable { $bundles = [ new FrameworkBundle(), @@ -275,7 +275,7 @@ class TestKernel extends Kernel /** * {@inheritdoc} */ - public function getCacheDir() + public function getCacheDir(): string { return parent::getCacheDir().'/'.$this->flags; } @@ -283,7 +283,7 @@ class TestKernel extends Kernel /** * {@inheritdoc} */ - public function getLogDir() + public function getLogDir(): string { return parent::getLogDir().'/'.$this->flags; } diff --git a/Tests/Functional/WebTestCase.php b/Tests/Functional/WebTestCase.php index 2268de2..e8a5da4 100644 --- a/Tests/Functional/WebTestCase.php +++ b/Tests/Functional/WebTestCase.php @@ -13,10 +13,11 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use OpenApi\Annotations as OA; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; +use Symfony\Component\HttpKernel\KernelInterface; class WebTestCase extends BaseWebTestCase { - protected static function createKernel(array $options = []) + protected static function createKernel(array $options = []): KernelInterface { return new TestKernel(); } From a041c69a0ca86a8f6f10cba7f8805f82ab7d9c39 Mon Sep 17 00:00:00 2001 From: Gemorroj Date: Sat, 11 Dec 2021 16:32:00 +0300 Subject: [PATCH 5/9] try fix #1876 (#1919) --- DependencyInjection/NelmioApiDocExtension.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index cd6a622..2040acb 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -152,14 +152,15 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI ->setArgument(1, $config['media_types']); } - // ApiPlatform support $bundles = $container->getParameter('kernel.bundles'); - if (!isset($bundles['TwigBundle'])) { + if (!isset($bundles['TwigBundle']) || !class_exists('Symfony\Component\Asset\Packages')) { $container->removeDefinition('nelmio_api_doc.controller.swagger_ui'); $container->removeDefinition('nelmio_api_doc.render_docs.html'); $container->removeDefinition('nelmio_api_doc.render_docs.html.asset'); } + + // ApiPlatform support if (isset($bundles['ApiPlatformBundle']) && class_exists('ApiPlatform\Core\Documentation\Documentation')) { $loader->load('api_platform.xml'); } From 14383f4ee5cab468ec1549097943cf31f05d4463 Mon Sep 17 00:00:00 2001 From: Alexey Alshenetsky Date: Sat, 11 Dec 2021 16:39:04 +0300 Subject: [PATCH 6/9] Add support for zircore/swagger-php 4.0 (#1916) * add zircore/swagger-php v4 to composer.json * fix incompatibilities * add compatibility with 3.2 * Apply fixes from StyleCI * mark SetsContextTrait as internal * Bump php version Co-authored-by: Alexey Co-authored-by: Alexey Alshenetsky Co-authored-by: Guilhem Niot --- .github/workflows/continuous-integration.yml | 2 +- ApiDocGenerator.php | 3 ++- Describer/DefaultDescriber.php | 9 ++++--- Describer/OpenApiPhpDescriber.php | 25 +++++++++++-------- .../Annotations/OpenApiAnnotationsReader.php | 14 +++++++---- .../Annotations/PropertyPhpDocReader.php | 5 ++-- .../SymfonyConstraintAnnotationReader.php | 7 +++--- ModelDescriber/FormModelDescriber.php | 5 ++-- ModelDescriber/JMSModelDescriber.php | 3 ++- ModelDescriber/ObjectModelDescriber.php | 5 ++-- OpenApiPhp/DefaultOperationId.php | 3 ++- OpenApiPhp/Util.php | 12 ++++----- .../CompoundPropertyDescriber.php | 3 ++- RouteDescriber/FosRestDescriber.php | 7 +++--- RouteDescriber/PhpDocDescriber.php | 5 ++-- RouteDescriber/RouteMetadataDescriber.php | 13 +++++----- Tests/Functional/FOSRestTest.php | 9 ++++--- Tests/Functional/FunctionalTest.php | 15 +++++------ Tests/Functional/WebTestCase.php | 15 +++++------ .../SymfonyConstraintAnnotationReaderTest.php | 23 +++++++++-------- .../ApplyOpenApiDiscriminatorTraitTest.php | 3 ++- Tests/SwaggerPhp/UtilTest.php | 6 ++--- Util/SetsContextTrait.php | 22 ++++++++++++++++ composer.json | 4 +-- 24 files changed, 133 insertions(+), 85 deletions(-) create mode 100644 Util/SetsContextTrait.php diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5500844..3174fff 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -24,7 +24,7 @@ jobs: strategy: matrix: include: - - php-version: 7.1 + - php-version: 7.2 composer-flags: "--prefer-lowest" - php-version: 7.2 symfony-require: "^4.0" diff --git a/ApiDocGenerator.php b/ApiDocGenerator.php index 57b1059..161e104 100644 --- a/ApiDocGenerator.php +++ b/ApiDocGenerator.php @@ -20,6 +20,7 @@ use Nelmio\ApiDocBundle\OpenApiPhp\ModelRegister; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Analysis; use OpenApi\Annotations\OpenApi; +use OpenApi\Generator; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerAwareTrait; @@ -110,7 +111,7 @@ final class ApiDocGenerator $defaultOperationIdProcessor = new DefaultOperationId(); $defaultOperationIdProcessor($analysis); - $analysis->process(); + $analysis->process((new Generator())->getProcessors()); $analysis->validate(); if (isset($item)) { diff --git a/Describer/DefaultDescriber.php b/Describer/DefaultDescriber.php index 5217d3c..3812e3a 100644 --- a/Describer/DefaultDescriber.php +++ b/Describer/DefaultDescriber.php @@ -13,6 +13,7 @@ namespace Nelmio\ApiDocBundle\Describer; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; +use OpenApi\Generator; /** * Makes the swagger documentation valid even if there are missing fields. @@ -26,22 +27,22 @@ final class DefaultDescriber implements DescriberInterface // Info /** @var OA\Info $info */ $info = Util::getChild($api, OA\Info::class); - if (OA\UNDEFINED === $info->title) { + if (Generator::UNDEFINED === $info->title) { $info->title = ''; } - if (OA\UNDEFINED === $info->version) { + if (Generator::UNDEFINED === $info->version) { $info->version = '0.0.0'; } // Paths - if (OA\UNDEFINED === $api->paths) { + if (Generator::UNDEFINED === $api->paths) { $api->paths = []; } foreach ($api->paths as $path) { foreach (Util::OPERATIONS as $method) { /** @var OA\Operation $operation */ $operation = $path->{$method}; - if (OA\UNDEFINED !== $operation && null !== $operation && (OA\UNDEFINED === $operation->responses || empty($operation->responses))) { + if (Generator::UNDEFINED !== $operation && null !== $operation && (Generator::UNDEFINED === $operation->responses || empty($operation->responses))) { /** @var OA\Response $response */ $response = Util::getIndexedCollectionItem($operation, OA\Response::class, 'default'); $response->description = ''; diff --git a/Describer/OpenApiPhpDescriber.php b/Describer/OpenApiPhpDescriber.php index 96aad2d..3c3e485 100644 --- a/Describer/OpenApiPhpDescriber.php +++ b/Describer/OpenApiPhpDescriber.php @@ -16,8 +16,9 @@ use Nelmio\ApiDocBundle\Annotation\Operation; use Nelmio\ApiDocBundle\Annotation\Security; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use Nelmio\ApiDocBundle\Util\ControllerReflector; -use OpenApi\Analyser; +use Nelmio\ApiDocBundle\Util\SetsContextTrait; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -27,6 +28,8 @@ class_exists(OA\OpenApi::class); final class OpenApiPhpDescriber { + use SetsContextTrait; + private $routeCollection; private $controllerReflector; private $annotationReader; @@ -52,11 +55,13 @@ final class OpenApiPhpDescriber $path = Util::getPath($api, $path); - Analyser::$context = Util::createContext(['nested' => $path], $path->_context); - Analyser::$context->namespace = $method->getNamespaceName(); - Analyser::$context->class = $declaringClass->getShortName(); - Analyser::$context->method = $method->name; - Analyser::$context->filename = $method->getFileName(); + $context = Util::createContext(['nested' => $path], $path->_context); + $context->namespace = $method->getNamespaceName(); + $context->class = $declaringClass->getShortName(); + $context->method = $method->name; + $context->filename = $method->getFileName(); + + $this->setContext($context); if (!array_key_exists($declaringClass->getName(), $classAnnotations)) { $classAnnotations = array_filter($this->annotationReader->getClassAnnotations($declaringClass), function ($v) { @@ -90,7 +95,7 @@ final class OpenApiPhpDescriber if (!in_array($annotation->method, $httpMethods, true)) { continue; } - if (OA\UNDEFINED !== $annotation->path && $path->path !== $annotation->path) { + if (Generator::UNDEFINED !== $annotation->path && $path->path !== $annotation->path) { continue; } @@ -135,14 +140,14 @@ final class OpenApiPhpDescriber $operation->merge($implicitAnnotations); $operation->mergeProperties($mergeProperties); - if (OA\UNDEFINED === $operation->operationId) { + if (Generator::UNDEFINED === $operation->operationId) { $operation->operationId = $httpMethod.'_'.$routeName; } } } - // Reset the Analyser after the parsing - Analyser::$context = null; + // Reset the Generator after the parsing + $this->setContext(null); } private function getMethodsToParse(): \Generator diff --git a/ModelDescriber/Annotations/OpenApiAnnotationsReader.php b/ModelDescriber/Annotations/OpenApiAnnotationsReader.php index 020b8b1..a7d20f1 100644 --- a/ModelDescriber/Annotations/OpenApiAnnotationsReader.php +++ b/ModelDescriber/Annotations/OpenApiAnnotationsReader.php @@ -15,16 +15,19 @@ use Doctrine\Common\Annotations\Reader; use Nelmio\ApiDocBundle\Model\ModelRegistry; use Nelmio\ApiDocBundle\OpenApiPhp\ModelRegister; use Nelmio\ApiDocBundle\OpenApiPhp\Util; -use OpenApi\Analyser; +use Nelmio\ApiDocBundle\Util\SetsContextTrait; use OpenApi\Analysis; use OpenApi\Annotations as OA; use OpenApi\Context; +use OpenApi\Generator; /** * @internal */ class OpenApiAnnotationsReader { + use SetsContextTrait; + private $annotationsReader; private $modelRegister; @@ -60,19 +63,20 @@ class OpenApiAnnotationsReader return $default; } - return OA\UNDEFINED !== $oaProperty->property ? $oaProperty->property : $default; + return Generator::UNDEFINED !== $oaProperty->property ? $oaProperty->property : $default; } public function updateProperty($reflection, OA\Property $property, array $serializationGroups = null): void { // In order to have nicer errors $declaringClass = $reflection->getDeclaringClass(); - Analyser::$context = new Context([ + + $this->setContext(new Context([ 'namespace' => $declaringClass->getNamespaceName(), 'class' => $declaringClass->getShortName(), 'property' => $reflection->name, 'filename' => $declaringClass->getFileName(), - ]); + ])); /** @var OA\Property $oaProperty */ if ($reflection instanceof \ReflectionProperty && !$oaProperty = $this->annotationsReader->getPropertyAnnotation($reflection, OA\Property::class)) { @@ -80,7 +84,7 @@ class OpenApiAnnotationsReader } elseif ($reflection instanceof \ReflectionMethod && !$oaProperty = $this->annotationsReader->getMethodAnnotation($reflection, OA\Property::class)) { return; } - Analyser::$context = null; + $this->setContext(null); // Read @Model annotations $this->modelRegister->__invoke(new Analysis([$oaProperty], Util::createContext()), $serializationGroups); diff --git a/ModelDescriber/Annotations/PropertyPhpDocReader.php b/ModelDescriber/Annotations/PropertyPhpDocReader.php index 5234b45..cd3f8de 100644 --- a/ModelDescriber/Annotations/PropertyPhpDocReader.php +++ b/ModelDescriber/Annotations/PropertyPhpDocReader.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\ModelDescriber\Annotations; use OpenApi\Annotations as OA; +use OpenApi\Generator; use phpDocumentor\Reflection\DocBlock\Tags\Var_; use phpDocumentor\Reflection\DocBlockFactory; @@ -53,10 +54,10 @@ class PropertyPhpDocReader } } } - if (OA\UNDEFINED === $property->title && $title) { + if (Generator::UNDEFINED === $property->title && $title) { $property->title = $title; } - if (OA\UNDEFINED === $property->description && $docBlock->getDescription() && $docBlock->getDescription()->render()) { + if (Generator::UNDEFINED === $property->description && $docBlock->getDescription() && $docBlock->getDescription()->render()) { $property->description = $docBlock->getDescription()->render(); } } diff --git a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php index 8fa31f6..6b9e771 100644 --- a/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php +++ b/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php @@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\ModelDescriber\Annotations; use Doctrine\Common\Annotations\Reader; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints as Assert; @@ -73,7 +74,7 @@ class SymfonyConstraintAnnotationReader return; } - $existingRequiredFields = OA\UNDEFINED !== $this->schema->required ? $this->schema->required : []; + $existingRequiredFields = Generator::UNDEFINED !== $this->schema->required ? $this->schema->required : []; $existingRequiredFields[] = $propertyName; $this->schema->required = array_values(array_unique($existingRequiredFields)); @@ -131,7 +132,7 @@ class SymfonyConstraintAnnotationReader } foreach ($this->schema->properties as $schemaProperty) { if ($schemaProperty === $property) { - return OA\UNDEFINED !== $schemaProperty->property ? $schemaProperty->property : null; + return Generator::UNDEFINED !== $schemaProperty->property ? $schemaProperty->property : null; } } @@ -146,7 +147,7 @@ class SymfonyConstraintAnnotationReader if (null === $newPattern) { return; } - if (OA\UNDEFINED !== $property->pattern) { + if (Generator::UNDEFINED !== $property->pattern) { $property->pattern = sprintf('%s, %s', $property->pattern, $newPattern); } else { $property->pattern = $newPattern; diff --git a/ModelDescriber/FormModelDescriber.php b/ModelDescriber/FormModelDescriber.php index 6bff0e4..093422b 100644 --- a/ModelDescriber/FormModelDescriber.php +++ b/ModelDescriber/FormModelDescriber.php @@ -18,6 +18,7 @@ use Nelmio\ApiDocBundle\Model\Model; use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormConfigInterface; @@ -90,7 +91,7 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry $property = Util::getProperty($schema, $name); if ($config->getRequired()) { - $required = OA\UNDEFINED !== $schema->required ? $schema->required : []; + $required = Generator::UNDEFINED !== $schema->required ? $schema->required : []; $required[] = $name; $schema->required = $required; @@ -100,7 +101,7 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry $property->mergeProperties($config->getOption('documentation')); } - if (OA\UNDEFINED !== $property->type) { + if (Generator::UNDEFINED !== $property->type) { continue; // Type manually defined } diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index c81519a..60a57ac 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -23,6 +23,7 @@ use Nelmio\ApiDocBundle\Model\Model; use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Component\PropertyInfo\Type; /** @@ -134,7 +135,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn $annotationsReader->updateProperty($reflection, $property, $groups); } - if (OA\UNDEFINED !== $property->type || OA\UNDEFINED !== $property->ref) { + if (Generator::UNDEFINED !== $property->type || Generator::UNDEFINED !== $property->ref) { $context->popPropertyMetadata(); continue; diff --git a/ModelDescriber/ObjectModelDescriber.php b/ModelDescriber/ObjectModelDescriber.php index 0aa339a..cab10c1 100644 --- a/ModelDescriber/ObjectModelDescriber.php +++ b/ModelDescriber/ObjectModelDescriber.php @@ -20,6 +20,7 @@ use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use Nelmio\ApiDocBundle\PropertyDescriber\PropertyDescriberInterface; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; @@ -72,7 +73,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar $annotationsReader->updateDefinition($reflClass, $schema); $discriminatorMap = $this->doctrineReader->getClassAnnotation($reflClass, DiscriminatorMap::class); - if ($discriminatorMap && OA\UNDEFINED === $schema->discriminator) { + if ($discriminatorMap && Generator::UNDEFINED === $schema->discriminator) { $this->applyOpenApiDiscriminator( $model, $schema, @@ -114,7 +115,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar } // If type manually defined - if (OA\UNDEFINED !== $property->type || OA\UNDEFINED !== $property->ref) { + if (Generator::UNDEFINED !== $property->type || Generator::UNDEFINED !== $property->ref) { continue; } diff --git a/OpenApiPhp/DefaultOperationId.php b/OpenApiPhp/DefaultOperationId.php index b923b8c..0a30fce 100644 --- a/OpenApiPhp/DefaultOperationId.php +++ b/OpenApiPhp/DefaultOperationId.php @@ -13,6 +13,7 @@ namespace Nelmio\ApiDocBundle\OpenApiPhp; use OpenApi\Analysis; use OpenApi\Annotations as OA; +use OpenApi\Generator; /** * Disable the OperationId processor from zircote/swagger-php as it breaks our documentation by setting non-unique operation ids. @@ -27,7 +28,7 @@ final class DefaultOperationId $allOperations = $analysis->getAnnotationsOfType(OA\Operation::class); foreach ($allOperations as $operation) { - if (OA\UNDEFINED === $operation->operationId) { + if (Generator::UNDEFINED === $operation->operationId) { $operation->operationId = null; } } diff --git a/OpenApiPhp/Util.php b/OpenApiPhp/Util.php index ba97ede..cb57de1 100644 --- a/OpenApiPhp/Util.php +++ b/OpenApiPhp/Util.php @@ -13,7 +13,7 @@ namespace Nelmio\ApiDocBundle\OpenApiPhp; use OpenApi\Annotations as OA; use OpenApi\Context; -use const OpenApi\UNDEFINED; +use OpenApi\Generator; /** * Class Util. @@ -163,7 +163,7 @@ final class Util $nested = $parent::$_nested; $property = $nested[$class]; - if (null === $parent->{$property} || UNDEFINED === $parent->{$property}) { + if (null === $parent->{$property} || Generator::UNDEFINED === $parent->{$property}) { $parent->{$property} = self::createChild($parent, $class, $properties); } @@ -192,7 +192,7 @@ final class Util if (!empty($properties)) { $key = self::searchCollectionItem( - $parent->{$collection} && UNDEFINED !== $parent->{$collection} ? $parent->{$collection} : [], + $parent->{$collection} && Generator::UNDEFINED !== $parent->{$collection} ? $parent->{$collection} : [], $properties ); } @@ -224,7 +224,7 @@ final class Util [$collection, $property] = $nested[$class]; $key = self::searchIndexedCollectionItem( - $parent->{$collection} && UNDEFINED !== $parent->{$collection} ? $parent->{$collection} : [], + $parent->{$collection} && Generator::UNDEFINED !== $parent->{$collection} ? $parent->{$collection} : [], $property, $value ); @@ -279,7 +279,7 @@ final class Util */ public static function createCollectionItem(OA\AbstractAnnotation $parent, $collection, $class, array $properties = []): int { - if (UNDEFINED === $parent->{$collection}) { + if (Generator::UNDEFINED === $parent->{$collection}) { $parent->{$collection} = []; } @@ -418,7 +418,7 @@ final class Util if (\is_string($type) && 0 === strpos($type, '[')) { $innerType = substr($type, 1, -1); - if (!$annotation->{$propertyName} || UNDEFINED === $annotation->{$propertyName}) { + if (!$annotation->{$propertyName} || Generator::UNDEFINED === $annotation->{$propertyName}) { $annotation->{$propertyName} = []; } diff --git a/PropertyDescriber/CompoundPropertyDescriber.php b/PropertyDescriber/CompoundPropertyDescriber.php index ba6cf6c..17801d1 100644 --- a/PropertyDescriber/CompoundPropertyDescriber.php +++ b/PropertyDescriber/CompoundPropertyDescriber.php @@ -15,6 +15,7 @@ use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; +use OpenApi\Generator; class CompoundPropertyDescriber implements PropertyDescriberInterface, ModelRegistryAwareInterface { @@ -30,7 +31,7 @@ class CompoundPropertyDescriber implements PropertyDescriberInterface, ModelRegi public function describe(array $types, OA\Schema $property, array $groups = null) { - $property->oneOf = OA\UNDEFINED !== $property->oneOf ? $property->oneOf : []; + $property->oneOf = Generator::UNDEFINED !== $property->oneOf ? $property->oneOf : []; foreach ($types as $type) { $property->oneOf[] = $schema = Util::createChild($property, OA\Schema::class, []); diff --git a/RouteDescriber/FosRestDescriber.php b/RouteDescriber/FosRestDescriber.php index e3f9f4a..7235f5d 100644 --- a/RouteDescriber/FosRestDescriber.php +++ b/RouteDescriber/FosRestDescriber.php @@ -16,6 +16,7 @@ use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\RequestParam; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Component\Routing\Route; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\DateTime; @@ -55,7 +56,7 @@ final class FosRestDescriber implements RouteDescriberInterface $parameter->required = !$annotation->nullable && $annotation->strict; - if (OA\UNDEFINED === $parameter->description) { + if (Generator::UNDEFINED === $parameter->description) { $parameter->description = $annotation->description; } @@ -128,7 +129,7 @@ final class FosRestDescriber implements RouteDescriberInterface private function getContentSchemaForType(OA\RequestBody $requestBody, string $type): OA\Schema { - $requestBody->content = OA\UNDEFINED !== $requestBody->content ? $requestBody->content : []; + $requestBody->content = Generator::UNDEFINED !== $requestBody->content ? $requestBody->content : []; switch ($type) { case 'json': $contentType = 'application/json'; @@ -165,7 +166,7 @@ final class FosRestDescriber implements RouteDescriberInterface { $schema->default = $annotation->getDefault(); - if (OA\UNDEFINED === $schema->type) { + if (Generator::UNDEFINED === $schema->type) { $schema->type = $annotation->map ? 'array' : 'string'; } diff --git a/RouteDescriber/PhpDocDescriber.php b/RouteDescriber/PhpDocDescriber.php index 1ae2ef7..75d0769 100644 --- a/RouteDescriber/PhpDocDescriber.php +++ b/RouteDescriber/PhpDocDescriber.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\RouteDescriber; use OpenApi\Annotations as OA; +use OpenApi\Generator; use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactoryInterface; use Symfony\Component\Routing\Route; @@ -47,10 +48,10 @@ final class PhpDocDescriber implements RouteDescriberInterface foreach ($this->getOperations($api, $route) as $operation) { if (null !== $docBlock) { - if (OA\UNDEFINED === $operation->summary && '' !== $docBlock->getSummary()) { + if (Generator::UNDEFINED === $operation->summary && '' !== $docBlock->getSummary()) { $operation->summary = $docBlock->getSummary(); } - if (OA\UNDEFINED === $operation->description && '' !== (string) $docBlock->getDescription()) { + if (Generator::UNDEFINED === $operation->description && '' !== (string) $docBlock->getDescription()) { $operation->description = (string) $docBlock->getDescription(); } if ($docBlock->hasTag('deprecated')) { diff --git a/RouteDescriber/RouteMetadataDescriber.php b/RouteDescriber/RouteMetadataDescriber.php index f68dadd..7a24b4d 100644 --- a/RouteDescriber/RouteMetadataDescriber.php +++ b/RouteDescriber/RouteMetadataDescriber.php @@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\RouteDescriber; use LogicException; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Component\Routing\Route; /** @@ -40,7 +41,7 @@ final class RouteMetadataDescriber implements RouteDescriberInterface /** @var OA\Parameter $parameter */ $parameter = $existingParams[$paramId] ?? null; if (null !== $parameter) { - if (!$parameter->required || OA\UNDEFINED === $parameter->required) { + if (!$parameter->required || Generator::UNDEFINED === $parameter->required) { throw new LogicException(\sprintf('Global parameter "%s" is used as part of route "%s" and must be set as "required"', $pathVariable, $route->getPath())); } @@ -52,11 +53,11 @@ final class RouteMetadataDescriber implements RouteDescriberInterface $parameter->schema = Util::getChild($parameter, OA\Schema::class); - if (OA\UNDEFINED === $parameter->schema->type) { + if (Generator::UNDEFINED === $parameter->schema->type) { $parameter->schema->type = 'string'; } - if (isset($requirements[$pathVariable]) && OA\UNDEFINED === $parameter->schema->pattern) { + if (isset($requirements[$pathVariable]) && Generator::UNDEFINED === $parameter->schema->pattern) { $parameter->schema->pattern = $requirements[$pathVariable]; } } @@ -71,15 +72,15 @@ final class RouteMetadataDescriber implements RouteDescriberInterface private function getRefParams(OA\OpenApi $api, OA\Operation $operation): array { /** @var OA\Parameter[] $globalParams */ - $globalParams = OA\UNDEFINED !== $api->components && OA\UNDEFINED !== $api->components->parameters ? $api->components->parameters : []; + $globalParams = Generator::UNDEFINED !== $api->components && Generator::UNDEFINED !== $api->components->parameters ? $api->components->parameters : []; $globalParams = array_column($globalParams, null, 'parameter'); // update the indexes of the array with the reference names actually used $existingParams = []; - $operationParameters = OA\UNDEFINED !== $operation->parameters ? $operation->parameters : []; + $operationParameters = Generator::UNDEFINED !== $operation->parameters ? $operation->parameters : []; /** @var OA\Parameter $parameter */ foreach ($operationParameters as $id => $parameter) { $ref = $parameter->ref; - if (OA\UNDEFINED === $ref) { + if (Generator::UNDEFINED === $ref) { // we only concern ourselves with '$ref' parameters, so continue the loop continue; } diff --git a/Tests/Functional/FOSRestTest.php b/Tests/Functional/FOSRestTest.php index 66917b8..cd1cfbe 100644 --- a/Tests/Functional/FOSRestTest.php +++ b/Tests/Functional/FOSRestTest.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use OpenApi\Annotations as OA; +use OpenApi\Generator; class FOSRestTest extends WebTestCase { @@ -37,17 +38,17 @@ class FOSRestTest extends WebTestCase $fooParameter = $this->getParameter($operation, 'foo', 'query'); $this->assertInstanceOf(OA\Schema::class, $fooParameter->schema); $this->assertEquals('\d+', $fooParameter->schema->pattern); - $this->assertEquals(OA\UNDEFINED, $fooParameter->schema->format); + $this->assertEquals(Generator::UNDEFINED, $fooParameter->schema->format); $mappedParameter = $this->getParameter($operation, 'mapped[]', 'query'); $this->assertTrue($mappedParameter->explode); $barProperty = $this->getProperty($bodySchema, 'bar'); $this->assertEquals('\d+', $barProperty->pattern); - $this->assertEquals(OA\UNDEFINED, $barProperty->format); + $this->assertEquals(Generator::UNDEFINED, $barProperty->format); $bazProperty = $this->getProperty($bodySchema, 'baz'); - $this->assertEquals(OA\UNDEFINED, $bazProperty->pattern); + $this->assertEquals(Generator::UNDEFINED, $bazProperty->pattern); $this->assertEquals('IsTrue', $bazProperty->format); $dateTimeProperty = $this->getProperty($bodySchema, 'datetime'); @@ -57,7 +58,7 @@ class FOSRestTest extends WebTestCase $this->assertEquals('date-time', $dateTimeAltProperty->format); $dateTimeNoFormatProperty = $this->getProperty($bodySchema, 'datetimeNoFormat'); - $this->assertEquals(OA\UNDEFINED, $dateTimeNoFormatProperty->format); + $this->assertEquals(Generator::UNDEFINED, $dateTimeNoFormatProperty->format); $dateProperty = $this->getProperty($bodySchema, 'date'); $this->assertEquals('date', $dateProperty->format); diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index 6094466..0704bc6 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use Nelmio\ApiDocBundle\Tests\Helper; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Component\Serializer\Annotation\SerializedName; class FunctionalTest extends WebTestCase @@ -84,7 +85,7 @@ class FunctionalTest extends WebTestCase public function testAnnotationWithManualPath() { $path = $this->getPath('/api/swagger2'); - $this->assertSame(OA\UNDEFINED, $path->post); + $this->assertSame(Generator::UNDEFINED, $path->post); $operation = $this->getOperation('/api/swagger', 'get'); $this->assertNotHasParameter('Accept-Version', 'header', $operation); @@ -123,10 +124,10 @@ class FunctionalTest extends WebTestCase { $operation = $this->getOperation('/api/test/{user}', 'get'); - $this->assertEquals(OA\UNDEFINED, $operation->security); - $this->assertEquals(OA\UNDEFINED, $operation->summary); - $this->assertEquals(OA\UNDEFINED, $operation->description); - $this->assertEquals(OA\UNDEFINED, $operation->deprecated); + $this->assertEquals(Generator::UNDEFINED, $operation->security); + $this->assertEquals(Generator::UNDEFINED, $operation->summary); + $this->assertEquals(Generator::UNDEFINED, $operation->description); + $this->assertEquals(Generator::UNDEFINED, $operation->deprecated); $this->assertHasResponse(200, $operation); $this->assertHasParameter('user', 'path', $operation); @@ -134,7 +135,7 @@ class FunctionalTest extends WebTestCase $this->assertTrue($parameter->required); $this->assertEquals('string', $parameter->schema->type); $this->assertEquals('/foo/', $parameter->schema->pattern); - $this->assertEquals(OA\UNDEFINED, $parameter->schema->format); + $this->assertEquals(Generator::UNDEFINED, $parameter->schema->format); } public function testDeprecatedAction() @@ -551,7 +552,7 @@ class FunctionalTest extends WebTestCase $this->assertCount(2, $model->discriminator->mapping); $this->assertArrayHasKey('one', $model->discriminator->mapping); $this->assertArrayHasKey('two', $model->discriminator->mapping); - $this->assertNotSame(OA\UNDEFINED, $model->oneOf); + $this->assertNotSame(Generator::UNDEFINED, $model->oneOf); $this->assertCount(2, $model->oneOf); } diff --git a/Tests/Functional/WebTestCase.php b/Tests/Functional/WebTestCase.php index e8a5da4..c792c0d 100644 --- a/Tests/Functional/WebTestCase.php +++ b/Tests/Functional/WebTestCase.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional; use OpenApi\Annotations as OA; +use OpenApi\Generator; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase; use Symfony\Component\HttpKernel\KernelInterface; @@ -94,7 +95,7 @@ class WebTestCase extends BaseWebTestCase public function assertHasPath($path, OA\OpenApi $api) { - $paths = array_column(OA\UNDEFINED !== $api->paths ? $api->paths : [], 'path'); + $paths = array_column(Generator::UNDEFINED !== $api->paths ? $api->paths : [], 'path'); static::assertContains( $path, $paths, @@ -104,7 +105,7 @@ class WebTestCase extends BaseWebTestCase public function assertNotHasPath($path, OA\OpenApi $api) { - $paths = array_column(OA\UNDEFINED !== $api->paths ? $api->paths : [], 'path'); + $paths = array_column(Generator::UNDEFINED !== $api->paths ? $api->paths : [], 'path'); static::assertNotContains( $path, $paths, @@ -114,7 +115,7 @@ class WebTestCase extends BaseWebTestCase public function assertHasResponse($responseCode, OA\Operation $operation) { - $responses = array_column(OA\UNDEFINED !== $operation->responses ? $operation->responses : [], 'response'); + $responses = array_column(Generator::UNDEFINED !== $operation->responses ? $operation->responses : [], 'response'); static::assertContains( $responseCode, $responses, @@ -125,7 +126,7 @@ class WebTestCase extends BaseWebTestCase public function assertHasParameter($name, $in, OA\AbstractAnnotation $annotation) { /* @var OA\Operation|OA\OpenApi $annotation */ - $parameters = array_filter(OA\UNDEFINED !== $annotation->parameters ? $annotation->parameters : [], function (OA\Parameter $parameter) use ($name, $in) { + $parameters = array_filter(Generator::UNDEFINED !== $annotation->parameters ? $annotation->parameters : [], function (OA\Parameter $parameter) use ($name, $in) { return $parameter->name === $name && $parameter->in === $in; }); @@ -138,7 +139,7 @@ class WebTestCase extends BaseWebTestCase public function assertNotHasParameter($name, $in, OA\AbstractAnnotation $annotation) { /* @var OA\Operation|OA\OpenApi $annotation */ - $parameters = array_column(OA\UNDEFINED !== $annotation->parameters ? $annotation->parameters : [], 'name', 'in'); + $parameters = array_column(Generator::UNDEFINED !== $annotation->parameters ? $annotation->parameters : [], 'name', 'in'); static::assertNotContains( $name, $parameters[$in] ?? [], @@ -149,7 +150,7 @@ class WebTestCase extends BaseWebTestCase public function assertHasProperty($property, OA\AbstractAnnotation $annotation) { /* @var OA\Schema|OA\Property|OA\Items $annotation */ - $properties = array_column(OA\UNDEFINED !== $annotation->properties ? $annotation->properties : [], 'property'); + $properties = array_column(Generator::UNDEFINED !== $annotation->properties ? $annotation->properties : [], 'property'); static::assertContains( $property, $properties, @@ -160,7 +161,7 @@ class WebTestCase extends BaseWebTestCase public function assertNotHasProperty($property, OA\AbstractAnnotation $annotation) { /* @var OA\Schema|OA\Property|OA\Items $annotation */ - $properties = array_column(OA\UNDEFINED !== $annotation->properties ? $annotation->properties : [], 'property'); + $properties = array_column(Generator::UNDEFINED !== $annotation->properties ? $annotation->properties : [], 'property'); static::assertNotContains( $property, $properties, diff --git a/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php b/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php index 9ee7fc8..6037df9 100644 --- a/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php +++ b/Tests/ModelDescriber/Annotations/SymfonyConstraintAnnotationReaderTest.php @@ -16,6 +16,7 @@ use Nelmio\ApiDocBundle\ModelDescriber\Annotations\SymfonyConstraintAnnotationRe use Nelmio\ApiDocBundle\Tests\Helper; use Nelmio\ApiDocBundle\Tests\ModelDescriber\Annotations\Fixture as CustomAssert; use OpenApi\Annotations as OA; +use OpenApi\Generator; use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Constraints as Assert; @@ -190,7 +191,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->maxLength); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->maxLength); $this->assertSame(1, $schema->properties[0]->minLength); } @@ -226,7 +227,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->minLength); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->minLength); $this->assertSame(100, $schema->properties[0]->maxLength); } @@ -272,11 +273,11 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $this->assertSame(5, $schema->properties[0]->maximum); $this->assertTrue($schema->properties[0]->exclusiveMaximum); } else { - $this->assertSame(OA\UNDEFINED, $schema->required); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->minimum); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->exclusiveMinimum); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->maximum); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->exclusiveMaximum); + $this->assertSame(Generator::UNDEFINED, $schema->required); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->minimum); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->exclusiveMinimum); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->maximum); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->exclusiveMaximum); } } @@ -295,7 +296,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->minItems); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->minItems); $this->assertSame(10, $schema->properties[0]->maxItems); } @@ -331,7 +332,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->maxItems); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->maxItems); $this->assertSame(10, $schema->properties[0]->minItems); } @@ -367,7 +368,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->maximum); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->maximum); $this->assertSame(10, $schema->properties[0]->minimum); } @@ -403,7 +404,7 @@ class SymfonyConstraintAnnotationReaderTest extends TestCase $symfonyConstraintAnnotationReader->updateProperty(new \ReflectionProperty($entity, 'property1'), $schema->properties[0]); - $this->assertSame(OA\UNDEFINED, $schema->properties[0]->minimum); + $this->assertSame(Generator::UNDEFINED, $schema->properties[0]->minimum); $this->assertSame(10, $schema->properties[0]->maximum); } diff --git a/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php b/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php index 51df5e4..cf65b8a 100644 --- a/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php +++ b/Tests/ModelDescriber/ApplyOpenApiDiscriminatorTraitTest.php @@ -15,6 +15,7 @@ use Nelmio\ApiDocBundle\Model\Model; use Nelmio\ApiDocBundle\Model\ModelRegistry; use Nelmio\ApiDocBundle\ModelDescriber\ApplyOpenApiDiscriminatorTrait; use OpenApi\Annotations as OA; +use OpenApi\Generator; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyInfo\Type; @@ -57,7 +58,7 @@ class ApplyOpenApiDiscriminatorTraitTest extends TestCase 'two' => 'SecondType', ]); - $this->assertNotSame(OA\UNDEFINED, $this->schema->oneOf); + $this->assertNotSame(Generator::UNDEFINED, $this->schema->oneOf); $this->assertCount(2, $this->schema->oneOf); $this->assertSame( $this->modelRegistry->register($this->createModel('FirstType')), diff --git a/Tests/SwaggerPhp/UtilTest.php b/Tests/SwaggerPhp/UtilTest.php index 2b3bfc8..9255ffa 100644 --- a/Tests/SwaggerPhp/UtilTest.php +++ b/Tests/SwaggerPhp/UtilTest.php @@ -14,7 +14,7 @@ namespace Nelmio\ApiDocBundle\Tests\SwaggerPhp; use Nelmio\ApiDocBundle\OpenApiPhp\Util; use OpenApi\Annotations as OA; use OpenApi\Context; -use const OpenApi\UNDEFINED; +use OpenApi\Generator; use PHPUnit\Framework\TestCase; /** @@ -107,7 +107,7 @@ class UtilTest extends TestCase return 0 !== strpos($key, '_'); }, ARRAY_FILTER_USE_KEY); - $this->assertEquals([UNDEFINED], array_unique(array_values($properties))); + $this->assertEquals([Generator::UNDEFINED], array_unique(array_values($properties))); $this->assertIsNested($this->rootAnnotation, $info); $this->assertIsConnectedToRootContext($info); @@ -220,7 +220,7 @@ class UtilTest extends TestCase foreach ($items as $assert) { $setupCollection = empty($assert['components']) ? ($setup[$collection] ?? []) : - (OA\UNDEFINED !== $setup['components']->{$collection} ? $setup['components']->{$collection} : []); + (Generator::UNDEFINED !== $setup['components']->{$collection} ? $setup['components']->{$collection} : []); // get the indexing correct within haystack preparation $properties = array_fill(0, \count($setupCollection), null); diff --git a/Util/SetsContextTrait.php b/Util/SetsContextTrait.php new file mode 100644 index 0000000..497b5c8 --- /dev/null +++ b/Util/SetsContextTrait.php @@ -0,0 +1,22 @@ +=7.1.3", + "php": ">=7.2", "ext-json": "*", "doctrine/annotations": "^1.11", "psr/cache": "^1.0|^2.0|^3.0", @@ -30,7 +30,7 @@ "symfony/options-resolver": "^4.4|^5.0", "symfony/property-info": "^4.4|^5.0", "symfony/routing": "^4.4|^5.0", - "zircote/swagger-php": "^3.0", + "zircote/swagger-php": "^3.2|^4.0", "phpdocumentor/reflection-docblock": "^3.1|^4.4|^5.0" }, "require-dev": { From babf788636589ad75fbf44b1ce6f53996021be10 Mon Sep 17 00:00:00 2001 From: jeremy <62880356+copiri-six@users.noreply.github.com> Date: Sat, 11 Dec 2021 08:41:46 -0500 Subject: [PATCH 7/9] Proposal: Add ``header_block`` Twig Block (#1896) * Adding ``header_block`` Twig Block In attempting to remove the default green header from the API docs (I am integrating the docs into a site that already has a header), I have found that the only possible way to do so is to overwrite the entire ``index.html.twig`` file. This is because the existing ``{% header %}`` twig tags are placed *inside* the ``
`` HTML tags. So, if one overrides the ``{% header %}`` block, it only adjusts the header content, as opposed to allowing control over the entire header object. To resolve this problem I considered two methods: - Move the existing ``{% header %}`` tags _*outside*_ of the ``
`` tags. This would resolve the problem in the simplest fashion, but would be a breaking change for those currently overriding the block. - Add a new twig block, placing its tags *_outside_* the existing ``
`` tags. This would leave the existing functionality as-is, and would not break any current implementations. I chose the latter for the reasons specified above, and suggested the name ``{% header_block %}``. Thanks for considering this proposal. * Add Location of Original Twig Template This doc page advises that one can `have a look at the original template to see which blocks can be overridden`, but does not say where the original template can be found. This edit adds that information so that users don't have to resort to searching for keywords (like I just had to). Note that I haven't run any tests as this is a docs-only change. --- Resources/views/SwaggerUi/index.html.twig | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Resources/views/SwaggerUi/index.html.twig b/Resources/views/SwaggerUi/index.html.twig index a711c25..fac6ea3 100644 --- a/Resources/views/SwaggerUi/index.html.twig +++ b/Resources/views/SwaggerUi/index.html.twig @@ -51,13 +51,16 @@ file that was distributed with this source code. #} {% endblock svg_icons %} -
- {% block header %} - - {% endblock header %} -
+ + {% block header_block %} +
+ {% block header %} + + {% endblock header %} +
+ {% endblock header_block %} {% block swagger_ui %}
From 7de49bb4a8efa15ee5a98aac6237902925bdf56b Mon Sep 17 00:00:00 2001 From: Alexey Alshenetsky Date: Sun, 12 Dec 2021 03:32:51 +0300 Subject: [PATCH 8/9] Add missing null check to ControllerReflector::getReflectionMethod (#1918) * add null check https://github.com/nelmio/NelmioApiDocBundle/issues/1909 * less code is better * add tests for ControllerReflection::getReflectionMethod() * lint fix * style_ci fixes Co-authored-by: Alexey --- Tests/Util/ControllerReflectorTest.php | 29 ++++++++++++++++++++++++++ Util/ControllerReflector.php | 11 +++++----- 2 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 Tests/Util/ControllerReflectorTest.php diff --git a/Tests/Util/ControllerReflectorTest.php b/Tests/Util/ControllerReflectorTest.php new file mode 100644 index 0000000..d44b0db --- /dev/null +++ b/Tests/Util/ControllerReflectorTest.php @@ -0,0 +1,29 @@ +assertEquals( + ReflectionMethod::class, + get_class($controllerReflector->getReflectionMethod([BazingaController::class, 'userAction'])) + ); + $this->assertEquals( + ReflectionMethod::class, + get_class($controllerReflector->getReflectionMethod(BazingaController::class.'::userAction')) + ); + $this->assertNull( + $controllerReflector->getReflectionMethod('UnknownController::userAction') + ); + $this->assertNull($controllerReflector->getReflectionMethod(null)); + } +} diff --git a/Util/ControllerReflector.php b/Util/ControllerReflector.php index 49fe9d7..b085658 100644 --- a/Util/ControllerReflector.php +++ b/Util/ControllerReflector.php @@ -38,15 +38,16 @@ class ControllerReflector /** * Returns the ReflectionMethod for the given controller string. * - * @return \ReflectionMethod|null + * @return \ReflectionMethod|null */ public function getReflectionMethod($controller) { if (is_string($controller)) { $controller = $this->getClassAndMethod($controller); - if (null === $controller) { - return null; - } + } + + if (null === $controller) { + return null; } return $this->geReflectionMethodByClassNameAndMethodName(...$controller); @@ -122,7 +123,7 @@ class ControllerReflector if (!isset($class) || !isset($method)) { $this->controllers[$controller] = null; - return; + return null; } return $this->controllers[$controller] = [$class, $method]; From 736d7e8783a692d93c6fbf9dfb225dee43b2365a Mon Sep 17 00:00:00 2001 From: Titouan Galopin Date: Tue, 14 Dec 2021 17:36:17 +0100 Subject: [PATCH 9/9] Add PHP 8.1 in CI and better test supported Symfony minors --- .github/workflows/continuous-integration.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 3174fff..fb2871f 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -26,16 +26,14 @@ jobs: include: - php-version: 7.2 composer-flags: "--prefer-lowest" - - php-version: 7.2 - symfony-require: "^4.0" - php-version: 7.3 - symfony-require: "^5.0" + symfony-require: "4.4.*" - php-version: 7.4 - symfony-require: "^4.0" - - php-version: 7.3 - symfony-require: "^5.0" + symfony-require: "5.3.*" - php-version: 8.0 - composer-flags: "--ignore-platform-reqs" + symfony-require: "5.4.*" + - php-version: 8.1 + symfony-require: "5.4.*" steps: - name: "Checkout"