From ffda7801f65e37dd7367bd3c548c5924b932cb8f Mon Sep 17 00:00:00 2001 From: Ener-Getick Date: Thu, 4 Aug 2016 22:27:10 +0200 Subject: [PATCH] Add FOSRestBundle support --- DependencyInjection/EXSystApiDocExtension.php | 14 ++-- Describer/DefaultDescriber.php | 16 ---- Resources/config/api_platform.xml | 2 +- Resources/config/nelmio_apidoc.xml | 14 ++++ Resources/config/php_doc.xml | 12 +++ Resources/config/services.xml | 16 ---- Resources/config/swagger_php.xml | 14 ++++ RouteDescriber/FosRestDescriber.php | 82 +++++++++++++++++++ RouteDescriber/NelmioAnnotationDescriber.php | 25 ++++-- RouteDescriber/PhpDocDescriber.php | 8 +- RouteDescriber/RouteMetadataDescriber.php | 13 ++- Tests/Functional/FunctionalTest.php | 2 +- composer.json | 11 ++- 13 files changed, 173 insertions(+), 56 deletions(-) create mode 100644 Resources/config/nelmio_apidoc.xml create mode 100644 Resources/config/php_doc.xml create mode 100644 Resources/config/swagger_php.xml create mode 100644 RouteDescriber/FosRestDescriber.php diff --git a/DependencyInjection/EXSystApiDocExtension.php b/DependencyInjection/EXSystApiDocExtension.php index f243ab1..3e0e4f4 100644 --- a/DependencyInjection/EXSystApiDocExtension.php +++ b/DependencyInjection/EXSystApiDocExtension.php @@ -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'); diff --git a/Describer/DefaultDescriber.php b/Describer/DefaultDescriber.php index 88a864e..3afcd0b 100644 --- a/Describer/DefaultDescriber.php +++ b/Describer/DefaultDescriber.php @@ -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())) { diff --git a/Resources/config/api_platform.xml b/Resources/config/api_platform.xml index 5d6a474..1d94baa 100644 --- a/Resources/config/api_platform.xml +++ b/Resources/config/api_platform.xml @@ -8,7 +8,7 @@ - + diff --git a/Resources/config/nelmio_apidoc.xml b/Resources/config/nelmio_apidoc.xml new file mode 100644 index 0000000..b750164 --- /dev/null +++ b/Resources/config/nelmio_apidoc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/Resources/config/php_doc.xml b/Resources/config/php_doc.xml new file mode 100644 index 0000000..d203ce5 --- /dev/null +++ b/Resources/config/php_doc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 26f39f9..c352ee3 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -9,12 +9,6 @@ - - %kernel.root_dir% - - - - @@ -30,16 +24,6 @@ - - - - - - - - - - diff --git a/Resources/config/swagger_php.xml b/Resources/config/swagger_php.xml new file mode 100644 index 0000000..86859d2 --- /dev/null +++ b/Resources/config/swagger_php.xml @@ -0,0 +1,14 @@ + + + + + + %kernel.root_dir% + + + + + + diff --git a/RouteDescriber/FosRestDescriber.php b/RouteDescriber/FosRestDescriber.php new file mode 100644 index 0000000..d1e19f9 --- /dev/null +++ b/RouteDescriber/FosRestDescriber.php @@ -0,0 +1,82 @@ +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(); + } + } +} diff --git a/RouteDescriber/NelmioAnnotationDescriber.php b/RouteDescriber/NelmioAnnotationDescriber.php index 2bc577c..c2237bf 100644 --- a/RouteDescriber/NelmioAnnotationDescriber.php +++ b/RouteDescriber/NelmioAnnotationDescriber.php @@ -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); diff --git a/RouteDescriber/PhpDocDescriber.php b/RouteDescriber/PhpDocDescriber.php index 7c73b53..90570bd 100644 --- a/RouteDescriber/PhpDocDescriber.php +++ b/RouteDescriber/PhpDocDescriber.php @@ -46,8 +46,12 @@ class PhpDocDescriber implements RouteDescriberInterface foreach ($this->getOperations($api, $route) as $operation) { if (null !== $docBlock) { - $operation->setSummary($docBlock->getSummary()); - $operation->setDescription((string) $docBlock->getDescription()); + 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); } diff --git a/RouteDescriber/RouteMetadataDescriber.php b/RouteDescriber/RouteMetadataDescriber.php index a32685b..84a48f1 100644 --- a/RouteDescriber/RouteMetadataDescriber.php +++ b/RouteDescriber/RouteMetadataDescriber.php @@ -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]); + } } } } diff --git a/Tests/Functional/FunctionalTest.php b/Tests/Functional/FunctionalTest.php index 203707f..b69c747 100644 --- a/Tests/Functional/FunctionalTest.php +++ b/Tests/Functional/FunctionalTest.php @@ -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()); diff --git a/composer.json b/composer.json index d4a997d..c1ad59e 100644 --- a/composer.json +++ b/composer.json @@ -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": {