Skip to content

Commit

Permalink
Allow custom processors through tagged services (#2149)
Browse files Browse the repository at this point in the history
* POC for custom processors

* Theoretically, there could be more than one ApiDocGenerator

* Register the Generator as a dependency

* Register the Generator as a proper service

* Register the "Before" attribute in the custom processor pass

* Update Tests

* Code Style enhancements.

* ID has to be a reference

* Prefix the service tag for better namespacing

* Make the generator optional...

* Move the generator for BC

Co-authored-by: Djordy Koert <djordy.koert@live.nl>

* remove spaces

---------

Co-authored-by: Djordy Koert <djordy.koert@live.nl>
Co-authored-by: Djordy Koert <djordy.koert@gmail.com>
  • Loading branch information
3 people committed Jan 2, 2024
1 parent 5130757 commit aa162b7
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 13 deletions.
45 changes: 34 additions & 11 deletions ApiDocGenerator.php
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
@@ -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
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
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
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

0 comments on commit aa162b7

Please sign in to comment.