Add FOSRestBundle support

This commit is contained in:
Ener-Getick 2016-08-04 22:27:10 +02:00
parent 7a7f78a53f
commit ffda7801f6
No known key found for this signature in database
GPG Key ID: 9E5D2DB67BF054DD
13 changed files with 173 additions and 56 deletions

View File

@ -39,15 +39,15 @@ class EXSystApiDocExtension extends Extension
$loader->load('services.xml');
// Removes useless services
if (!class_exists(ApiDoc::class)) {
$container->removeDefinition('exsyst_api_doc.route_describers.nelmio_annotation');
// Import services needed for each library
if (class_exists(ApiDoc::class)) {
$loader->load('nelmio_apidoc.xml');
}
if (!class_exists(DocBlockFactory::class)) {
$container->removeDefinition('exsyst_api_doc.route_describers.php_doc');
if (class_exists(DocBlockFactory::class)) {
$loader->load('php_doc.xml');
}
if (!class_exists(Swagger::class)) {
$container->removeDefinition('exsyst_api_doc.describers.swagger_php');
if (class_exists(Swagger::class)) {
$loader->load('swagger_php.xml');
}
$bundles = $container->getParameter('kernel.bundles');

View File

@ -34,24 +34,8 @@ class DefaultDescriber implements DescriberInterface
// Paths
$paths = $api->getPaths();
foreach ($paths as $uri => $path) {
// Path Parameters
preg_match_all('/\{(.+)\}/SU', $uri, $matches);
$pathParameters = $matches[1];
foreach ($path->getMethods() as $method) {
$operation = $path->getOperation($method);
$parameters = $operation->getParameters();
// Default Path Parameters
foreach ($pathParameters as $pathParameter) {
if ($parameters->has($pathParameter, 'path')) {
continue;
}
$parameters->get($pathParameter, 'path')
->setRequired(true)
->setType('string');
}
// Default Response
if (0 === iterator_count($operation->getResponses())) {

View File

@ -8,7 +8,7 @@
<argument type="service" id="exsyst_api_doc.describers.api_platform.documentation" />
<argument type="service" id="api_platform.swagger.normalizer.documentation" />
<tag name="exsyst_api_doc.describer" priority="-150" />
<tag name="exsyst_api_doc.describer" priority="-200" />
</service>
<service id="exsyst_api_doc.describers.api_platform.documentation" class="ApiPlatform\Core\Documentation\Documentation" public="false">

View File

@ -0,0 +1,14 @@
<?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="exsyst_api_doc.route_describers.nelmio_annotation" class="EXSyst\Bundle\ApiDocBundle\RouteDescriber\NelmioAnnotationDescriber" public="false">
<argument type="service" id="annotation_reader" />
<tag name="exsyst_api_doc.route_describer" priority="-250" />
</service>
</services>
</container>

View File

@ -0,0 +1,12 @@
<?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="exsyst_api_doc.route_describers.php_doc" class="EXSyst\Bundle\ApiDocBundle\RouteDescriber\PhpDocDescriber" public="false">
<tag name="exsyst_api_doc.route_describer" priority="-1000" />
</service>
</services>
</container>

View File

@ -9,12 +9,6 @@
</service>
<!-- Extractors -->
<service id="exsyst_api_doc.describers.swagger_php" class="EXSyst\Bundle\ApiDocBundle\Describer\SwaggerPhpDescriber" public="false">
<argument>%kernel.root_dir%</argument>
<tag name="exsyst_api_doc.describer" priority="-150" />
</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" />
@ -30,16 +24,6 @@
<!-- Routing Extractors -->
<service id="exsyst_api_doc.route_describers.route_metadata" class="EXSyst\Bundle\ApiDocBundle\RouteDescriber\RouteMetadataDescriber" public="false">
<tag name="exsyst_api_doc.route_describer" priority="-50" />
</service>
<service id="exsyst_api_doc.route_describers.php_doc" class="EXSyst\Bundle\ApiDocBundle\RouteDescriber\PhpDocDescriber" public="false">
<tag name="exsyst_api_doc.route_describer" priority="-70" />
</service>
<service id="exsyst_api_doc.route_describers.nelmio_annotation" class="EXSyst\Bundle\ApiDocBundle\RouteDescriber\NelmioAnnotationDescriber" public="false">
<argument type="service" id="annotation_reader" />
<tag name="exsyst_api_doc.route_describer" priority="-100" />
</service>
</services>

View File

@ -0,0 +1,14 @@
<?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="exsyst_api_doc.describers.swagger_php" class="EXSyst\Bundle\ApiDocBundle\Describer\SwaggerPhpDescriber" public="false">
<argument>%kernel.root_dir%</argument>
<tag name="exsyst_api_doc.describer" priority="-300" />
</service>
</services>
</container>

View File

@ -0,0 +1,82 @@
<?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\RouteDescriber;
use Doctrine\Common\Annotations\Reader;
use EXSyst\Component\Swagger\Parameter;
use EXSyst\Component\Swagger\Swagger;
use FOS\RestBundle\Controller\Annotations\RequestParam;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\Routing\Route;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Regex;
class FosRestDescriber implements RouteDescriberInterface
{
use RouteDescriberTrait;
private $annotationReader;
public function __construct(Reader $annotationReader)
{
$this->annotationReader = $annotationReader;
}
public function describe(Swagger $api, Route $route, \ReflectionMethod $reflectionMethod)
{
$annotations = $this->annotationReader->getMethodAnnotations();
$annotations = array_filter($annotations, function ($value) {
return $value instanceof RequestParam || $value instanceof QueryParam;
});
foreach ($this->getOperations($api, $route) as $operation) {
foreach ($annotations as $annotation) {
$in = $annotation instanceof QueryParam ? 'query' : 'formData';
$parameter = $operation->getParameters()->get($annotation->getKey(), $in);
$parameter->setAllowEmptyValue($annotation->nullable && $annotation->allowBlank);
$parameter->setType($annotation->map ? 'array' : 'string');
$parameter->setDefault($annotation->getDefault());
if (null === $parameter->getDescription()) {
$parameter->setDescription($annotation->description);
}
$normalizedRequirements = $this->normalizeRequirements($annotation->requirements);
if (null !== $normalizedRequirements) {
$parameter->setFormat($normalizedRequirements);
}
}
}
}
private function normalizeRequirements($requirements)
{
// if pattern
if (isset($requirements['rule'])) {
return (string) $requirements['rule'];
}
if (is_string($requirements)) {
return $requirements;
}
// if custom constraint
if ($requirements instanceof Constraint) {
if ($requirements instanceof Regex) {
return $requirements->getHtmlPattern();
}
$reflectionClass = new \ReflectionClass($requirements);
return $reflectionClass->getShortName();
}
}
}

View File

@ -39,28 +39,41 @@ class NelmioAnnotationDescriber implements RouteDescriberInterface
$annotationArray = $annotation->toArray();
foreach ($this->getOperations($api, $route) as $operation) {
if ($annotation->getDescription()) {
if (null === $operation->getDescription()) {
$operation->setDescription($annotation->getDescription());
}
$operation->setDeprecated($operation->getDeprecated() || $annotation->getDeprecated());
if (null === $operation->getDeprecated() && $annotation->getDeprecated()) {
$operation->setDeprecated(true);
}
// Request parameters
foreach ($annotation->getParameters() as $name => $configuration) {
$parameter = $operation->getParameters()->get($name, 'formData');
if (isset($configuration['required'])) {
$parameter->setRequired($parameter->getRequired() || $configuration['required']);
if (isset($configuration['required']) && $configuration['required']) {
$parameter->setRequired(true);
}
$this->configureParameter($parameter, $configuration);
}
// Query parameters
// Query/Path required parameters
$compiledRoute = $route->compile();
$pathVariables = $compiledRoute->getVariables();
$hostVariables = $compiledRoute->getHostVariables();
foreach ($annotation->getRequirements() as $name => $configuration) {
$parameter = $operation->getParameters()->get($name, 'query');
if (in_array($name, $pathVariables)) {
$in = 'path';
} elseif (!in_array($name, $hostVariables)) {
$in = 'query';
} else { // Host variables not supported
continue;
}
$parameter = $operation->getParameters()->get($name, $in);
$parameter->setRequired(true);
$this->configureParameter($parameter, $configuration);
}
// Optional Query parameters
foreach ($annotation->getFilters() as $name => $configuration) {
$parameter = $operation->getParameters()->get($name, 'query');
$this->configureParameter($parameter, $configuration);

View File

@ -46,8 +46,12 @@ class PhpDocDescriber implements RouteDescriberInterface
foreach ($this->getOperations($api, $route) as $operation) {
if (null !== $docBlock) {
if (null === $operation->getSummary() && '' !== $docBlock->getSummary()) {
$operation->setSummary($docBlock->getSummary());
}
if (null === $operation->getDescription() && '' !== (string) $docBlock->getDescription()) {
$operation->setDescription((string) $docBlock->getDescription());
}
if ($docBlock->hasTag('deprecated')) {
$operation->setDeprecated(true);
}

View File

@ -23,11 +23,18 @@ class RouteMetadataDescriber implements RouteDescriberInterface
foreach ($this->getOperations($api, $route) as $operation) {
$operation->merge(['schemes' => $route->getSchemes()]);
foreach ($route->getRequirements() as $parameterName => $requirement) {
$parameter = $operation->getParameters()->get($parameterName, 'path');
$requirements = $route->getRequirements();
$compiledRoute = $route->compile();
// Don't include path variables
foreach ($compiledRoute->getPathVariables() as $pathVariable) {
$parameter = $operation->getParameters()->get($pathVariable, 'path');
$parameter->setRequired(true);
$parameter->setType('string');
$parameter->setFormat($requirement);
if (isset($requirements[$pathVariable])) {
$parameter->setFormat($requirements[$pathVariable]);
}
}
}
}

View File

@ -38,7 +38,7 @@ class FunctionalTest extends WebTestCase
$operation = $this->getOperation('/nelmio/{foo}', 'post');
$this->assertEquals('This action is described.', $operation->getDescription());
$this->assertFalse($operation->getDeprecated());
$this->assertNull($operation->getDeprecated());
$foo = $operation->getParameters()->get('foo', 'path');
$this->assertTrue($foo->getRequired());

View File

@ -11,7 +11,7 @@
"require": {
"php": "^7.0",
"symfony/framework-bundle": "^3.2",
"exsyst/swagger": "dev-master"
"exsyst/swagger": "~0.2.1"
},
"require-dev": {
"symfony/validator": "^3.2",
@ -20,17 +20,20 @@
"symfony/cache": "^3.2",
"symfony/phpunit-bridge": "^3.2",
"sensio/framework-extra-bundle": "^3.0",
"phpunit/phpunit": "^5.4",
"nelmio/api-doc-bundle": "^2.0",
"phpdocumentor/reflection-docblock": "^3.1",
"phpunit/phpunit": "^5.4",
"zircote/swagger-php": "^2.0",
"api-platform/core": "dev-master"
"api-platform/core": "dev-master",
"friendsofsymfony/rest-bundle": "^2.0"
},
"suggest": {
"nelmio/api-doc-bundle": "For using the ApiDoc annotation.",
"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."
},
"autoload": {
"psr-4": {