Merge branch 'dev' into CONFIG

This commit is contained in:
Guilhem N 2017-02-07 19:14:35 +01:00 committed by GitHub
commit cde91cc38c
15 changed files with 79 additions and 166 deletions

View File

@ -22,7 +22,6 @@ final class Model extends AbstractAnnotation
public static $_parents = [ public static $_parents = [
'Swagger\Annotations\Parameter', 'Swagger\Annotations\Parameter',
'Swagger\Annotations\Response', 'Swagger\Annotations\Response',
'Swagger\Annotations\Swagger',
]; ];
public $type; public $type;

View File

@ -14,7 +14,10 @@ namespace Nelmio\ApiDocBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
class AddDescribersPass implements CompilerPassInterface /**
* @internal
*/
final class AddDescribersPass implements CompilerPassInterface
{ {
use PriorityTaggedServiceTrait; use PriorityTaggedServiceTrait;

View File

@ -14,7 +14,10 @@ namespace Nelmio\ApiDocBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
class AddRouteDescribersPass implements CompilerPassInterface /**
* @internal
*/
final class AddRouteDescribersPass implements CompilerPassInterface
{ {
use PriorityTaggedServiceTrait; use PriorityTaggedServiceTrait;

View File

@ -27,7 +27,6 @@ final class Configuration implements ConfigurationInterface
->example(['info' => ['title' => 'My App']]) ->example(['info' => ['title' => 'My App']])
->prototype('variable')->end() ->prototype('variable')->end()
->end() ->end()
->scalarNode('source_folder')->defaultValue('%kernel.root_dir%/../src')->end()
->arrayNode('routes') ->arrayNode('routes')
->info('Filter the routes that are documented') ->info('Filter the routes that are documented')
->addDefaultsIfNotSet() ->addDefaultsIfNotSet()

View File

@ -17,10 +17,19 @@ use Swagger\Annotations\Swagger;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
final class NelmioApiDocExtension extends Extension final class NelmioApiDocExtension extends Extension implements PrependExtensionInterface
{ {
/**
* {@inheritdoc}
*/
public function prepend(ContainerBuilder $container)
{
$container->prependExtensionConfig('framework', ['property_info' => ['enabled' => true]]);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -36,15 +45,10 @@ final class NelmioApiDocExtension extends Extension
$routeCollectionBuilder->replaceArgument(0, $config['routes']['path_patterns']); $routeCollectionBuilder->replaceArgument(0, $config['routes']['path_patterns']);
// 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');
} }
if (class_exists(Swagger::class)) {
$loader->load('swagger_php.xml');
$swaggerPHPDescriber = $container->getDefinition('nelmio_api_doc.describers.swagger_php');
$swaggerPHPDescriber->replaceArgument(0, $config['source_folder']);
}
if (interface_exists(ParamInterface::class)) { if (interface_exists(ParamInterface::class)) {
$loader->load('fos_rest.xml'); $loader->load('fos_rest.xml');
} }

View File

@ -14,37 +14,59 @@ namespace Nelmio\ApiDocBundle\Describer;
use Nelmio\ApiDocBundle\SwaggerPhp\AddDefaults; use Nelmio\ApiDocBundle\SwaggerPhp\AddDefaults;
use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister; use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister;
use Nelmio\ApiDocBundle\SwaggerPhp\OperationResolver; use Nelmio\ApiDocBundle\SwaggerPhp\OperationResolver;
use Nelmio\ApiDocBundle\Util\ControllerReflector;
use Swagger\Analyser; use Swagger\Analyser;
use Swagger\Analysis; use Swagger\Analysis;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Routing\RouteCollection;
final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelRegistryAwareInterface final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelRegistryAwareInterface
{ {
use ModelRegistryAwareTrait; use ModelRegistryAwareTrait;
private $operationResolver; private $routeCollection;
private $controllerReflector;
public function __construct(string $projectPath, bool $overwrite = false) public function __construct(RouteCollection $routeCollection, ControllerReflector $controllerReflector, bool $overwrite = false)
{ {
$nelmioNamespace = 'Nelmio\\ApiDocBundle\\'; $this->routeCollection = $routeCollection;
if (!in_array($nelmioNamespace, Analyser::$whitelist)) { $this->controllerReflector = $controllerReflector;
Analyser::$whitelist[] = $nelmioNamespace;
}
parent::__construct(function () use ($projectPath) { parent::__construct(function () {
$options = ['processors' => $this->getProcessors()]; $whitelist = Analyser::$whitelist;
$annotation = \Swagger\scan($projectPath, $options); Analyser::$whitelist = false;
try {
$options = ['processors' => $this->getProcessors()];
$annotation = \Swagger\scan($this->getFinder(), $options);
return json_decode(json_encode($annotation)); return json_decode(json_encode($annotation));
} finally {
Analyser::$whitelist = $whitelist;
}
}, $overwrite); }, $overwrite);
} }
/** private function getFinder()
* If set, the describer will try to complete paths and create
* implicit operations.
*/
public function setOperationResolver(OperationResolver $operationResolver)
{ {
$this->operationResolver = $operationResolver; $files = [];
foreach ($this->routeCollection->all() as $route) {
if (!$route->hasDefault('_controller')) {
continue;
}
// if able to resolve the controller
$controller = $route->getDefault('_controller');
if ($callable = $this->controllerReflector->getReflectionClassAndMethod($controller)) {
list($class, $method) = $callable;
$files[$class->getFileName()] = true;
}
}
$finder = new Finder();
$finder->append(array_keys($files));
return $finder;
} }
private function getProcessors(): array private function getProcessors(): array
@ -52,10 +74,8 @@ final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelReg
$processors = [ $processors = [
new AddDefaults(), new AddDefaults(),
new ModelRegister($this->modelRegistry), new ModelRegister($this->modelRegistry),
new OperationResolver($this->routeCollection, $this->controllerReflector),
]; ];
if (null !== $this->operationResolver) {
$processors[] = $this->operationResolver;
}
return array_merge($processors, Analysis::processors()); return array_merge($processors, Analysis::processors());
} }

View File

@ -1,45 +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;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddDescribersPass;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddModelDescribersPass;
use Nelmio\ApiDocBundle\DependencyInjection\Compiler\AddRouteDescribersPass;
use Nelmio\ApiDocBundle\DependencyInjection\EXSystApiDocExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class EXSystApiDocBundle extends Bundle
{
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new AddDescribersPass());
$container->addCompilerPass(new AddRouteDescribersPass());
$container->addCompilerPass(new AddModelDescribersPass());
}
/**
* {@inheritdoc}
*/
public function getContainerExtension()
{
if (null === $this->extension) {
$this->extension = new EXSystApiDocExtension();
}
if ($this->extension) {
return $this->extension;
}
}
}

View File

@ -21,7 +21,7 @@ class ScalarModelDescriber implements ModelDescriberInterface
Type::BUILTIN_TYPE_INT => 'integer', Type::BUILTIN_TYPE_INT => 'integer',
Type::BUILTIN_TYPE_FLOAT => 'float', Type::BUILTIN_TYPE_FLOAT => 'float',
Type::BUILTIN_TYPE_STRING => 'string', Type::BUILTIN_TYPE_STRING => 'string',
Type::BUILTIN_TYPE_BOOL => 'boolean' Type::BUILTIN_TYPE_BOOL => 'boolean',
]; ];
public function describe(Model $model, Schema $schema) public function describe(Model $model, Schema $schema)

View File

@ -5,31 +5,15 @@
<services> <services>
<service id="nelmio_api_doc.describers.swagger_php" class="Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber" public="false"> <service id="nelmio_api_doc.describers.swagger_php" class="Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber" public="false">
<argument>%kernel.root_dir%</argument> <argument type="service">
<call method="setOperationResolver"> <service class="Symfony\Component\Routing\RouteCollection">
<argument type="service" id="nelmio_api_doc.describers.swagger_php.operation_resolver" /> <factory service="router" method="getRouteCollection" />
</call> </service>
</argument>
<argument type="service" id="nelmio_api_doc.controller_reflector" />
<tag name="nelmio_api_doc.describer" priority="-200" /> <tag name="nelmio_api_doc.describer" priority="-200" />
</service> </service>
<service id="nelmio_api_doc.describers.swagger_php.path_resolver" class="Nelmio\ApiDocBundle\SwaggerPhp\PathResolver" public="false">
<argument type="service">
<service class="Symfony\Component\Routing\RouteCollection">
<factory service="router" method="getRouteCollection" />
</service>
</argument>
<argument type="service" id="nelmio_api_doc.controller_reflector" />
</service>
<service id="nelmio_api_doc.describers.swagger_php.operation_resolver" class="Nelmio\ApiDocBundle\SwaggerPhp\OperationResolver" public="false">
<argument type="service">
<service class="Symfony\Component\Routing\RouteCollection">
<factory service="router" method="getRouteCollection" />
</service>
</argument>
<argument type="service" id="nelmio_api_doc.controller_reflector" />
</service>
</services> </services>
</container> </container>

View File

@ -1,34 +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\Tests\Describer;
use EXSyst\Component\Swagger\Swagger;
use Nelmio\ApiDocBundle\Describer\SwaggerPhpDescriber;
use Nelmio\ApiDocBundle\Model\ModelRegistry;
class SwaggerPhpDescriberTest extends AbstractDescriberTest
{
public function testDescribe()
{
$api = $this->getSwaggerDoc();
$info = $api->getInfo();
$this->assertEquals('My Awesome App', $info->getTitle());
$this->assertEquals('1.3', $info->getVersion());
}
protected function setUp()
{
$this->describer = new SwaggerPhpDescriber(__DIR__.'/../Fixtures');
$this->describer->setModelRegistry(new ModelRegistry([], new Swagger()));
}
}

View File

@ -1,24 +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\Tests\Functional\Fixtures\SwaggerPhp;
use Swagger\Annotations\Info;
/**
* @Info(
* title="My Awesome App",
* version="1.3"
* )
*/
class Api
{
}

View File

@ -13,9 +13,7 @@ namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;
use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\RequestParam; use FOS\RestBundle\Controller\Annotations\RequestParam;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Annotation\Model; use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\Dummy;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User; use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Swagger\Annotations as SWG; use Swagger\Annotations as SWG;
@ -41,7 +39,7 @@ class ApiController
* @SWG\Response( * @SWG\Response(
* response="201", * response="201",
* description="Operation automatically detected", * description="Operation automatically detected",
* @Model(type="Nelmio\ApiDocBundle\Tests\Functional\Entity\User") * @Model(type=User::class)
* ) * )
* @SWG\Parameter( * @SWG\Parameter(
* name="foo", * name="foo",
@ -49,7 +47,7 @@ class ApiController
* description="This is a parameter", * description="This is a parameter",
* @SWG\Schema( * @SWG\Schema(
* type="array", * type="array",
* @Model(type="Nelmio\ApiDocBundle\Tests\Functional\Entity\User") * @Model(type=User::class)
* ) * )
* ) * )
*/ */

View File

@ -67,8 +67,7 @@ class TestKernel extends Kernel
'title' => 'My Test App', 'title' => 'My Test App',
], ],
], ],
'source_folder' => '%kernel.root_dir%', 'routes' => [
'routes' => [
'path_patterns' => ['^/api(?!/admin)'], 'path_patterns' => ['^/api(?!/admin)'],
], ],
]); ]);

View File

@ -22,6 +22,8 @@ final class ControllerReflector
private $container; private $container;
private $controllerNameParser; private $controllerNameParser;
private $controllers = [];
public function __construct(ContainerInterface $container, ControllerNameParser $controllerNameParser) public function __construct(ContainerInterface $container, ControllerNameParser $controllerNameParser)
{ {
$this->container = $container; $this->container = $container;
@ -69,6 +71,10 @@ final class ControllerReflector
private function getClassAndMethod(string $controller) private function getClassAndMethod(string $controller)
{ {
if (isset($this->controllers[$controller])) {
return $this->controllers[$controller];
}
if (false === strpos($controller, '::') && 2 === substr_count($controller, ':')) { if (false === strpos($controller, '::') && 2 === substr_count($controller, ':')) {
$controller = $this->controllerNameParser->parse($controller); $controller = $this->controllerNameParser->parse($controller);
} }
@ -98,9 +104,11 @@ final class ControllerReflector
} }
if (!isset($class) || !isset($method)) { if (!isset($class) || !isset($method)) {
$this->controllers[$controller] = null;
return; return;
} }
return [$class, $method]; return $this->controllers[$controller] = [$class, $method];
} }
} }

View File

@ -18,7 +18,8 @@
"php": "~7.0|~7.1", "php": "~7.0|~7.1",
"symfony/framework-bundle": "^2.8|^3.0", "symfony/framework-bundle": "^2.8|^3.0",
"symfony/property-info": "^2.8|^3.0", "symfony/property-info": "^2.8|^3.0",
"exsyst/swagger": "~0.2.3" "exsyst/swagger": "~0.2.3",
"zircote/swagger-php": "dev-master"
}, },
"require-dev": { "require-dev": {
"symfony/twig-bundle": "^2.8|^3.0", "symfony/twig-bundle": "^2.8|^3.0",
@ -34,13 +35,11 @@
"doctrine/annotations": "^1.2", "doctrine/annotations": "^1.2",
"phpdocumentor/reflection-docblock": "^3.1", "phpdocumentor/reflection-docblock": "^3.1",
"zircote/swagger-php": "^2.0",
"api-platform/core": "^2.0", "api-platform/core": "^2.0",
"friendsofsymfony/rest-bundle": "^2.0" "friendsofsymfony/rest-bundle": "^2.0"
}, },
"suggest": { "suggest": {
"phpdocumentor/reflection-docblock": "For parsing php docs.", "phpdocumentor/reflection-docblock": "For parsing php docs.",
"zircote/swagger-php": "For using swagger annotations.",
"api-platform/core": "For using an API oriented framework.", "api-platform/core": "For using an API oriented framework.",
"friendsofsymfony/rest-bundle": "For using the parameters annotations." "friendsofsymfony/rest-bundle": "For using the parameters annotations."
}, },