diff --git a/ApiDocGenerator.php b/ApiDocGenerator.php index 9c9c2b75f..2c383c90f 100644 --- a/ApiDocGenerator.php +++ b/ApiDocGenerator.php @@ -20,6 +20,7 @@ use OpenApi\Analysis; use OpenApi\Annotations\OpenApi; use OpenApi\Generator; +use OpenApi\Processors\ProcessorInterface; use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerAwareTrait; @@ -52,16 +53,20 @@ final class ApiDocGenerator */ private $openApiVersion = null; + /** @var Generator */ + private $generator; + /** * @param DescriberInterface[]|iterable $describers * @param ModelDescriberInterface[]|iterable $modelDescribers */ - public function __construct($describers, $modelDescribers, CacheItemPoolInterface $cacheItemPool = null, string $cacheItemId = null) + public function __construct($describers, $modelDescribers, CacheItemPoolInterface $cacheItemPool = null, string $cacheItemId = null, Generator $generator = null) { $this->describers = $describers; $this->modelDescribers = $modelDescribers; $this->cacheItemPool = $cacheItemPool; $this->cacheItemId = $cacheItemId; + $this->generator = $generator ?? new Generator($this->logger); } public function setAlternativeNames(array $alternativeNames) @@ -92,19 +97,13 @@ public function generate(): OpenApi } } - $generator = new Generator($this->logger); if ($this->openApiVersion) { - $generator->setVersion($this->openApiVersion); + $this->generator->setVersion($this->openApiVersion); } - // Remove OperationId processor as we use a lot of generated annotations which do not have enough information in their context - // to generate these ids properly. - // @see https://github.com/zircote/swagger-php/issues/1153 - $generator->setProcessors(array_filter($generator->getProcessors(), function ($processor) { - return !$processor instanceof \OpenApi\Processors\OperationId; - })); + $this->generator->setProcessors($this->getProcessors($this->generator)); - $context = Util::createContext(['version' => $generator->getVersion()]); + $context = Util::createContext(['version' => $this->generator->getVersion()]); $this->openApi = new OpenApi(['_context' => $context]); $modelRegistry = new ModelRegistry($this->modelDescribers, $this->openApi, $this->alternativeNames); @@ -129,7 +128,7 @@ public function generate(): OpenApi // Calculate the associated schemas $modelRegistry->registerSchemas(); - $analysis->process($generator->getProcessors()); + $analysis->process($this->generator->getProcessors()); $analysis->validate(); if (isset($item)) { @@ -138,4 +137,28 @@ public function generate(): OpenApi return $this->openApi; } + + /** + * Get an array of processors that will be used to process the OpenApi object. + * + * @param Generator $generator The generator instance to get the standard processors from + * + * @return array The array of processors + */ + private function getProcessors(Generator $generator): array + { + // Get the standard processors from the generator. + $processors = $generator->getProcessors(); + + // Remove OperationId processor as we use a lot of generated annotations which do not have enough information in their context + // to generate these ids properly. + // @see \Nelmio\ApiDocBundle\OpenApiPhp\Util::createContext + foreach ($processors as $key => $processor) { + if ($processor instanceof \OpenApi\Processors\OperationId) { + unset($processors[$key]); + } + } + + return $processors; + } } diff --git a/DependencyInjection/Compiler/CustomProcessorPass.php b/DependencyInjection/Compiler/CustomProcessorPass.php new file mode 100644 index 000000000..bdf9b1a17 --- /dev/null +++ b/DependencyInjection/Compiler/CustomProcessorPass.php @@ -0,0 +1,53 @@ +findDefinition('nelmio_api_doc.open_api.generator'); + + foreach ($container->findTaggedServiceIds('nelmio_api_doc.swagger.processor') as $id => $tags) { + /** + * Before which processor should this processor be run? + * + * @var string|null + */ + $before = null; + + // See if the processor has a 'before' attribute. + foreach ($tags as $tag) { + if (isset($tag['before'])) { + $before = $tag['before']; + } + } + + $definition->addMethodCall('addProcessor', [new Reference($id), $before]); + } + } +} diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index 26d42c216..d3a300668 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -22,6 +22,7 @@ use Nelmio\ApiDocBundle\ModelDescriber\JMSModelDescriber; use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface; use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; +use OpenApi\Generator; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -68,6 +69,11 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('nelmio_api_doc.areas', array_keys($config['areas'])); $container->setParameter('nelmio_api_doc.media_types', $config['media_types']); $container->setParameter('nelmio_api_doc.use_validation_groups', $config['use_validation_groups']); + + // Register the OpenAPI Generator as a service. + $container->register('nelmio_api_doc.open_api.generator', Generator::class) + ->setPublic(false); + foreach ($config['areas'] as $area => $areaConfig) { $nameAliases = $this->findNameAliases($config['models']['names'], $area); $container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class) @@ -80,6 +86,9 @@ public function load(array $configs, ContainerBuilder $container): void ->setArguments([ new TaggedIteratorArgument(sprintf('nelmio_api_doc.describer.%s', $area)), new TaggedIteratorArgument('nelmio_api_doc.model_describer'), + null, + null, + new Reference('nelmio_api_doc.open_api.generator'), ]); $container->register(sprintf('nelmio_api_doc.describers.route.%s', $area), RouteDescriber::class) diff --git a/NelmioApiDocBundle.php b/NelmioApiDocBundle.php index 69acfa684..fad44e2c6 100644 --- a/NelmioApiDocBundle.php +++ b/NelmioApiDocBundle.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle; use Nelmio\ApiDocBundle\DependencyInjection\Compiler\ConfigurationPass; +use Nelmio\ApiDocBundle\DependencyInjection\Compiler\CustomProcessorPass; use Nelmio\ApiDocBundle\DependencyInjection\Compiler\PhpDocExtractorPass; use Nelmio\ApiDocBundle\DependencyInjection\Compiler\TagDescribersPass; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -27,5 +28,6 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new ConfigurationPass()); $container->addCompilerPass(new TagDescribersPass()); $container->addCompilerPass(new PhpDocExtractorPass()); + $container->addCompilerPass(new CustomProcessorPass()); } } diff --git a/Tests/ApiDocGeneratorTest.php b/Tests/ApiDocGeneratorTest.php index ec51d4ca0..36853fadb 100644 --- a/Tests/ApiDocGeneratorTest.php +++ b/Tests/ApiDocGeneratorTest.php @@ -13,6 +13,7 @@ use Nelmio\ApiDocBundle\ApiDocGenerator; use Nelmio\ApiDocBundle\Describer\DefaultDescriber; +use OpenApi\Generator; use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\ArrayAdapter; @@ -21,7 +22,7 @@ class ApiDocGeneratorTest extends TestCase public function testCache() { $adapter = new ArrayAdapter(); - $generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter); + $generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter, null, new Generator()); $this->assertEquals(json_encode($generator->generate()), json_encode($adapter->getItem('openapi_doc')->get())); } @@ -29,7 +30,7 @@ public function testCache() public function testCacheWithCustomId() { $adapter = new ArrayAdapter(); - $generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter, 'custom_id'); + $generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter, 'custom_id', new Generator()); $this->assertEquals(json_encode($generator->generate()), json_encode($adapter->getItem('custom_id')->get())); }