routeCollection = $routeCollection; $this->controllerReflector = $controllerReflector; $this->annotationReader = $annotationReader; $this->logger = $logger; $this->overwrite = $overwrite; } public function describe(OA\OpenApi $api) { $classAnnotations = []; /** @var \ReflectionMethod $method */ foreach ($this->getMethodsToParse() as $method => list($path, $httpMethods, $routeName)) { $declaringClass = $method->getDeclaringClass(); $path = Util::getPath($api, $path); $context = Util::createContext(['nested' => $path], $path->_context); $context->namespace = $declaringClass->getNamespaceName(); $context->class = $declaringClass->getShortName(); $context->method = $method->name; $context->filename = $method->getFileName(); $this->setContext($context); if (!array_key_exists($declaringClass->getName(), $classAnnotations)) { $classAnnotations = array_filter($this->annotationReader->getClassAnnotations($declaringClass), function ($v) { return $v instanceof OA\AbstractAnnotation; }); $classAnnotations = array_merge($classAnnotations, $this->getAttributesAsAnnotation($declaringClass, $context)); $classAnnotations[$declaringClass->getName()] = $classAnnotations; } $annotations = array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) { return $v instanceof OA\AbstractAnnotation; }); $annotations = array_merge($annotations, $this->getAttributesAsAnnotation($method, $context)); if (0 === count($annotations) && 0 === count($classAnnotations[$declaringClass->getName()])) { continue; } $implicitAnnotations = []; $mergeProperties = new \stdClass(); foreach (array_merge($annotations, $classAnnotations[$declaringClass->getName()]) as $annotation) { if ($annotation instanceof Operation) { foreach ($httpMethods as $httpMethod) { $operation = Util::getOperation($path, $httpMethod); $operation->mergeProperties($annotation); } continue; } if ($annotation instanceof OA\Operation) { if (!in_array($annotation->method, $httpMethods, true)) { continue; } if (Generator::UNDEFINED !== $annotation->path && $path->path !== $annotation->path) { continue; } $operation = Util::getOperation($path, $annotation->method); $operation->mergeProperties($annotation); continue; } if ($annotation instanceof Security) { $annotation->validate(); if (null === $annotation->name) { $mergeProperties->security = []; continue; } $mergeProperties->security[] = [$annotation->name => $annotation->scopes]; continue; } if ($annotation instanceof OA\Tag) { $annotation->validate(); $mergeProperties->tags[] = $annotation->name; continue; } if ( !$annotation instanceof OA\Response && !$annotation instanceof OA\RequestBody && !$annotation instanceof OA\Parameter && !$annotation instanceof OA\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; } if (empty($implicitAnnotations) && empty(get_object_vars($mergeProperties))) { continue; } foreach ($httpMethods as $httpMethod) { $operation = Util::getOperation($path, $httpMethod); $operation->merge($implicitAnnotations); $operation->mergeProperties($mergeProperties); if (Generator::UNDEFINED === $operation->operationId) { $operation->operationId = $httpMethod.'_'.$routeName; } } } // Reset the Generator after the parsing $this->setContext(null); } private function getMethodsToParse(): \Generator { foreach ($this->routeCollection->all() as $routeName => $route) { if (!$route->hasDefault('_controller')) { continue; } $controller = $route->getDefault('_controller'); $reflectedMethod = $this->controllerReflector->getReflectionMethod($controller); if (null === $reflectedMethod) { continue; } $path = $this->normalizePath($route->getPath()); $supportedHttpMethods = $this->getSupportedHttpMethods($route); if (empty($supportedHttpMethods)) { $this->logger->warning('None of the HTTP methods specified for path {path} are supported by swagger-ui, skipping this path', [ 'path' => $path, ]); continue; } yield $reflectedMethod => [$path, $supportedHttpMethods, $routeName]; } } private function getSupportedHttpMethods(Route $route): array { $allMethods = Util::OPERATIONS; $methods = array_map('strtolower', $route->getMethods()); return array_intersect($methods ?: $allMethods, $allMethods); } private function normalizePath(string $path): string { if ('.{_format}' === substr($path, -10)) { $path = substr($path, 0, -10); } return $path; } /** * @param \ReflectionClass|\ReflectionMethod $reflection * * @return OA\AbstractAnnotation[] */ private function getAttributesAsAnnotation($reflection, \OpenApi\Context $context): array { $attributesFactory = new AttributeAnnotationFactory(); $attributes = $attributesFactory->build($reflection, $context); // The attributes factory removes the context after executing so we need to set it back... $this->setContext($context); return $attributes; } }