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()
{
$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;
}

View File

@ -40,6 +40,10 @@ class EXSystApiDocExtension extends Extension
$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
if (class_exists(ApiDoc::class)) {
$loader->load('nelmio_apidoc.xml');

View File

@ -17,25 +17,25 @@ use EXSyst\Component\Swagger\Swagger;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\RouteCollection;
class RouteDescriber implements DescriberInterface
{
private $container;
private $router;
private $routeCollection;
private $controllerNameParser;
private $routeDescribers;
/**
* @param ContainerInterface $container
* @param RouterInterface $router
* @param RouteCollection $routeCollection
* @param ControllerNameParser $controllerNameParser
* @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->router = $router;
$this->routeCollection = $routeCollection;
$this->controllerNameParser = $controllerNameParser;
$this->routeDescribers = $routeDescribers;
}
@ -46,7 +46,7 @@ class RouteDescriber implements DescriberInterface
return;
}
foreach ($this->getRoutes() as $route) {
foreach ($this->routeCollection->all() as $route) {
// if able to resolve the controller
if ($method = $this->getReflectionMethod($route->getDefault('_controller'))) {
// 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.
*

View File

@ -9,9 +9,23 @@
</service>
<!-- 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">
<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="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 Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* @Route("/api")
*/
class ApiController
{
/**
@ -45,4 +48,13 @@ class ApiController
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
{
public function testUndocumentedAction()
{
$paths = $this->getSwaggerDefinition()->getPaths();
$this->assertFalse($paths->has('/undocumented'));
$this->assertFalse($paths->has('/api/admin'));
}
public function testUserAction()
{
$operation = $this->getOperation('/test/{user}', 'get');
$operation = $this->getOperation('/api/test/{user}', 'get');
$this->assertEquals(['https'], $operation->getSchemes());
$this->assertEmpty($operation->getSummary());
@ -35,7 +42,7 @@ class FunctionalTest extends WebTestCase
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->assertNull($operation->getDeprecated());
@ -47,7 +54,7 @@ class FunctionalTest extends WebTestCase
public function testDeprecatedAction()
{
$operation = $this->getOperation('/deprecated', 'get');
$operation = $this->getOperation('/api/deprecated', 'get');
$this->assertEquals('This action is deprecated.', $operation->getSummary());
$this->assertEquals('Please do not use this action.', $operation->getDescription());

View File

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