2016-07-28 10:20:59 +02:00
< ? php
/*
2016-12-29 12:09:26 +01:00
* This file is part of the NelmioApiDocBundle package .
2016-07-28 10:20:59 +02:00
*
2016-12-29 12:09:26 +01:00
* ( c ) Nelmio
2016-07-28 10:20:59 +02:00
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2016-12-29 12:09:26 +01:00
namespace Nelmio\ApiDocBundle\Describer ;
2016-07-28 10:20:59 +02:00
2017-03-16 19:35:04 +01:00
use Doctrine\Common\Annotations\Reader ;
use EXSyst\Component\Swagger\Swagger ;
use Nelmio\ApiDocBundle\Annotation\Operation ;
2017-01-14 17:36:56 +01:00
use Nelmio\ApiDocBundle\SwaggerPhp\AddDefaults ;
use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister ;
2017-01-23 19:46:38 +01:00
use Nelmio\ApiDocBundle\Util\ControllerReflector ;
2016-12-30 13:37:02 +01:00
use Swagger\Analysis ;
2017-06-26 10:34:42 +02:00
use Swagger\Annotations\AbstractAnnotation ;
2017-03-16 19:35:04 +01:00
use Swagger\Annotations as SWG ;
use Swagger\Context ;
2017-01-23 19:46:38 +01:00
use Symfony\Component\Routing\RouteCollection ;
2016-12-30 13:37:02 +01:00
2017-01-14 17:36:56 +01:00
final class SwaggerPhpDescriber extends ExternalDocDescriber implements ModelRegistryAwareInterface
2016-07-28 10:20:59 +02:00
{
2017-01-14 17:36:56 +01:00
use ModelRegistryAwareTrait ;
2017-01-23 19:46:38 +01:00
private $routeCollection ;
2017-12-22 17:42:18 +00:00
2017-01-23 19:46:38 +01:00
private $controllerReflector ;
2017-12-22 17:42:18 +00:00
2017-03-16 19:35:04 +01:00
private $annotationReader ;
2016-12-30 13:37:02 +01:00
2017-03-16 19:35:04 +01:00
public function __construct ( RouteCollection $routeCollection , ControllerReflector $controllerReflector , Reader $annotationReader , bool $overwrite = false )
2016-07-28 10:20:59 +02:00
{
2017-01-23 19:46:38 +01:00
$this -> routeCollection = $routeCollection ;
$this -> controllerReflector = $controllerReflector ;
2017-03-16 19:35:04 +01:00
$this -> annotationReader = $annotationReader ;
2017-01-14 17:36:56 +01:00
2017-01-23 19:46:38 +01:00
parent :: __construct ( function () {
2017-03-16 19:35:04 +01:00
$analysis = $this -> getAnnotations ();
$analysis -> process ( $this -> getProcessors ());
$analysis -> validate ();
return json_decode ( json_encode ( $analysis -> swagger ));
2016-07-29 10:22:40 +02:00
}, $overwrite );
2016-07-28 10:20:59 +02:00
}
2016-12-30 13:37:02 +01:00
2017-03-16 19:35:04 +01:00
private function getProcessors () : array
{
$processors = [
new AddDefaults (),
new ModelRegister ( $this -> modelRegistry ),
];
return array_merge ( $processors , Analysis :: processors ());
}
private function getAnnotations () : Analysis
{
$analysis = new Analysis ();
$operationAnnotations = [
'get' => SWG\Get :: class ,
'post' => SWG\Post :: class ,
'put' => SWG\Put :: class ,
'patch' => SWG\Patch :: class ,
'delete' => SWG\Delete :: class ,
'options' => SWG\Options :: class ,
'head' => SWG\Head :: class ,
];
foreach ( $this -> getMethodsToParse () as $method => list ( $path , $httpMethods )) {
$annotations = array_filter ( $this -> annotationReader -> getMethodAnnotations ( $method ), function ( $v ) {
return $v instanceof SWG\AbstractAnnotation ;
});
if ( 0 === count ( $annotations )) {
continue ;
}
$declaringClass = $method -> getDeclaringClass ();
$context = new Context ([
'namespace' => $method -> getNamespaceName (),
'class' => $declaringClass -> getShortName (),
'method' => $method -> name ,
'filename' => $method -> getFileName (),
]);
2017-06-02 21:30:31 +02:00
$nestedContext = clone $context ;
$nestedContext -> nested = true ;
2017-03-16 19:35:04 +01:00
$implicitAnnotations = [];
2017-03-17 19:45:46 +01:00
$tags = [];
2017-03-16 19:35:04 +01:00
foreach ( $annotations as $annotation ) {
$annotation -> _context = $context ;
2017-06-02 21:30:31 +02:00
$this -> updateNestedAnnotations ( $annotation , $nestedContext );
2017-03-16 19:35:04 +01:00
if ( $annotation instanceof Operation ) {
foreach ( $httpMethods as $httpMethod ) {
$annotationClass = $operationAnnotations [ $httpMethod ];
$operation = new $annotationClass ([ '_context' => $context ]);
$operation -> path = $path ;
$operation -> mergeProperties ( $annotation );
$analysis -> addAnnotation ( $operation , null );
}
continue ;
}
if ( $annotation instanceof SWG\Operation ) {
if ( null === $annotation -> path ) {
$annotation = clone $annotation ;
$annotation -> path = $path ;
}
$analysis -> addAnnotation ( $annotation , null );
continue ;
}
2017-03-17 19:45:46 +01:00
if ( $annotation instanceof SWG\Tag ) {
$annotation -> validate ();
$tags [] = $annotation -> name ;
continue ;
}
2017-03-16 19:35:04 +01:00
if ( ! $annotation instanceof SWG\Response && ! $annotation instanceof SWG\Parameter && ! $annotation instanceof SWG\ExternalDocumentation ) {
throw new \LogicException ( sprintf ( 'Using the annotation "%s" as a root annotation in "%s::%s()" is not allowed.' , get_class ( $annotation ), $method -> getDeclaringClass () -> name , $method -> name ));
}
$implicitAnnotations [] = $annotation ;
}
2017-03-17 19:45:46 +01:00
if ( 0 === count ( $implicitAnnotations ) && 0 === count ( $tags )) {
2017-03-16 19:35:04 +01:00
continue ;
}
foreach ( $httpMethods as $httpMethod ) {
$annotationClass = $operationAnnotations [ $httpMethod ];
2017-03-17 19:45:46 +01:00
$operation = new $annotationClass ([ '_context' => $context , 'path' => $path , 'value' => $implicitAnnotations , 'tags' => $tags ]);
2017-03-16 19:35:04 +01:00
$analysis -> addAnnotation ( $operation , null );
}
}
return $analysis ;
}
2017-03-17 19:37:41 +01:00
private function getMethodsToParse () : \Generator
2016-12-30 13:37:02 +01:00
{
2017-01-23 19:46:38 +01:00
foreach ( $this -> routeCollection -> all () as $route ) {
if ( ! $route -> hasDefault ( '_controller' )) {
continue ;
}
$controller = $route -> getDefault ( '_controller' );
if ( $callable = $this -> controllerReflector -> getReflectionClassAndMethod ( $controller )) {
list ( $class , $method ) = $callable ;
2017-03-16 19:35:04 +01:00
$path = $this -> normalizePath ( $route -> getPath ());
$httpMethods = $route -> getMethods () ? : Swagger :: $METHODS ;
$httpMethods = array_map ( 'strtolower' , $httpMethods );
2017-01-23 19:46:38 +01:00
2017-03-16 19:35:04 +01:00
yield $method => [ $path , $httpMethods ];
2017-01-23 19:46:38 +01:00
}
}
2016-12-30 13:37:02 +01:00
}
2017-01-14 17:36:56 +01:00
2017-03-17 19:37:41 +01:00
private function normalizePath ( string $path ) : string
2017-01-14 17:36:56 +01:00
{
2017-12-17 10:44:07 +01:00
if ( '.{_format}' === substr ( $path , - 10 )) {
2017-03-16 19:35:04 +01:00
$path = substr ( $path , 0 , - 10 );
}
2017-01-14 17:36:56 +01:00
2017-03-16 19:35:04 +01:00
return $path ;
2017-01-14 17:36:56 +01:00
}
2017-06-02 21:30:31 +02:00
2017-06-26 10:34:42 +02:00
private function updateNestedAnnotations ( $value , Context $context )
{
2017-06-02 21:30:31 +02:00
if ( $value instanceof AbstractAnnotation ) {
$value -> _context = $context ;
} elseif ( ! is_array ( $value )) {
return ;
}
foreach ( $value as $v ) {
$this -> updateNestedAnnotations ( $v , $context );
}
}
2016-07-28 10:20:59 +02:00
}