option('verbose'); $this->docConfig = new DocumentationConfig(config('apidoc')); $this->baseUrl = $this->docConfig->get('base_url') ?? config('app.url'); URL::forceRootUrl($this->baseUrl); $routes = $routeMatcher->getRoutes($this->docConfig->get('routes'), $this->docConfig->get('router')); $generator = new Generator($this->docConfig); $parsedRoutes = $this->processRoutes($generator, $routes); $groupedRoutes = collect($parsedRoutes) ->groupBy('metadata.groupName') ->sortBy(static function ($group) { /* @var $group Collection */ return $group->first()['metadata']['groupName']; }, SORT_NATURAL); $writer = new Writer( $this, $this->docConfig, $this->option('force') ); $writer->writeDocs($groupedRoutes); } /** * @param \Mpociot\ApiDoc\Extracting\Generator $generator * @param Match[] $routes * * @throws \ReflectionException * * @return array */ private function processRoutes(Generator $generator, array $routes) { $parsedRoutes = []; foreach ($routes as $routeItem) { $route = $routeItem->getRoute(); /** @var Route $route */ $messageFormat = '%s route: [%s] %s'; $routeMethods = implode(',', $generator->getMethods($route)); $routePath = $generator->getUri($route); $routeControllerAndMethod = Utils::getRouteClassAndMethodNames($route->getAction()); if (! $this->isValidRoute($routeControllerAndMethod)) { $this->warn(sprintf($messageFormat, 'Skipping invalid', $routeMethods, $routePath)); continue; } if (! $this->doesControllerMethodExist($routeControllerAndMethod)) { $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath) . ': Controller method does not exist.'); continue; } if (! $this->isRouteVisibleForDocumentation($routeControllerAndMethod)) { $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath) . ': @hideFromAPIDocumentation was specified.'); continue; } try { $parsedRoutes[] = $generator->processRoute($route, $routeItem->getRules()); $this->info(sprintf($messageFormat, 'Processed', $routeMethods, $routePath)); } catch (\Exception $exception) { $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath) . '- Exception ' . get_class($exception) . ' encountered : ' . $exception->getMessage()); } } return $parsedRoutes; } /** * @param array $routeControllerAndMethod * * @return bool */ private function isValidRoute(array $routeControllerAndMethod = null) { if (is_array($routeControllerAndMethod)) { [$classOrObject, $method] = $routeControllerAndMethod; if (Utils::isInvokableObject($classOrObject)) { return true; } $routeControllerAndMethod = $classOrObject . '@' . $method; } return ! is_callable($routeControllerAndMethod) && ! is_null($routeControllerAndMethod); } /** * @param array $routeControllerAndMethod * * @throws ReflectionException * * @return bool */ private function doesControllerMethodExist(array $routeControllerAndMethod) { [$class, $method] = $routeControllerAndMethod; $reflection = new ReflectionClass($class); if (! $reflection->hasMethod($method)) { return false; } return true; } /** * @param array $routeControllerAndMethod * * @throws ReflectionException * * @return bool */ private function isRouteVisibleForDocumentation(array $routeControllerAndMethod) { $comment = Utils::reflectRouteMethod($routeControllerAndMethod)->getDocComment(); if ($comment) { $phpdoc = new DocBlock($comment); return collect($phpdoc->getTags()) ->filter(function ($tag) { return $tag->getName() === 'hideFromAPIDocumentation'; }) ->isEmpty(); } return true; } }