Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom processors through tagged services #2149

Merged
merged 14 commits into from
Jan 2, 2024
Merged
45 changes: 34 additions & 11 deletions ApiDocGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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)) {
Expand All @@ -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<ProcessorInterface|callable> 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;
}
}
53 changes: 53 additions & 0 deletions DependencyInjection/Compiler/CustomProcessorPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
* Compiler Pass to identify and register custom processors.
* *
* @internal
*/
final class CustomProcessorPass implements CompilerPassInterface
{
/**
* Process services tagged as 'swagger.processor'.
*
* @param ContainerBuilder $container The container builder
*/
public function process(ContainerBuilder $container): void
{
// Find the OpenAPI generator service.
$definition = $container->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]);
}
}
}
9 changes: 9 additions & 0 deletions DependencyInjection/NelmioApiDocExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions NelmioApiDocBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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());
}
}
5 changes: 3 additions & 2 deletions Tests/ApiDocGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -21,15 +22,15 @@ 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()));
}

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()));
}
Expand Down