Merge pull request #2 from EXSyst/FILTER_ROUTES

Allow to filter routes based on their path
This commit is contained in:
Guilhem N 2016-11-30 15:02:27 +01:00 committed by GitHub
commit 9b82da1d85
9 changed files with 144 additions and 21 deletions

View File

@ -19,7 +19,21 @@ class Configuration implements ConfigurationInterface
public function getConfigTreeBuilder() public function getConfigTreeBuilder()
{ {
$treeBuilder = new TreeBuilder(); $treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('exsyst_api_doc'); $treeBuilder
->root('exsyst_api_doc')
->children()
->arrayNode('routes')
->info('Filter the routes that are documented')
->addDefaultsIfNotSet()
->children()
->arrayNode('path_patterns')
->example(array('^/api', '^/api(?!/admin)'))
->prototype('scalar')->end()
->end()
->end()
->end()
->end();
return $treeBuilder; return $treeBuilder;
} }

View File

@ -40,6 +40,10 @@ class EXSystApiDocExtension extends Extension
$loader->load('services.xml'); $loader->load('services.xml');
// Filter routes
$routeCollectionBuilder = $container->getDefinition('exsyst_api_doc.describers.route.filtered_route_collection_builder');
$routeCollectionBuilder->replaceArgument(0, $config['routes']['path_patterns']);
// Import services needed for each library // Import services needed for each library
if (class_exists(ApiDoc::class)) { if (class_exists(ApiDoc::class)) {
$loader->load('nelmio_apidoc.xml'); $loader->load('nelmio_apidoc.xml');

View File

@ -17,25 +17,25 @@ use EXSyst\Component\Swagger\Swagger;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser; use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\RouteCollection;
class RouteDescriber implements DescriberInterface class RouteDescriber implements DescriberInterface
{ {
private $container; private $container;
private $router; private $routeCollection;
private $controllerNameParser; private $controllerNameParser;
private $routeDescribers; private $routeDescribers;
/** /**
* @param ContainerInterface $container * @param ContainerInterface $container
* @param RouterInterface $router * @param RouteCollection $routeCollection
* @param ControllerNameParser $controllerNameParser * @param ControllerNameParser $controllerNameParser
* @param RouteDescriberInterface[] $routeDescribers * @param RouteDescriberInterface[] $routeDescribers
*/ */
public function __construct(ContainerInterface $container, RouterInterface $router, ControllerNameParser $controllerNameParser, array $routeDescribers) public function __construct(ContainerInterface $container, RouteCollection $routeCollection, ControllerNameParser $controllerNameParser, array $routeDescribers)
{ {
$this->container = $container; $this->container = $container;
$this->router = $router; $this->routeCollection = $routeCollection;
$this->controllerNameParser = $controllerNameParser; $this->controllerNameParser = $controllerNameParser;
$this->routeDescribers = $routeDescribers; $this->routeDescribers = $routeDescribers;
} }
@ -46,7 +46,7 @@ class RouteDescriber implements DescriberInterface
return; return;
} }
foreach ($this->getRoutes() as $route) { foreach ($this->routeCollection->all() as $route) {
// if able to resolve the controller // if able to resolve the controller
if ($method = $this->getReflectionMethod($route->getDefault('_controller'))) { if ($method = $this->getReflectionMethod($route->getDefault('_controller'))) {
// Extract as many informations as possible about this route // Extract as many informations as possible about this route
@ -57,16 +57,6 @@ class RouteDescriber implements DescriberInterface
} }
} }
/**
* Return a list of route to inspect.
*
* @return Route[] An array of routes
*/
private function getRoutes()
{
return $this->router->getRouteCollection()->all();
}
/** /**
* Returns the ReflectionMethod for the given controller string. * Returns the ReflectionMethod for the given controller string.
* *

View File

@ -9,9 +9,23 @@
</service> </service>
<!-- Extractors --> <!-- Extractors -->
<service id="exsyst_api_doc.describers.route.filtered_route_collection_builder" class="EXSyst\Bundle\ApiDocBundle\Routing\FilteredRouteCollectionBuilder" public="false">
<argument type="collection" /> <!-- Path patterns -->
</service>
<service id="exsyst_api_doc.describers.route" class="EXSyst\Bundle\ApiDocBundle\Describer\RouteDescriber" public="false"> <service id="exsyst_api_doc.describers.route" class="EXSyst\Bundle\ApiDocBundle\Describer\RouteDescriber" public="false">
<argument type="service" id="service_container" /> <argument type="service" id="service_container" />
<argument type="service" id="router" /> <argument type="service">
<service class="Symfony\Component\Routing\RouteCollection">
<factory service="exsyst_api_doc.describers.route.filtered_route_collection_builder" method="filter" />
<argument type="service">
<service class="Symfony\Component\Routing\RouteCollection">
<factory service="router" method="getRouteCollection" />
</service>
</argument>
</service>
</argument>
<argument type="service" id="controller_name_converter" /> <argument type="service" id="controller_name_converter" />
<argument type="collection" /> <argument type="collection" />

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the ApiDocBundle package.
*
* (c) EXSyst
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace EXSyst\Bundle\ApiDocBundle\Routing;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
final class FilteredRouteCollectionBuilder
{
private $pathPatterns;
public function __construct(array $pathPatterns = [])
{
$this->pathPatterns = $pathPatterns;
}
public function filter(RouteCollection $routes): RouteCollection
{
$filteredRoutes = new RouteCollection();
foreach ($routes->all() as $name => $route) {
if ($this->match($route)) {
$filteredRoutes->add($name, $route);
}
}
return $filteredRoutes;
}
private function match(Route $route): bool
{
foreach ($this->pathPatterns as $pathPattern) {
if (!preg_match('{'.$pathPattern.'}', $route->getPath())) {
return false;
}
}
return true;
}
}

View File

@ -14,6 +14,9 @@ namespace EXSyst\Bundle\ApiDocBundle\Tests\Functional\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* @Route("/api")
*/
class ApiController class ApiController
{ {
/** /**
@ -45,4 +48,13 @@ class ApiController
public function deprecatedAction() public function deprecatedAction()
{ {
} }
/**
* This action is not documented. It is excluded by the config.
*
* @Route("/admin", methods={"GET"})
*/
public function adminAction()
{
}
} }

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the ApiDocBundle package.
*
* (c) EXSyst
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace EXSyst\Bundle\ApiDocBundle\Tests\Functional\Controller;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class UndocumentedController
{
/**
* This path is excluded by the config (only /api allowed).
*
* @Route("/undocumented", methods={"GET"})
*/
public function undocumentedAction()
{
}
}

View File

@ -15,9 +15,16 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class FunctionalTest extends WebTestCase class FunctionalTest extends WebTestCase
{ {
public function testUndocumentedAction()
{
$paths = $this->getSwaggerDefinition()->getPaths();
$this->assertFalse($paths->has('/undocumented'));
$this->assertFalse($paths->has('/api/admin'));
}
public function testUserAction() public function testUserAction()
{ {
$operation = $this->getOperation('/test/{user}', 'get'); $operation = $this->getOperation('/api/test/{user}', 'get');
$this->assertEquals(['https'], $operation->getSchemes()); $this->assertEquals(['https'], $operation->getSchemes());
$this->assertEmpty($operation->getSummary()); $this->assertEmpty($operation->getSummary());
@ -35,7 +42,7 @@ class FunctionalTest extends WebTestCase
public function testNelmioAction() public function testNelmioAction()
{ {
$operation = $this->getOperation('/nelmio/{foo}', 'post'); $operation = $this->getOperation('/api/nelmio/{foo}', 'post');
$this->assertEquals('This action is described.', $operation->getDescription()); $this->assertEquals('This action is described.', $operation->getDescription());
$this->assertNull($operation->getDeprecated()); $this->assertNull($operation->getDeprecated());
@ -47,7 +54,7 @@ class FunctionalTest extends WebTestCase
public function testDeprecatedAction() public function testDeprecatedAction()
{ {
$operation = $this->getOperation('/deprecated', 'get'); $operation = $this->getOperation('/api/deprecated', 'get');
$this->assertEquals('This action is deprecated.', $operation->getSummary()); $this->assertEquals('This action is deprecated.', $operation->getSummary());
$this->assertEquals('Please do not use this action.', $operation->getDescription()); $this->assertEquals('Please do not use this action.', $operation->getDescription());

View File

@ -59,5 +59,12 @@ class TestKernel extends Kernel
'test' => null, 'test' => null,
'validation' => null, 'validation' => null,
]); ]);
// Filter routes
$c->loadFromExtension('exsyst_api_doc', [
'routes' => [
'path_patterns' => ['^/api(?!/admin)'],
],
]);
} }
} }