mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-09 19:19:29 +03:00
Implement alternative naming system via configuration (#1312)
* implement alternative naming system via configuration * use strict comparison * test di configs * rever * test naming aliases are applied * set "default" as default area * test names are passed to generators * cs formatting * added extra check for built-int types * cs * added documentation about alternative names * allow to create the same alias in two different areas * document and test better aliasing strategy * specify that the last matching rule is used * Make last matching rule wins * Fix documentation
This commit is contained in:
parent
cf0857af64
commit
ab005c4129
@ -28,6 +28,8 @@ final class ApiDocGenerator
|
|||||||
|
|
||||||
private $cacheItemPool;
|
private $cacheItemPool;
|
||||||
|
|
||||||
|
private $alternativeNames = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param DescriberInterface[]|iterable $describers
|
* @param DescriberInterface[]|iterable $describers
|
||||||
* @param ModelDescriberInterface[]|iterable $modelDescribers
|
* @param ModelDescriberInterface[]|iterable $modelDescribers
|
||||||
@ -40,6 +42,11 @@ final class ApiDocGenerator
|
|||||||
$this->cacheItemId = $cacheItemId;
|
$this->cacheItemId = $cacheItemId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setAlternativeNames(array $alternativeNames)
|
||||||
|
{
|
||||||
|
$this->alternativeNames = $alternativeNames;
|
||||||
|
}
|
||||||
|
|
||||||
public function generate(): Swagger
|
public function generate(): Swagger
|
||||||
{
|
{
|
||||||
if (null !== $this->swagger) {
|
if (null !== $this->swagger) {
|
||||||
@ -54,7 +61,7 @@ final class ApiDocGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->swagger = new Swagger();
|
$this->swagger = new Swagger();
|
||||||
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->swagger);
|
$modelRegistry = new ModelRegistry($this->modelDescribers, $this->swagger, $this->alternativeNames);
|
||||||
foreach ($this->describers as $describer) {
|
foreach ($this->describers as $describer) {
|
||||||
if ($describer instanceof ModelRegistryAwareInterface) {
|
if ($describer instanceof ModelRegistryAwareInterface) {
|
||||||
$describer->setModelRegistry($modelRegistry);
|
$describer->setModelRegistry($modelRegistry);
|
||||||
|
@ -85,6 +85,23 @@ final class Configuration implements ConfigurationInterface
|
|||||||
->children()
|
->children()
|
||||||
->booleanNode('use_jms')->defaultFalse()->end()
|
->booleanNode('use_jms')->defaultFalse()->end()
|
||||||
->end()
|
->end()
|
||||||
|
->children()
|
||||||
|
->arrayNode('names')
|
||||||
|
->prototype('array')
|
||||||
|
->children()
|
||||||
|
->scalarNode('alias')->isRequired()->end()
|
||||||
|
->scalarNode('type')->isRequired()->end()
|
||||||
|
->arrayNode('groups')
|
||||||
|
->defaultValue([])
|
||||||
|
->prototype('scalar')->end()
|
||||||
|
->end()
|
||||||
|
->arrayNode('areas')
|
||||||
|
->defaultValue([])
|
||||||
|
->prototype('scalar')->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end();
|
->end();
|
||||||
|
|
||||||
|
@ -61,8 +61,11 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
|
|||||||
|
|
||||||
$container->setParameter('nelmio_api_doc.areas', array_keys($config['areas']));
|
$container->setParameter('nelmio_api_doc.areas', array_keys($config['areas']));
|
||||||
foreach ($config['areas'] as $area => $areaConfig) {
|
foreach ($config['areas'] as $area => $areaConfig) {
|
||||||
|
$nameAliases = $this->findNameAliases($config['models']['names'], $area);
|
||||||
|
|
||||||
$container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class)
|
$container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class)
|
||||||
->setPublic(false)
|
->setPublic(false)
|
||||||
|
->addMethodCall('setAlternativeNames', [$nameAliases])
|
||||||
->setArguments([
|
->setArguments([
|
||||||
new TaggedIteratorArgument(sprintf('nelmio_api_doc.describer.%s', $area)),
|
new TaggedIteratorArgument(sprintf('nelmio_api_doc.describer.%s', $area)),
|
||||||
new TaggedIteratorArgument('nelmio_api_doc.model_describer'),
|
new TaggedIteratorArgument('nelmio_api_doc.model_describer'),
|
||||||
@ -152,4 +155,21 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
|
|||||||
// Import the base configuration
|
// Import the base configuration
|
||||||
$container->getDefinition('nelmio_api_doc.describers.config')->replaceArgument(0, $config['documentation']);
|
$container->getDefinition('nelmio_api_doc.describers.config')->replaceArgument(0, $config['documentation']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function findNameAliases(array $names, string $area): array
|
||||||
|
{
|
||||||
|
$nameAliases = array_filter($names, function (array $aliasInfo) use ($area) {
|
||||||
|
return empty($aliasInfo['areas']) || in_array($area, $aliasInfo['areas'], true);
|
||||||
|
});
|
||||||
|
|
||||||
|
$aliases = [];
|
||||||
|
foreach ($nameAliases as $nameAlias) {
|
||||||
|
$aliases[$nameAlias['alias']] = [
|
||||||
|
'type' => $nameAlias['type'],
|
||||||
|
'groups' => $nameAlias['groups'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $aliases;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ use Symfony\Component\PropertyInfo\Type;
|
|||||||
|
|
||||||
final class ModelRegistry
|
final class ModelRegistry
|
||||||
{
|
{
|
||||||
|
private $alternativeNames = [];
|
||||||
|
|
||||||
private $unregistered = [];
|
private $unregistered = [];
|
||||||
|
|
||||||
private $models = [];
|
private $models = [];
|
||||||
@ -34,10 +36,11 @@ final class ModelRegistry
|
|||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public function __construct($modelDescribers, Swagger $api)
|
public function __construct($modelDescribers, Swagger $api, array $alternativeNames = [])
|
||||||
{
|
{
|
||||||
$this->modelDescribers = $modelDescribers;
|
$this->modelDescribers = $modelDescribers;
|
||||||
$this->api = $api;
|
$this->api = $api;
|
||||||
|
$this->alternativeNames = array_reverse($alternativeNames); // last rule wins
|
||||||
}
|
}
|
||||||
|
|
||||||
public function register(Model $model): string
|
public function register(Model $model): string
|
||||||
@ -95,7 +98,9 @@ final class ModelRegistry
|
|||||||
private function generateModelName(Model $model): string
|
private function generateModelName(Model $model): string
|
||||||
{
|
{
|
||||||
$definitions = $this->api->getDefinitions();
|
$definitions = $this->api->getDefinitions();
|
||||||
$base = $name = $this->getTypeShortName($model->getType());
|
|
||||||
|
$name = $base = $this->getAlternativeName($model) ?? $this->getTypeShortName($model->getType());
|
||||||
|
|
||||||
$i = 1;
|
$i = 1;
|
||||||
while ($definitions->has($name)) {
|
while ($definitions->has($name)) {
|
||||||
++$i;
|
++$i;
|
||||||
@ -105,6 +110,27 @@ final class ModelRegistry
|
|||||||
return $name;
|
return $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Model $model
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function getAlternativeName(Model $model)
|
||||||
|
{
|
||||||
|
$type = $model->getType();
|
||||||
|
foreach ($this->alternativeNames as $alternativeName => $criteria) {
|
||||||
|
if (
|
||||||
|
Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() &&
|
||||||
|
$type->getClassName() === $criteria['type'] &&
|
||||||
|
$criteria['groups'] == $model->getGroups()
|
||||||
|
) {
|
||||||
|
return $alternativeName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private function getTypeShortName(Type $type): string
|
private function getTypeShortName(Type $type): string
|
||||||
{
|
{
|
||||||
if (null !== $type->getCollectionValueType()) {
|
if (null !== $type->getCollectionValueType()) {
|
||||||
|
28
Resources/doc/alternative_names.rst
Normal file
28
Resources/doc/alternative_names.rst
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
Alternative Names
|
||||||
|
=================
|
||||||
|
|
||||||
|
NelmioApiDoc generates automatically the model names but the ``nelmio_api_doc.models.names`` option allows to
|
||||||
|
customize the names for some models.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can define alternative names for each group and area combination, the last matching rule is used:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
nelmio_api_doc:
|
||||||
|
models:
|
||||||
|
names:
|
||||||
|
- { alias: MainUser, type: App\Entity\User}
|
||||||
|
- { alias: MainUser_light, type: App\Entity\User, groups: [light] }
|
||||||
|
- { alias: MainUser_secret, type: App\Entity\User, areas: [private] }
|
||||||
|
- { alias: MainUser, type: App\Entity\User, groups: [standard], areas: [private] }
|
||||||
|
|
||||||
|
|
||||||
|
In this case the class ``App\Entity\User`` will be aliased into:
|
||||||
|
|
||||||
|
- ``MainUser`` when no more detailed rules are specified
|
||||||
|
- ``MainUser_light`` when the group is equal to ``light``
|
||||||
|
- ``MainUser_secret`` for the ``private`` area
|
||||||
|
- ``MainUser`` for the ``private`` area when the group is equal to ``standard``
|
@ -302,6 +302,7 @@ If you need more complex features, take a look at:
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
areas
|
areas
|
||||||
|
alternative_names
|
||||||
faq
|
faq
|
||||||
|
|
||||||
.. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html
|
.. _`Symfony PropertyInfo component`: https://symfony.com/doc/current/components/property_info.html
|
||||||
|
@ -37,6 +37,74 @@ class ConfigurationTest extends TestCase
|
|||||||
$this->assertSame($areas, $config['areas']);
|
$this->assertSame($areas, $config['areas']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAlternativeNames()
|
||||||
|
{
|
||||||
|
$processor = new Processor();
|
||||||
|
$config = $processor->processConfiguration(new Configuration(), [[
|
||||||
|
'models' => [
|
||||||
|
'names' => [
|
||||||
|
[
|
||||||
|
'alias' => 'Foo1',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
'groups' => ['group'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo2',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
'groups' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo3',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo4',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
'groups' => ['group'],
|
||||||
|
'areas' => ['internal'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo1',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
'areas' => ['internal'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]]);
|
||||||
|
$this->assertEquals([
|
||||||
|
[
|
||||||
|
'alias' => 'Foo1',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
'groups' => ['group'],
|
||||||
|
'areas' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo2',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
'groups' => [],
|
||||||
|
'areas' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo3',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
'groups' => [],
|
||||||
|
'areas' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo4',
|
||||||
|
'type' => 'App\\Foo',
|
||||||
|
'groups' => ['group'],
|
||||||
|
'areas' => ['internal'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'alias' => 'Foo1',
|
||||||
|
'type' => 'App\\Foo',
|
||||||
|
'groups' => [],
|
||||||
|
'areas' => ['internal'],
|
||||||
|
],
|
||||||
|
], $config['models']['names']);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @group legacy
|
* @group legacy
|
||||||
* @expectedException \InvalidArgumentException
|
* @expectedException \InvalidArgumentException
|
||||||
|
@ -17,6 +17,74 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|||||||
|
|
||||||
class NelmioApiDocExtensionTest extends TestCase
|
class NelmioApiDocExtensionTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function testNameAliasesArePassedToModelRegistry()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->setParameter('kernel.bundles', []);
|
||||||
|
$extension = new NelmioApiDocExtension();
|
||||||
|
$extension->load([[
|
||||||
|
'areas' => [
|
||||||
|
'default' => ['path_patterns' => ['/foo'], 'host_patterns' => []],
|
||||||
|
'commercial' => ['path_patterns' => ['/internal'], 'host_patterns' => []],
|
||||||
|
],
|
||||||
|
'models' => [
|
||||||
|
'names' => [
|
||||||
|
[ // Test1 alias for all the areas
|
||||||
|
'alias' => 'Test1',
|
||||||
|
'type' => 'App\Test',
|
||||||
|
],
|
||||||
|
[ // Foo1 alias for all the areas
|
||||||
|
'alias' => 'Foo1',
|
||||||
|
'type' => 'App\Foo',
|
||||||
|
],
|
||||||
|
[ // overwrite Foo1 alias for all the commercial area
|
||||||
|
'alias' => 'Foo1',
|
||||||
|
'type' => 'App\Bar',
|
||||||
|
'areas' => ['commercial'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]], $container);
|
||||||
|
|
||||||
|
$methodCalls = $container->getDefinition('nelmio_api_doc.generator.default')->getMethodCalls();
|
||||||
|
$foundMethodCall = false;
|
||||||
|
foreach ($methodCalls as $methodCall) {
|
||||||
|
if ('setAlternativeNames' === $methodCall[0]) {
|
||||||
|
$this->assertEquals([
|
||||||
|
'Foo1' => [
|
||||||
|
'type' => 'App\\Foo',
|
||||||
|
'groups' => [],
|
||||||
|
],
|
||||||
|
'Test1' => [
|
||||||
|
'type' => 'App\\Test',
|
||||||
|
'groups' => [],
|
||||||
|
],
|
||||||
|
], $methodCall[1][0]);
|
||||||
|
$foundMethodCall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertTrue($foundMethodCall);
|
||||||
|
|
||||||
|
$methodCalls = $container->getDefinition('nelmio_api_doc.generator.commercial')->getMethodCalls();
|
||||||
|
$foundMethodCall = false;
|
||||||
|
foreach ($methodCalls as $methodCall) {
|
||||||
|
if ('setAlternativeNames' === $methodCall[0]) {
|
||||||
|
$this->assertEquals([
|
||||||
|
'Foo1' => [
|
||||||
|
'type' => 'App\\Bar',
|
||||||
|
'groups' => [],
|
||||||
|
],
|
||||||
|
'Test1' => [
|
||||||
|
'type' => 'App\\Test',
|
||||||
|
'groups' => [],
|
||||||
|
],
|
||||||
|
], $methodCall[1][0]);
|
||||||
|
$foundMethodCall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertTrue($foundMethodCall);
|
||||||
|
}
|
||||||
|
|
||||||
public function testMergesRootKeysFromMultipleConfigurations()
|
public function testMergesRootKeysFromMultipleConfigurations()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
namespace Nelmio\ApiDocBundle\Tests\Model;
|
namespace Nelmio\ApiDocBundle\Tests\Model;
|
||||||
|
|
||||||
use EXSyst\Component\Swagger\Schema;
|
|
||||||
use EXSyst\Component\Swagger\Swagger;
|
use EXSyst\Component\Swagger\Swagger;
|
||||||
use Nelmio\ApiDocBundle\Model\Model;
|
use Nelmio\ApiDocBundle\Model\Model;
|
||||||
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
use Nelmio\ApiDocBundle\Model\ModelRegistry;
|
||||||
@ -20,6 +19,79 @@ use Symfony\Component\PropertyInfo\Type;
|
|||||||
|
|
||||||
class ModelRegistryTest extends TestCase
|
class ModelRegistryTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function testNameAliasingNotAppliedForCollections()
|
||||||
|
{
|
||||||
|
$alternativeNames = [
|
||||||
|
'Foo1' => [
|
||||||
|
'type' => self::class,
|
||||||
|
'groups' => ['group1'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$registry = new ModelRegistry([], new Swagger(), $alternativeNames);
|
||||||
|
$type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true);
|
||||||
|
|
||||||
|
$this->assertEquals('#/definitions/array', $registry->register(new Model($type, ['group1'])));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getNameAlternatives
|
||||||
|
*
|
||||||
|
* @param $expected
|
||||||
|
*/
|
||||||
|
public function testNameAliasingForObjects(string $expected, $groups, array $alternativeNames)
|
||||||
|
{
|
||||||
|
$registry = new ModelRegistry([], new Swagger(), $alternativeNames);
|
||||||
|
$type = new Type(Type::BUILTIN_TYPE_OBJECT, false, self::class);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $registry->register(new Model($type, $groups)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNameAlternatives()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'#/definitions/ModelRegistryTest',
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'Foo1' => [
|
||||||
|
'type' => self::class,
|
||||||
|
'groups' => ['group1'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#/definitions/Foo1',
|
||||||
|
['group1'],
|
||||||
|
[
|
||||||
|
'Foo1' => [
|
||||||
|
'type' => self::class,
|
||||||
|
'groups' => ['group1'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#/definitions/Foo1',
|
||||||
|
['group1', 'group2'],
|
||||||
|
[
|
||||||
|
'Foo1' => [
|
||||||
|
'type' => self::class,
|
||||||
|
'groups' => ['group1', 'group2'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#/definitions/Foo1',
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'Foo1' => [
|
||||||
|
'type' => self::class,
|
||||||
|
'groups' => [],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider unsupportedTypesProvider
|
* @dataProvider unsupportedTypesProvider
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user