Add areas support (#1169)

* Add areas support

* Document the Areas feature

* Allow to expose swagger area as JSON

* Fixes

* last fixes
This commit is contained in:
Guilhem N 2018-01-05 13:08:02 +01:00 committed by GitHub
parent 92f3eb633b
commit 393a6c061e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 447 additions and 153 deletions

View File

@ -7,3 +7,5 @@ enabled:
disabled: disabled:
- unalign_equals - unalign_equals
- braces
- property_separation

View File

@ -32,11 +32,12 @@ final class ApiDocGenerator
* @param DescriberInterface[]|iterable $describers * @param DescriberInterface[]|iterable $describers
* @param ModelDescriberInterface[]|iterable $modelDescribers * @param ModelDescriberInterface[]|iterable $modelDescribers
*/ */
public function __construct($describers, $modelDescribers, CacheItemPoolInterface $cacheItemPool = null) public function __construct($describers, $modelDescribers, CacheItemPoolInterface $cacheItemPool = null, string $cacheItemId = null)
{ {
$this->describers = $describers; $this->describers = $describers;
$this->modelDescribers = $modelDescribers; $this->modelDescribers = $modelDescribers;
$this->cacheItemPool = $cacheItemPool; $this->cacheItemPool = $cacheItemPool;
$this->cacheItemId = $cacheItemId;
} }
public function generate(): Swagger public function generate(): Swagger
@ -46,7 +47,7 @@ final class ApiDocGenerator
} }
if ($this->cacheItemPool) { if ($this->cacheItemPool) {
$item = $this->cacheItemPool->getItem('swagger_doc'); $item = $this->cacheItemPool->getItem($this->cacheItemId ?? 'swagger_doc');
if ($item->isHit()) { if ($item->isHit()) {
return $this->swagger = $item->get(); return $this->swagger = $item->get();
} }

View File

@ -15,6 +15,21 @@ JMS Serializer
SwaggerPHP SwaggerPHP
* Handle `enum` and `default` properties from SwaggerPHP annotation * Handle `enum` and `default` properties from SwaggerPHP annotation
Config
* `nelmio_api_doc.routes` has been replaced by `nelmio_api_doc.areas`. Please update your config accordingly.
Before:
```yml
nelmio_api_doc:
routes: [ path_patterns: [ /api ] ]
```
After:
```yml
nelmio_api_doc:
areas: [ path_patterns: [ /api ] ]
```
3.0.0 (2017-12-10) 3.0.0 (2017-12-10)
------------------ ------------------

View File

@ -12,21 +12,42 @@
namespace Nelmio\ApiDocBundle\Controller; namespace Nelmio\ApiDocBundle\Controller;
use Nelmio\ApiDocBundle\ApiDocGenerator; use Nelmio\ApiDocBundle\ApiDocGenerator;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final class DocumentationController final class DocumentationController
{ {
private $apiDocGenerator; private $generatorLocator;
public function __construct(ApiDocGenerator $apiDocGenerator) /**
* @param ContainerInterface $generatorLocator
*/
public function __construct($generatorLocator)
{ {
$this->apiDocGenerator = $apiDocGenerator; if (!$generatorLocator instanceof ContainerInterface) {
if (!$generatorLocator instanceof ApiDocGenerator) {
throw new \InvalidArgumentException(sprintf('Providing an instance of "%s" to "%s" is not supported.', get_class($generatorLocator), __METHOD__));
}
@trigger_error(sprintf('Providing an instance of "%s" to "%s()" is deprecated since version 3.1. Provide it an instance of "%s" instead.', ApiDocGenerator::class, __METHOD__, ContainerInterface::class), E_USER_DEPRECATED);
$generatorLocator = new ServiceLocator(['default' => function () use ($generatorLocator): ApiDocGenerator {
return $generatorLocator;
}]);
}
$this->generatorLocator = $generatorLocator;
} }
public function __invoke(Request $request) public function __invoke(Request $request, $area = 'default')
{ {
$spec = $this->apiDocGenerator->generate()->toArray(); if (!$this->generatorLocator->has($area)) {
throw new BadRequestHttpException(sprintf('Area "%s" is not supported.', $area));
}
$spec = $this->generatorLocator->get($area)->generate()->toArray();
if ('' !== $request->getBaseUrl()) { if ('' !== $request->getBaseUrl()) {
$spec['basePath'] = $request->getBaseUrl(); $spec['basePath'] = $request->getBaseUrl();
} }

View File

@ -12,24 +12,45 @@
namespace Nelmio\ApiDocBundle\Controller; namespace Nelmio\ApiDocBundle\Controller;
use Nelmio\ApiDocBundle\ApiDocGenerator; use Nelmio\ApiDocBundle\ApiDocGenerator;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
final class SwaggerUiController final class SwaggerUiController
{ {
private $apiDocGenerator; private $generatorLocator;
private $twig; private $twig;
public function __construct(ApiDocGenerator $apiDocGenerator, \Twig_Environment $twig) /**
* @param ContainerInterface $generatorLocator
*/
public function __construct($generatorLocator, \Twig_Environment $twig)
{ {
$this->apiDocGenerator = $apiDocGenerator; if (!$generatorLocator instanceof ContainerInterface) {
if (!$generatorLocator instanceof ApiDocGenerator) {
throw new \InvalidArgumentException(sprintf('Providing an instance of "%s" to "%s" is not supported.', get_class($generatorLocator), __METHOD__));
}
@trigger_error(sprintf('Providing an instance of "%s" to "%s()" is deprecated since version 3.1. Provide it an instance of "%s" instead.', ApiDocGenerator::class, __METHOD__, ContainerInterface::class), E_USER_DEPRECATED);
$generatorLocator = new ServiceLocator(['default' => function () use ($generatorLocator): ApiDocGenerator {
return $generatorLocator;
}]);
}
$this->generatorLocator = $generatorLocator;
$this->twig = $twig; $this->twig = $twig;
} }
public function __invoke(Request $request) public function __invoke(Request $request, $area = 'default')
{ {
$spec = $this->apiDocGenerator->generate()->toArray(); if (!$this->generatorLocator->has($area)) {
throw new BadRequestHttpException(sprintf('Area "%s" is not supported.', $area));
}
$spec = $this->generatorLocator->get($area)->generate()->toArray();
if ('' !== $request->getBaseUrl()) { if ('' !== $request->getBaseUrl()) {
$spec['basePath'] = $request->getBaseUrl(); $spec['basePath'] = $request->getBaseUrl();
} }

View File

@ -1,31 +0,0 @@
<?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\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @internal
*/
final class AddModelDescribersPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container)
{
$modelDescribers = $this->findAndSortTaggedServices('nelmio_api_doc.model_describer', $container);
$container->getDefinition('nelmio_api_doc.generator')->replaceArgument(1, $modelDescribers);
}
}

View File

@ -1,31 +0,0 @@
<?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\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @internal
*/
final class AddRouteDescribersPass implements CompilerPassInterface
{
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container)
{
$routeDescribers = $this->findAndSortTaggedServices('nelmio_api_doc.route_describer', $container);
$container->getDefinition('nelmio_api_doc.describers.route')->replaceArgument(2, $routeDescribers);
}
}

View File

@ -12,20 +12,22 @@
namespace Nelmio\ApiDocBundle\DependencyInjection\Compiler; namespace Nelmio\ApiDocBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
/** /**
* @internal * @internal
*/ */
final class AddDescribersPass implements CompilerPassInterface final class TagDescribersPass implements CompilerPassInterface
{ {
use PriorityTaggedServiceTrait;
public function process(ContainerBuilder $container) public function process(ContainerBuilder $container)
{ {
$describers = $this->findAndSortTaggedServices('nelmio_api_doc.describer', $container); foreach ($container->findTaggedServiceIds('nelmio_api_doc.describer') as $id => $tags) {
$describer = $container->getDefinition($id);
$container->getDefinition('nelmio_api_doc.generator')->replaceArgument(0, $describers); foreach ($container->getParameter('nelmio_api_doc.areas') as $area) {
foreach ($tags as $tag) {
$describer->addTag(sprintf('nelmio_api_doc.describer.%s', $area), $tag);
}
}
}
} }
} }

View File

@ -21,6 +21,24 @@ final class Configuration implements ConfigurationInterface
$treeBuilder = new TreeBuilder(); $treeBuilder = new TreeBuilder();
$treeBuilder $treeBuilder
->root('nelmio_api_doc') ->root('nelmio_api_doc')
->beforeNormalization()
->ifTrue(function ($v) {
return !isset($v['areas']) && isset($v['routes']);
})
->then(function ($v) {
$v['areas'] = $v['routes'];
unset($v['routes']);
@trigger_error('The `nelmio_api_doc.routes` config option is deprecated. Please use `nelmio_api_doc.areas` instead (just replace `routes` by `areas` in your config).', E_USER_DEPRECATED);
return $v;
})
->end()
->beforeNormalization()
->ifTrue(function ($v) {
return isset($v['routes']);
})
->thenInvalid('You must not use both `nelmio_api_doc.areas` and `nelmio_api_doc.routes` config options. Please update your config to only use `nelmio_api_doc.areas`.')
->end()
->children() ->children()
->arrayNode('documentation') ->arrayNode('documentation')
->useAttributeAsKey('key') ->useAttributeAsKey('key')
@ -28,13 +46,30 @@ final class Configuration implements ConfigurationInterface
->example(['info' => ['title' => 'My App']]) ->example(['info' => ['title' => 'My App']])
->prototype('variable')->end() ->prototype('variable')->end()
->end() ->end()
->arrayNode('routes') ->arrayNode('areas')
->info('Filter the routes that are documented') ->info('Filter the routes that are documented')
->addDefaultsIfNotSet() ->beforeNormalization()
->children() ->ifTrue(function ($v) {
->arrayNode('path_patterns') return empty($v) or isset($v['path_patterns']);
->example(['^/api', '^/api(?!/admin)']) })
->prototype('scalar')->end() ->then(function ($v) {
return ['default' => $v];
})
->end()
->validate()
->ifTrue(function ($v) {
return !isset($v['default']);
})
->thenInvalid('You must specify a `default` area under `nelmio_api_doc.areas`.')
->end()
->useAttributeAsKey('name')
->prototype('array')
->addDefaultsIfNotSet()
->children()
->arrayNode('path_patterns')
->example(['^/api', '^/api(?!/admin)'])
->prototype('scalar')->end()
->end()
->end() ->end()
->end() ->end()
->end() ->end()

View File

@ -12,10 +12,14 @@
namespace Nelmio\ApiDocBundle\DependencyInjection; namespace Nelmio\ApiDocBundle\DependencyInjection;
use FOS\RestBundle\Controller\Annotations\ParamInterface; use FOS\RestBundle\Controller\Annotations\ParamInterface;
use Nelmio\ApiDocBundle\ApiDocGenerator;
use Nelmio\ApiDocBundle\Describer\RouteDescriber;
use Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber;
use Nelmio\ApiDocBundle\ModelDescriber\JMSModelDescriber; use Nelmio\ApiDocBundle\ModelDescriber\JMSModelDescriber;
use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder; use Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder;
use phpDocumentor\Reflection\DocBlockFactory; use phpDocumentor\Reflection\DocBlockFactory;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
@ -54,22 +58,57 @@ final class NelmioApiDocExtension extends Extension implements PrependExtensionI
$routesDefinition = (new Definition(RouteCollection::class)) $routesDefinition = (new Definition(RouteCollection::class))
->setFactory([new Reference('router'), 'getRouteCollection']); ->setFactory([new Reference('router'), 'getRouteCollection']);
if (0 === count($config['routes']['path_patterns'])) { $container->setParameter('nelmio_api_doc.areas', array_keys($config['areas']));
$container->setDefinition('nelmio_api_doc.routes', $routesDefinition) foreach ($config['areas'] as $area => $areaConfig) {
->setPublic(false); $container->register(sprintf('nelmio_api_doc.generator.%s', $area), ApiDocGenerator::class)
} else {
$container->register('nelmio_api_doc.routes', RouteCollection::class)
->setPublic(false) ->setPublic(false)
->setFactory([ ->setArguments([
(new Definition(FilteredRouteCollectionBuilder::class)) new TaggedIteratorArgument(sprintf('nelmio_api_doc.describer.%s', $area)),
->addArgument($config['routes']['path_patterns']), new TaggedIteratorArgument('nelmio_api_doc.model_describer'),
'filter', ]);
if (0 === count($areaConfig['path_patterns'])) {
$container->setDefinition(sprintf('nelmio_api_doc.routes.%s', $area), $routesDefinition)
->setPublic(false);
} else {
$container->register(sprintf('nelmio_api_doc.routes.%s', $area), RouteCollection::class)
->setPublic(false)
->setFactory([
(new Definition(FilteredRouteCollectionBuilder::class))
->addArgument($areaConfig['path_patterns']),
'filter',
])
->addArgument($routesDefinition);
}
$container->register(sprintf('nelmio_api_doc.describers.route.%s', $area), RouteDescriber::class)
->setPublic(false)
->setArguments([
new Reference(sprintf('nelmio_api_doc.routes.%s', $area)),
new Reference('nelmio_api_doc.controller_reflector'),
new TaggedIteratorArgument('nelmio_api_doc.route_describer'),
]) ])
->addArgument($routesDefinition); ->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -400]);
$container->register(sprintf('nelmio_api_doc.describers.swagger_php.%s', $area), SwaggerPhpDescriber::class)
->setPublic(false)
->setArguments([
new Reference(sprintf('nelmio_api_doc.routes.%s', $area)),
new Reference('nelmio_api_doc.controller_reflector'),
new Reference('annotation_reader'),
])
->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -200]);
} }
$container->register('nelmio_api_doc.generator_locator')
->setPublic(false)
->addTag('container.service_locator')
->addArgument(array_combine(
array_keys($config['areas']),
array_map(function ($area) { return new Reference(sprintf('nelmio_api_doc.generator.%s', $area)); }, array_keys($config['areas']))
));
// Import services needed for each library // Import services needed for each library
$loader->load('swagger_php.xml');
if (class_exists(DocBlockFactory::class)) { if (class_exists(DocBlockFactory::class)) {
$loader->load('php_doc.xml'); $loader->load('php_doc.xml');
} }

View File

@ -11,10 +11,8 @@
namespace Nelmio\ApiDocBundle; namespace Nelmio\ApiDocBundle;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddDescribersPass;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddModelDescribersPass;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddRouteDescribersPass;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\ConfigurationPass; use Nelmio\ApiDocBundle\DependencyInjection\Compiler\ConfigurationPass;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\TagDescribersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
@ -26,8 +24,6 @@ final class NelmioApiDocBundle extends Bundle
public function build(ContainerBuilder $container) public function build(ContainerBuilder $container)
{ {
$container->addCompilerPass(new ConfigurationPass()); $container->addCompilerPass(new ConfigurationPass());
$container->addCompilerPass(new AddDescribersPass()); $container->addCompilerPass(new TagDescribersPass());
$container->addCompilerPass(new AddModelDescribersPass());
$container->addCompilerPass(new AddRouteDescribersPass());
} }
} }

View File

@ -6,19 +6,16 @@
<services> <services>
<!-- Controllers --> <!-- Controllers -->
<service id="nelmio_api_doc.controller.swagger_ui" class="Nelmio\ApiDocBundle\Controller\SwaggerUiController" public="true"> <service id="nelmio_api_doc.controller.swagger_ui" class="Nelmio\ApiDocBundle\Controller\SwaggerUiController" public="true">
<argument type="service" id="nelmio_api_doc.generator" /> <argument type="service" id="nelmio_api_doc.generator_locator" />
<argument type="service" id="twig" /> <argument type="service" id="twig" />
</service> </service>
<service id="nelmio_api_doc.controller.swagger" class="Nelmio\ApiDocBundle\Controller\DocumentationController" public="true"> <service id="nelmio_api_doc.controller.swagger" class="Nelmio\ApiDocBundle\Controller\DocumentationController" public="true">
<argument type="service" id="nelmio_api_doc.generator" /> <argument type="service" id="nelmio_api_doc.generator_locator" />
</service> </service>
<!-- Swagger Spec Generator --> <!-- Swagger Spec Generator -->
<service id="nelmio_api_doc.generator" class="Nelmio\ApiDocBundle\ApiDocGenerator" public="true"> <service id="nelmio_api_doc.generator" alias="nelmio_api_doc.generator.default" />
<argument type="collection" /> <!-- Describers -->
<argument type="collection" /> <!-- Model Describers -->
</service>
<service id="nelmio_api_doc.controller_reflector" class="Nelmio\ApiDocBundle\Util\ControllerReflector" public="false"> <service id="nelmio_api_doc.controller_reflector" class="Nelmio\ApiDocBundle\Util\ControllerReflector" public="false">
<argument type="service" id="service_container" /> <argument type="service" id="service_container" />
@ -32,14 +29,6 @@
<tag name="nelmio_api_doc.describer" priority="1000" /> <tag name="nelmio_api_doc.describer" priority="1000" />
</service> </service>
<service id="nelmio_api_doc.describers.route" class="Nelmio\ApiDocBundle\Describer\RouteDescriber" public="false">
<argument type="service" id="nelmio_api_doc.routes" />
<argument type="service" id="nelmio_api_doc.controller_reflector" />
<argument type="collection" />
<tag name="nelmio_api_doc.describer" priority="-400" />
</service>
<service id="nelmio_api_doc.describers.default" class="Nelmio\ApiDocBundle\Describer\DefaultDescriber" public="false"> <service id="nelmio_api_doc.describers.default" class="Nelmio\ApiDocBundle\Describer\DefaultDescriber" public="false">
<tag name="nelmio_api_doc.describer" priority="-1000" /> <tag name="nelmio_api_doc.describer" priority="-1000" />
</service> </service>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="nelmio_api_doc.describers.swagger_php" class="Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber" public="false">
<argument type="service" id="nelmio_api_doc.routes" />
<argument type="service" id="nelmio_api_doc.controller_reflector" />
<argument type="service" id="annotation_reader" />
<tag name="nelmio_api_doc.describer" priority="-200" />
</service>
</services>
</container>

48
Resources/doc/areas.rst Normal file
View File

@ -0,0 +1,48 @@
Areas
=====
We've already seen that you can configure which routes are documented using ``nelmio_api_doc.areas``:
.. code-block:: yaml
nelmio_api_doc:
areas:
path_patterns: [ ^/api ]
But in fact, this config option is way more powerful and allows you to split your documentation in several parts.
Configuration
-------------
You can define areas which will each generates a different documentation:
.. code-block:: yaml
nelmio_api_doc:
areas:
default:
path_patterns: [ ^/api ]
internal:
path_patterns: [ ^/internal ]
commercial:
path_patterns: [ ^/commercial ]
Your main documentation is under the ``default`` area. It's the one shown when accessing ``/api/doc``.
Then update your routing to be able to access your different documentations:
.. code-block:: yaml
# app/config/routing.yml
app.swagger_ui:
path: /api/doc/{area}
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui, area: default }
# To expose them as JSON
#app.swagger.areas:
# path: /api/doc/{area}.json
# methods: GET
# defaults: { _controller: nelmio_api_doc.controller.swagger }
That's all! You can now access ``/api/doc/internal`` and ``/api/doc/commercial``.

View File

@ -1,6 +1,6 @@
NelmioApiDocBundle NelmioApiDocBundle
================== ==================
The **NelmioApiDocBundle** bundle allows you to generate documentation in the The **NelmioApiDocBundle** bundle allows you to generate documentation in the
OpenAPI (Swagger) format and provides a sandbox to interactively browse the API documentation. OpenAPI (Swagger) format and provides a sandbox to interactively browse the API documentation.
@ -12,7 +12,7 @@ This bundle supports _Symfony_ route requirements, PHP annotations,
[_FOSRestBundle_](https://github.com/FriendsOfSymfony/FOSRestBundle) annotations [_FOSRestBundle_](https://github.com/FriendsOfSymfony/FOSRestBundle) annotations
and apps using [_Api-Platform_](https://github.com/api-platform/api-platform). and apps using [_Api-Platform_](https://github.com/api-platform/api-platform).
For models, it supports the Symfony serializer and the JMS serializer. For models, it supports the Symfony serializer and the JMS serializer.
Migrate from 2.x to 3.0 Migrate from 2.x to 3.0
----------------------- -----------------------
@ -21,8 +21,8 @@ Migrate from 2.x to 3.0
Installation Installation
------------ ------------
Open a command console, enter your project directory and execute the following command to download the latest version of this bundle: Open a command console, enter your project directory and execute the following command to download the latest version of this bundle:
.. code-block:: bash .. code-block:: bash
@ -65,7 +65,7 @@ Open a command console, enter your project directory and execute the following c
path: /api/doc.json path: /api/doc.json
methods: GET methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger } defaults: { _controller: nelmio_api_doc.controller.swagger }
As you just installed the bundle, you'll likely see routes you don't want in As you just installed the bundle, you'll likely see routes you don't want in
your documentation such as `/_profiler/`. To fix this, you can filter the your documentation such as `/_profiler/`. To fix this, you can filter the
routes that are documented by configuring the bundle: routes that are documented by configuring the bundle:
@ -79,7 +79,7 @@ Open a command console, enter your project directory and execute the following c
- ^/api(?!/doc$) - ^/api(?!/doc$)
How does this bundle work? How does this bundle work?
-------------------------- --------------------------
It generates you a swagger documentation from your symfony app thanks to It generates you a swagger documentation from your symfony app thanks to
_Describers_. Each of these _Describers_ extract infos from various sources. _Describers_. Each of these _Describers_ extract infos from various sources.
@ -88,11 +88,11 @@ routes, etc.
If you configured the ``app.swagger_ui`` route above, you can browse your If you configured the ``app.swagger_ui`` route above, you can browse your
documentation at `http://example.org/api/doc`. documentation at `http://example.org/api/doc`.
Using the bundle Using the bundle
---------------- ----------------
You can configure global information in the bundle configuration ``documentation.info`` section (take a look at You can configure global information in the bundle configuration ``documentation.info`` section (take a look at
[the Swagger specification](http://swagger.io/specification/) to know the fields [the Swagger specification](http://swagger.io/specification/) to know the fields
available): available):
@ -151,7 +151,7 @@ Use models
As shown in the example above, the bundle provides the ``@Model`` annotation. As shown in the example above, the bundle provides the ``@Model`` annotation.
When you use it, the bundle will deduce your model properties. When you use it, the bundle will deduce your model properties.
It has two options: It has two options:
* ``type`` to specify your model's type:: * ``type`` to specify your model's type::
@ -165,12 +165,12 @@ It has two options:
/** /**
* @Model(type=User::class, groups={"non_sensitive_data"}) * @Model(type=User::class, groups={"non_sensitive_data"})
*/ */
.. warning:: .. warning::
The ``@Model`` annotation acts like a ``@Schema`` annotation. If you nest it with a ``@Schema`` annotation, the bundle will consider that The ``@Model`` annotation acts like a ``@Schema`` annotation. If you nest it with a ``@Schema`` annotation, the bundle will consider that
you're documenting an array of models. you're documenting an array of models.
For instance, the following example:: For instance, the following example::
/** /**
@ -215,7 +215,7 @@ It has two options:
public function myAction() public function myAction()
{ {
} }
If you're not using the JMS Serializer If you're not using the JMS Serializer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -240,3 +240,13 @@ support in your config:
nelmio_api_doc: nelmio_api_doc:
models: { use_jms: false } models: { use_jms: false }
Learn more
----------
If you need more complex features, take a look at:
.. toctree::
:maxdepth: 1
areas

View File

@ -0,0 +1,36 @@
<?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\Tests;
use Nelmio\ApiDocBundle\ApiDocGenerator;
use Nelmio\ApiDocBundle\Describer\DefaultDescriber;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
class ApiDocGeneratorTest extends TestCase
{
public function testCache()
{
$adapter = new ArrayAdapter();
$generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter);
$this->assertEquals($generator->generate(), $adapter->getItem('swagger_doc')->get());
}
public function testCacheWithCustomId()
{
$adapter = new ArrayAdapter();
$generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter, 'custom_id');
$this->assertEquals($generator->generate(), $adapter->getItem('custom_id')->get());
}
}

View File

@ -0,0 +1,41 @@
<?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\Tests\Controller;
use Nelmio\ApiDocBundle\ApiDocGenerator;
use Nelmio\ApiDocBundle\Controller\DocumentationController;
use Nelmio\ApiDocBundle\Controller\SwaggerUiController;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
class ControllersTest extends TestCase
{
/**
* @group legacy
* @expectedDeprecation Providing an instance of "Nelmio\ApiDocBundle\ApiDocGenerator" to "Nelmio\ApiDocBundle\Controller\SwaggerUiController::__construct()" is deprecated since version 3.1. Provide it an instance of "Psr\Container\ContainerInterface" instead.
*/
public function testSwaggerUiControllerInstanciation()
{
$controller = new SwaggerUiController(new ApiDocGenerator([], []), $this->createMock('Twig_Environment'));
$controller(new Request());
}
/**
* @group legacy
* @expectedDeprecation Providing an instance of "Nelmio\ApiDocBundle\ApiDocGenerator" to "Nelmio\ApiDocBundle\Controller\DocumentationController::__construct()" is deprecated since version 3.1. Provide it an instance of "Psr\Container\ContainerInterface" instead.
*/
public function testDocumentationControllerInstanciation()
{
$controller = new DocumentationController(new ApiDocGenerator([], []));
$controller(new Request());
}
}

View File

@ -0,0 +1,62 @@
<?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\Tests\DependencyInjection;
use Nelmio\ApiDocBundle\DependencyInjection\Configuration;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\Processor;
class ConfigurationTest extends TestCase
{
public function testDefaultArea()
{
$processor = new Processor();
$config = $processor->processConfiguration(new Configuration(), [['areas' => ['path_patterns' => ['/foo']]]]);
$this->assertEquals(['default' => ['path_patterns' => ['/foo']]], $config['areas']);
}
public function testAreas()
{
$processor = new Processor();
$config = $processor->processConfiguration(new Configuration(), [['areas' => $areas = [
'default' => ['path_patterns' => ['/foo']],
'internal' => ['path_patterns' => ['/internal']],
'commercial' => ['path_patterns' => ['/internal']],
]]]);
$this->assertEquals($areas, $config['areas']);
}
/**
* @group legacy
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage You must not use both `nelmio_api_doc.areas` and `nelmio_api_doc.routes` config options. Please update your config to only use `nelmio_api_doc.areas`.
*/
public function testBothAreasAndRoutes()
{
$processor = new Processor();
$config = $processor->processConfiguration(new Configuration(), [['areas' => [], 'routes' => []]]);
}
/**
* @group legacy
* @expectedDeprecation The `nelmio_api_doc.routes` config option is deprecated. Please use `nelmio_api_doc.areas` instead (just replace `routes` by `areas` in your config).
*/
public function testDefaultConfig()
{
$processor = new Processor();
$config = $processor->processConfiguration(new Configuration(), [['routes' => ['path_patterns' => ['/foo']]]]);
$this->assertEquals(['default' => ['path_patterns' => ['/foo']]], $config['areas']);
}
}

View File

@ -0,0 +1,32 @@
<?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\Tests\Functional\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Swagger\Annotations as SWG;
/**
* @Route("/test")
*/
class TestController
{
/**
* @SWG\Response(
* response="200",
* description="Test"
* )
* @Route("/test/", methods={"GET"})
*/
public function testAction()
{
}
}

View File

@ -18,19 +18,40 @@ class SwaggerUiTest extends WebTestCase
return parent::createClient([], ['PHP_SELF' => '/app_dev.php/docs', 'SCRIPT_FILENAME' => '/var/www/app/web/app_dev.php']); return parent::createClient([], ['PHP_SELF' => '/app_dev.php/docs', 'SCRIPT_FILENAME' => '/var/www/app/web/app_dev.php']);
} }
public function testSwaggerUi() /**
* @dataProvider areaProvider
*/
public function testSwaggerUi($url, $area, $expected)
{ {
$client = self::createClient(); $client = self::createClient();
$crawler = $client->request('GET', '/app_dev.php/docs/'); $crawler = $client->request('GET', '/app_dev.php'.$url);
$response = $client->getResponse(); $response = $client->getResponse();
$this->assertEquals(200, $response->getStatusCode()); $this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('text/html; charset=UTF-8', $response->headers->get('Content-Type')); $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('Content-Type'));
$this->assertEquals($expected, json_decode($crawler->filterXPath('//script[@id="swagger-data"]')->text(), true)['spec']);
}
public function areaProvider()
{
$expected = $this->getSwaggerDefinition()->toArray(); $expected = $this->getSwaggerDefinition()->toArray();
$expected['basePath'] = '/app_dev.php'; $expected['basePath'] = '/app_dev.php';
$this->assertEquals($expected, json_decode($crawler->filterXPath('//script[@id="swagger-data"]')->text(), true)['spec']); yield ['/docs', 'default', $expected];
// Api-platform documentation
$expected['paths'] = [
'/api/dummies' => $expected['paths']['/api/dummies'],
'/api/foo' => $expected['paths']['/api/foo'],
'/api/dummies/{id}' => $expected['paths']['/api/dummies/{id}'],
'/test/test/' => ['get' => [
'responses' => ['200' => ['description' => 'Test']],
]],
];
$expected['definitions'] = ['Dummy' => $expected['definitions']['Dummy']];
yield ['/docs/test', 'test', $expected];
} }
public function testJsonDocs() public function testJsonDocs()

View File

@ -64,11 +64,11 @@ class TestKernel extends Kernel
*/ */
protected function configureRoutes(RouteCollectionBuilder $routes) protected function configureRoutes(RouteCollectionBuilder $routes)
{ {
$routes->import(__DIR__.'/Controller/TestController.php', '/', 'annotation');
$routes->import(__DIR__.'/Controller/ApiController.php', '/', 'annotation'); $routes->import(__DIR__.'/Controller/ApiController.php', '/', 'annotation');
$routes->import(__DIR__.'/Controller/UndocumentedController.php', '/', 'annotation'); $routes->import(__DIR__.'/Controller/UndocumentedController.php', '/', 'annotation');
$routes->import('', '/api', 'api_platform'); $routes->import('', '/api', 'api_platform');
$routes->import('@NelmioApiDocBundle/Resources/config/routing/swaggerui.xml', '/docs'); $routes->add('/docs/{area}', 'nelmio_api_doc.controller.swagger_ui')->setDefault('area', 'default');
$routes->add('/docs.json', 'nelmio_api_doc.controller.swagger'); $routes->add('/docs.json', 'nelmio_api_doc.controller.swagger');
if ($this->useJMS) { if ($this->useJMS) {
@ -110,8 +110,9 @@ class TestKernel extends Kernel
'title' => 'My Test App', 'title' => 'My Test App',
], ],
], ],
'routes' => [ 'areas' => [
'path_patterns' => ['^/api(?!/admin)'], 'default' => ['path_patterns' => ['^/api(?!/admin)']],
'test' => ['path_patterns' => ['^/test']],
], ],
]); ]);
} }