mirror of
https://github.com/retailcrm/NelmioApiDocBundle.git
synced 2025-02-02 23:59:26 +03:00
Initialize
This commit is contained in:
parent
92dbf2f617
commit
827d5ea152
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/vendor/*
|
||||
composer.lock
|
||||
.php_cs.cache
|
27
.php_cs
Normal file
27
.php_cs
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Symfony\CS\Config\Config;
|
||||
use Symfony\CS\Finder\DefaultFinder;
|
||||
use Symfony\CS\Fixer\Contrib\HeaderCommentFixer;
|
||||
use Symfony\CS\FixerInterface;
|
||||
|
||||
$finder = DefaultFinder::create()
|
||||
->in(__DIR__)
|
||||
;
|
||||
|
||||
$header = <<<EOF
|
||||
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.
|
||||
EOF;
|
||||
HeaderCommentFixer::setHeader($header);
|
||||
|
||||
return Config::create()
|
||||
->level(FixerInterface::SYMFONY_LEVEL)
|
||||
->fixers(array('align_double_arrow', 'header_comment'))
|
||||
->finder($finder)
|
||||
->setUsingCache(true)
|
||||
;
|
29
.travis.yml
Normal file
29
.travis.yml
Normal file
@ -0,0 +1,29 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- hhvm
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^\d+\.\d+$/
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- php: 5.5
|
||||
env: COMPOSER_FLAGS="--prefer-lowest"
|
||||
|
||||
before_install:
|
||||
- composer self-update
|
||||
|
||||
install: composer update $COMPOSER_FLAGS --prefer-dist
|
41
ApiDocGenerator.php
Normal file
41
ApiDocGenerator.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?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;
|
||||
|
||||
use EXSyst\Bundle\ApiDocBundle\Extractor\ExtractorInterface;
|
||||
use gossi\swagger\Swagger;
|
||||
|
||||
class ApiDocGenerator
|
||||
{
|
||||
private $extractors;
|
||||
|
||||
/**
|
||||
* @param ExtractorInterface[] $extractors
|
||||
*/
|
||||
public function __construct(array $extractors)
|
||||
{
|
||||
$this->extractors = $extractors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Swagger
|
||||
*/
|
||||
public function extract()
|
||||
{
|
||||
$swagger = new Swagger();
|
||||
foreach ($this->extractors as $extractor) {
|
||||
$extractor->extractIn($swagger);
|
||||
}
|
||||
|
||||
return $swagger;
|
||||
}
|
||||
}
|
19
Extractor/ExtractorInterface.php
Normal file
19
Extractor/ExtractorInterface.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?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\Extractor;
|
||||
|
||||
use gossi\swagger\Swagger;
|
||||
|
||||
interface ExtractorInterface
|
||||
{
|
||||
public function extractIn(Swagger $api);
|
||||
}
|
113
Extractor/RouteExtractor.php
Normal file
113
Extractor/RouteExtractor.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?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\Extractor;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use EXSyst\Bundle\ApiDocBundle\Extractor\Routing\RouteExtractorInterface;
|
||||
use gossi\swagger\Swagger;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser;
|
||||
use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
class RouteExtractor implements ExtractorInterface
|
||||
{
|
||||
private $routeExtractors;
|
||||
|
||||
/**
|
||||
* @param RouterInterface $router
|
||||
* @param ControllerNameParser $controllerNameParser
|
||||
* @param RouteExtractorInterface[] $extractors
|
||||
*/
|
||||
public function __construct(RouterInterface $router, ControllerNameParser $controllerNameParser, array $routeExtractors)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->controllerNameParser = $controllerNameParser;
|
||||
$this->routeExtractors = $routeExtractors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Swagger
|
||||
*/
|
||||
public function extract()
|
||||
{
|
||||
if (0 === count($this->routeExtractors)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$swagger = new Swagger();
|
||||
foreach ($this->getRoutes() as $route) {
|
||||
// if able to resolve the controller
|
||||
if ($method = $this->getReflectionMethod($route->getDefault('_controller'))) {
|
||||
// Extract as many informations as possible about this route
|
||||
foreach ($this->routeExtractors as $extractor) {
|
||||
$extractor->extractIn($swagger, $route, $method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $swagger;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param string $controller
|
||||
*
|
||||
* @return \ReflectionMethod|null
|
||||
*/
|
||||
private function getReflectionMethod($controller)
|
||||
{
|
||||
if (false === strpos($controller, '::') && 2 === substr_count($controller, ':')) {
|
||||
$controller = $this->controllerNameParser->parse($controller);
|
||||
}
|
||||
|
||||
if (preg_match('#(.+)::([\w]+)#', $controller, $matches)) {
|
||||
$class = $matches[1];
|
||||
$method = $matches[2];
|
||||
} elseif (class_exists($controller)) {
|
||||
$class = $controller;
|
||||
$method = '__invoke';
|
||||
} else {
|
||||
if (preg_match('#(.+):([\w]+)#', $controller, $matches)) {
|
||||
$controller = $matches[1];
|
||||
$method = $matches[2];
|
||||
}
|
||||
|
||||
if ($this->container->has($controller)) {
|
||||
if (class_exists(ClassUtils::class)) {
|
||||
$class = ClassUtils::getRealClass(get_class($this->container->get($controller)));
|
||||
}
|
||||
|
||||
if (!isset($method) && method_exists($class, '__invoke')) {
|
||||
$method = '__invoke';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($class) && isset($method)) {
|
||||
try {
|
||||
return new \ReflectionMethod($class, $method);
|
||||
} catch (\ReflectionException $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
125
Extractor/Routing/NelmioAnnotationExtractor.php
Normal file
125
Extractor/Routing/NelmioAnnotationExtractor.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?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\Extractor\Routing;
|
||||
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use gossi\swagger\Parameter;
|
||||
use gossi\swagger\Swagger;
|
||||
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
class NelmioAnnotationExtractor implements RouteExtractorInterface
|
||||
{
|
||||
use RouteExtractorTrait;
|
||||
|
||||
private $annotationReader;
|
||||
private $nelmioLoaded;
|
||||
|
||||
public function __construct(Reader $annotationReader)
|
||||
{
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->nelmioLoaded = class_exists(ApiDoc::class);
|
||||
}
|
||||
|
||||
public function extractIn(Swagger $api, Route $route, \ReflectionMethod $reflectionMethod)
|
||||
{
|
||||
if (!$this->nelmioLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$annotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, ApiDoc::class);
|
||||
// some fields aren't available otherwise
|
||||
$annotationArray = $annotation->toArray();
|
||||
if (null === $annotation) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->getOperations($api, $route) as $operation) {
|
||||
if ($annotation->getDescription()) {
|
||||
$operation->setDescription($annotation->getDescription());
|
||||
}
|
||||
if (null !== $annotation->getDeprecated()) {
|
||||
$operation->setDeprecated($operation->getDeprecated || $annotation->getDeprecated());
|
||||
}
|
||||
|
||||
// Request parameters
|
||||
foreach ($annotation->getParameters() as $name => $configuration) {
|
||||
$parameter = $operation->getParameters()->get($name, 'formData');
|
||||
if (isset($configuration['required'])) {
|
||||
$parameter->setRequired($parameter->getRequired() || $configuration['required']);
|
||||
}
|
||||
|
||||
$this->configureParameter($parameter, $configuration);
|
||||
}
|
||||
|
||||
// Query parameters
|
||||
foreach ($annotation->getRequirements() as $name => $configuration) {
|
||||
$parameter = $operation->getParameters()->get($name, 'query');
|
||||
$parameter->setRequired(true);
|
||||
|
||||
$this->configureParameter($parameter, $configuration);
|
||||
}
|
||||
foreach ($annotation->getFilters() as $name => $configuration) {
|
||||
$parameter = $operation->getParameters()->get($name, 'query');
|
||||
$this->configureParameter($parameter, $configuration);
|
||||
}
|
||||
|
||||
// External docs
|
||||
if (isset($annotationArray['link'])) {
|
||||
$operation->getExternalDocs()->setUrl($annotationArray['link']);
|
||||
}
|
||||
|
||||
// Responses
|
||||
if (isset($annotationArray['statusCodes'])) {
|
||||
$responses = $operation->getResponses();
|
||||
foreach ($annotationArray['statusCodes'] as $statusCode => $description) {
|
||||
$response = $responses->get($statusCode);
|
||||
$response->setDescription($description);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function configureParameter(Parameter $parameter, array $configuration)
|
||||
{
|
||||
$dataType = null;
|
||||
if (isset($configuration['dataType'])) {
|
||||
$dataType = $configuration['dataType'];
|
||||
} elseif ($configuration['requirement']) {
|
||||
$dataType = $configuration['requirement'];
|
||||
}
|
||||
|
||||
if ('[]' === substr($requirement, -2)) {
|
||||
$parameter->setType('array');
|
||||
$items = $parameter;
|
||||
do {
|
||||
$items->setCollectionFormat('multi');
|
||||
$requirement = substr($requirement, 0, -2);
|
||||
|
||||
$items = $items->getItems();
|
||||
} while ('[]' === substr($requirement, -2));
|
||||
|
||||
$items->setType(Swagger::T_STRING);
|
||||
$items->setFormat($requirement);
|
||||
} else {
|
||||
$parameter->setType(Swagger::T_STRING);
|
||||
$parameter->setFormat($requirement);
|
||||
}
|
||||
|
||||
if (isset($configuration['description'])) {
|
||||
$parameter->setDescription($configuration['description']);
|
||||
}
|
||||
if (isset($configuration['default'])) {
|
||||
$parameter->setDefault($configuration['default']);
|
||||
}
|
||||
}
|
||||
}
|
20
Extractor/Routing/RouteExtractorInterface.php
Normal file
20
Extractor/Routing/RouteExtractorInterface.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?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\Extractor\Routing;
|
||||
|
||||
use gossi\swagger\Swagger;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
interface RouteExtractorInterface
|
||||
{
|
||||
public function extractIn(Swagger $api, Route $route, \ReflectionMethod $reflectionMethod);
|
||||
}
|
43
Extractor/Routing/RouteExtractorTrait.php
Normal file
43
Extractor/Routing/RouteExtractorTrait.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?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\Extractor\Routing;
|
||||
|
||||
use gossi\swagger\Operation;
|
||||
use gossi\swagger\Swagger;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait RouteExtractorTrait
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return Operation[]
|
||||
*/
|
||||
private function getOperations(Swagger $api, Route $route)
|
||||
{
|
||||
$path = $swagger->getPaths()->get($route->getPath());
|
||||
$methods = $route->getMethods() ?: Swagger::$METHODS;
|
||||
foreach ($methods as $method) {
|
||||
$method = strtolower($method);
|
||||
if (!in_array($method, Swagger::$METHODS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$operations[] = $path->getOperation($method);
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
}
|
34
Extractor/Routing/RouteMetadataExtractor.php
Normal file
34
Extractor/Routing/RouteMetadataExtractor.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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\Extractor\Routing;
|
||||
|
||||
use gossi\swagger\Swagger;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
class RouteMetadataExtractor implements RouteExtractorInterface
|
||||
{
|
||||
use RouteExtractorTrait;
|
||||
|
||||
public function extractIn(Swagger $api, Route $route, \ReflectionMethod $reflectionMethod)
|
||||
{
|
||||
foreach ($this->getOperations($api, $route) as $operation) {
|
||||
$operation->getSchemes()->addAll($route->getSchemes());
|
||||
|
||||
foreach ($route->getRequirements() as $parameterName => $requirement) {
|
||||
$parameter = $operation->getParameters()->get($parameterName, 'path');
|
||||
$parameter->setRequired(true);
|
||||
$parameter->setType(swagger\Swagger::T_STRING);
|
||||
$parameter->setFormat($requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
composer.json
Normal file
33
composer.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "exsyst/api-doc-bundle",
|
||||
"type": "symfony-bundle",
|
||||
"description": "[WIP] Generates Swagger docs from several sources",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "EXSyst"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"symfony/framework-bundle": "^2.7|^3.0",
|
||||
"gossi/swagger": "^0.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"nelmio/api-doc-bundle": "^2.0",
|
||||
"symfony/phpunit-bridge": "^2.7|^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"nelmio/api-doc-bundle": "For using the ApiDoc annotation."
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"EXSyst\\Bundle\\ApiDocBundle\\": ""
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
26
phpunit.xml.dist
Normal file
26
phpunit.xml.dist
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
</php>
|
||||
<testsuites>
|
||||
<testsuite name="EXSyst Api Doc Bundle Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./vendor</directory>
|
||||
<directory>./Tests/</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
Loading…
x
Reference in New Issue
Block a user