vendor/nelmio/api-doc-bundle/Describer/SwaggerPhpDescriber.php line 54

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the NelmioApiDocBundle package.
  4.  *
  5.  * (c) Nelmio
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Nelmio\ApiDocBundle\Describer;
  11. use Doctrine\Common\Annotations\Reader;
  12. use EXSyst\Component\Swagger\Swagger;
  13. use Nelmio\ApiDocBundle\Annotation\Operation;
  14. use Nelmio\ApiDocBundle\Annotation\Security;
  15. use Nelmio\ApiDocBundle\SwaggerPhp\AddDefaults;
  16. use Nelmio\ApiDocBundle\SwaggerPhp\ModelRegister;
  17. use Nelmio\ApiDocBundle\Util\ControllerReflector;
  18. use Psr\Log\LoggerInterface;
  19. use Swagger\Analysis;
  20. use Swagger\Annotations\AbstractAnnotation;
  21. use Swagger\Annotations as SWG;
  22. use Swagger\Context;
  23. use Symfony\Component\Routing\RouteCollection;
  24. // Help opcache.preload discover Swagger\Annotations\Swagger
  25. class_exists(SWG\Swagger::class);
  26. final class SwaggerPhpDescriber implements ModelRegistryAwareInterface
  27. {
  28.     use ModelRegistryAwareTrait;
  29.     private $routeCollection;
  30.     private $controllerReflector;
  31.     private $annotationReader;
  32.     private $logger;
  33.     private $overwrite;
  34.     public function __construct(RouteCollection $routeCollectionControllerReflector $controllerReflectorReader $annotationReaderLoggerInterface $loggerbool $overwrite false)
  35.     {
  36.         $this->routeCollection $routeCollection;
  37.         $this->controllerReflector $controllerReflector;
  38.         $this->annotationReader $annotationReader;
  39.         $this->logger $logger;
  40.         $this->overwrite $overwrite;
  41.     }
  42.     public function describe(Swagger $api)
  43.     {
  44.         $analysis $this->getAnnotations($api);
  45.         $analysis->process($this->getProcessors());
  46.         $analysis->validate();
  47.         $api->merge(json_decode(json_encode($analysis->swagger), true), $this->overwrite);
  48.     }
  49.     private function getProcessors(): array
  50.     {
  51.         $processors = [
  52.             new AddDefaults(),
  53.             new ModelRegister($this->modelRegistry),
  54.         ];
  55.         return array_merge($processorsAnalysis::processors());
  56.     }
  57.     private function getAnnotations(Swagger $api): Analysis
  58.     {
  59.         $analysis = new Analysis();
  60.         $analysis->addAnnotation(new class($api) extends SWG\Swagger {
  61.             private $api;
  62.             public function __construct(Swagger $api)
  63.             {
  64.                 $this->api $api;
  65.                 parent::__construct([]);
  66.             }
  67.             /**
  68.              * Support definitions from the config and reference to models.
  69.              */
  70.             public function ref($ref)
  71.             {
  72.                 if (=== strpos($ref'#/definitions/') && $this->api->getDefinitions()->has(substr($ref14))) {
  73.                     return;
  74.                 }
  75.                 if (=== strpos($ref'#/parameters/') && isset($this->api->getParameters()[substr($ref13)])) {
  76.                     return;
  77.                 }
  78.                 if (=== strpos($ref'#/responses/') && $this->api->getResponses()->has(substr($ref12))) {
  79.                     return;
  80.                 }
  81.                 parent::ref($ref);
  82.             }
  83.         }, null);
  84.         $operationAnnotations = [
  85.             'get' => SWG\Get::class,
  86.             'post' => SWG\Post::class,
  87.             'put' => SWG\Put::class,
  88.             'patch' => SWG\Patch::class,
  89.             'delete' => SWG\Delete::class,
  90.             'options' => SWG\Options::class,
  91.             'head' => SWG\Head::class,
  92.         ];
  93.         $classAnnotations = [];
  94.         foreach ($this->getMethodsToParse() as $method => list($path$httpMethods)) {
  95.             $declaringClass $method->getDeclaringClass();
  96.             if (!array_key_exists($declaringClass->getName(), $classAnnotations)) {
  97.                 $classAnnotations array_filter($this->annotationReader->getClassAnnotations($declaringClass), function ($v) {
  98.                     return $v instanceof SWG\AbstractAnnotation;
  99.                 });
  100.                 $classAnnotations[$declaringClass->getName()] = $classAnnotations;
  101.             }
  102.             $annotations array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) {
  103.                 return $v instanceof SWG\AbstractAnnotation;
  104.             });
  105.             if (=== count($annotations) && === count($classAnnotations[$declaringClass->getName()])) {
  106.                 continue;
  107.             }
  108.             $context = new Context([
  109.                 'namespace' => $method->getNamespaceName(),
  110.                 'class' => $declaringClass->getShortName(),
  111.                 'method' => $method->name,
  112.                 'filename' => $method->getFileName(),
  113.             ]);
  114.             $nestedContext = clone $context;
  115.             $nestedContext->nested true;
  116.             $implicitAnnotations = [];
  117.             $operations = [];
  118.             $tags = [];
  119.             $security = [];
  120.             foreach (array_merge($annotations$classAnnotations[$declaringClass->getName()]) as $annotation) {
  121.                 $annotation->_context $context;
  122.                 $this->updateNestedAnnotations($annotation$nestedContext);
  123.                 if ($annotation instanceof Operation) {
  124.                     foreach ($httpMethods as $httpMethod) {
  125.                         $annotationClass $operationAnnotations[$httpMethod];
  126.                         $operation = new $annotationClass(['_context' => $context]);
  127.                         $operation->path $path;
  128.                         $operation->mergeProperties($annotation);
  129.                         $operations[$httpMethod] = $operation;
  130.                         $analysis->addAnnotation($operationnull);
  131.                     }
  132.                     continue;
  133.                 }
  134.                 if ($annotation instanceof SWG\Operation) {
  135.                     if (null === $annotation->path) {
  136.                         $annotation = clone $annotation;
  137.                         $annotation->path $path;
  138.                     }
  139.                     $operations[$annotation->method] = $annotation;
  140.                     $analysis->addAnnotation($annotationnull);
  141.                     continue;
  142.                 }
  143.                 if ($annotation instanceof Security) {
  144.                     $annotation->validate();
  145.                     $security[] = [$annotation->name => []];
  146.                     continue;
  147.                 }
  148.                 if ($annotation instanceof SWG\Tag) {
  149.                     $annotation->validate();
  150.                     $tags[] = $annotation->name;
  151.                     continue;
  152.                 }
  153.                 if (!$annotation instanceof SWG\Response && !$annotation instanceof SWG\Parameter && !$annotation instanceof SWG\ExternalDocumentation) {
  154.                     throw new \LogicException(sprintf('Using the annotation "%s" as a root annotation in "%s::%s()" is not allowed. It should probably be nested in a `@SWG\Response` or `@SWG\Parameter` annotation.'get_class($annotation), $method->getDeclaringClass()->name$method->name));
  155.                 }
  156.                 $implicitAnnotations[] = $annotation;
  157.             }
  158.             if (=== count($implicitAnnotations) && === count($tags) && === count($security)) {
  159.                 continue;
  160.             }
  161.             // Registers new annotations
  162.             $analysis->addAnnotations($implicitAnnotationsnull);
  163.             foreach ($httpMethods as $httpMethod) {
  164.                 $annotationClass $operationAnnotations[$httpMethod];
  165.                 $constructorArg = [
  166.                     '_context' => $context,
  167.                     'path' => $path,
  168.                     'value' => $implicitAnnotations,
  169.                 ];
  170.                 if (!== count($tags)) {
  171.                     $constructorArg['tags'] = $tags;
  172.                 }
  173.                 if (!== count($security)) {
  174.                     $constructorArg['security'] = $security;
  175.                 }
  176.                 $operation = new $annotationClass($constructorArg);
  177.                 if (isset($operations[$httpMethod])) {
  178.                     $operations[$httpMethod]->mergeProperties($operation);
  179.                 } else {
  180.                     $analysis->addAnnotation($operationnull);
  181.                 }
  182.             }
  183.         }
  184.         return $analysis;
  185.     }
  186.     private function getMethodsToParse(): \Generator
  187.     {
  188.         foreach ($this->routeCollection->all() as $route) {
  189.             if (!$route->hasDefault('_controller')) {
  190.                 continue;
  191.             }
  192.             $controller $route->getDefault('_controller');
  193.             if ($method $this->controllerReflector->getReflectionMethod($controller)) {
  194.                 $path $this->normalizePath($route->getPath());
  195.                 $httpMethods $route->getMethods() ?: Swagger::$METHODS;
  196.                 $httpMethods array_map('strtolower'$httpMethods);
  197.                 $supportedHttpMethods array_intersect($httpMethodsSwagger::$METHODS);
  198.                 if (empty($supportedHttpMethods)) {
  199.                     $this->logger->warning('None of the HTTP methods specified for path {path} are supported by swagger-ui, skipping this path', [
  200.                         'path' => $path,
  201.                         'methods' => $httpMethods,
  202.                     ]);
  203.                     continue;
  204.                 }
  205.                 yield $method => [$path$supportedHttpMethods];
  206.             }
  207.         }
  208.     }
  209.     private function normalizePath(string $path): string
  210.     {
  211.         if ('.{_format}' === substr($path, -10)) {
  212.             $path substr($path0, -10);
  213.         }
  214.         return $path;
  215.     }
  216.     private function updateNestedAnnotations($valueContext $context)
  217.     {
  218.         if ($value instanceof AbstractAnnotation) {
  219.             $value->_context $context;
  220.         } elseif (!is_array($value)) {
  221.             return;
  222.         }
  223.         foreach ($value as $v) {
  224.             $this->updateNestedAnnotations($v$context);
  225.         }
  226.     }
  227. }