routeMatcher = $routeMatcher; } /** * Execute the console command. * * @return false|null */ public function handle() { $routes = config('apidoc.router') == 'dingo' ? $this->routeMatcher->getDingoRoutesToBeDocumented(config('apidoc.routes')) : $this->routeMatcher->getLaravelRoutesToBeDocumented(config('apidoc.routes')); if ($this->option('router') === 'laravel') { $generator = new LaravelGenerator(); } else { $generator = new DingoGenerator(); } $parsedRoutes = $this->processRoutes($generator, $routes); $parsedRoutes = collect($parsedRoutes)->groupBy('resource') ->sort(function ($a, $b) { return strcmp($a->first()['resource'], $b->first()['resource']); }); $this->writeMarkdown($parsedRoutes); } /** * @param Collection $parsedRoutes * * @return void */ private function writeMarkdown($parsedRoutes) { $outputPath = $this->option('output'); $targetFile = $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'index.md'; $compareFile = $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'.compare.md'; $prependFile = $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'prepend.md'; $appendFile = $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'append.md'; $infoText = view('apidoc::partials.info') ->with('outputPath', ltrim($outputPath, 'public/')) ->with('showPostmanCollectionButton', ! $this->option('noPostmanCollection')); $parsedRouteOutput = $parsedRoutes->map(function ($routeGroup) { return $routeGroup->map(function ($route) { $route['output'] = (string) view('apidoc::partials.route')->with('parsedRoute', $route)->render(); return $route; }); }); $frontmatter = view('apidoc::partials.frontmatter'); /* * In case the target file already exists, we should check if the documentation was modified * and skip the modified parts of the routes. */ if (file_exists($targetFile) && file_exists($compareFile)) { $generatedDocumentation = file_get_contents($targetFile); $compareDocumentation = file_get_contents($compareFile); if (preg_match('/---(.*)---\\s/is', $generatedDocumentation, $generatedFrontmatter)) { $frontmatter = trim($generatedFrontmatter[1], "\n"); } $parsedRouteOutput->transform(function ($routeGroup) use ($generatedDocumentation, $compareDocumentation) { return $routeGroup->transform(function ($route) use ($generatedDocumentation, $compareDocumentation) { if (preg_match('/(.*)/is', $generatedDocumentation, $routeMatch)) { $routeDocumentationChanged = (preg_match('/(.*)/is', $compareDocumentation, $compareMatch) && $compareMatch[1] !== $routeMatch[1]); if ($routeDocumentationChanged === false || $this->option('force')) { if ($routeDocumentationChanged) { $this->warn('Discarded manual changes for route ['.implode(',', $route['methods']).'] '.$route['uri']); } } else { $this->warn('Skipping modified route ['.implode(',', $route['methods']).'] '.$route['uri']); $route['modified_output'] = $routeMatch[0]; } } return $route; }); }); } $prependFileContents = file_exists($prependFile) ? file_get_contents($prependFile)."\n" : ''; $appendFileContents = file_exists($appendFile) ? "\n".file_get_contents($appendFile) : ''; $documentarian = new Documentarian(); $markdown = view('apidoc::documentarian') ->with('writeCompareFile', false) ->with('frontmatter', $frontmatter) ->with('infoText', $infoText) ->with('prependMd', $prependFileContents) ->with('appendMd', $appendFileContents) ->with('outputPath', $this->option('output')) ->with('showPostmanCollectionButton', ! $this->option('noPostmanCollection')) ->with('parsedRoutes', $parsedRouteOutput); if (! is_dir($outputPath)) { $documentarian->create($outputPath); } // Write output file file_put_contents($targetFile, $markdown); // Write comparable markdown file $compareMarkdown = view('apidoc::documentarian') ->with('writeCompareFile', true) ->with('frontmatter', $frontmatter) ->with('infoText', $infoText) ->with('prependMd', $prependFileContents) ->with('appendMd', $appendFileContents) ->with('outputPath', $this->option('output')) ->with('showPostmanCollectionButton', ! $this->option('noPostmanCollection')) ->with('parsedRoutes', $parsedRouteOutput); file_put_contents($compareFile, $compareMarkdown); $this->info('Wrote index.md to: '.$outputPath); $this->info('Generating API HTML code'); $documentarian->generate($outputPath); $this->info('Wrote HTML documentation to: '.$outputPath.'/index.html'); if ($this->option('noPostmanCollection') !== true) { $this->info('Generating Postman collection'); file_put_contents($outputPath.DIRECTORY_SEPARATOR.'collection.json', $this->generatePostmanCollection($parsedRoutes)); } } /** * @param AbstractGenerator $generator * @param array $routes * * @return array */ private function processRoutes(AbstractGenerator $generator, array $routes) { $parsedRoutes = []; foreach ($routes as ['route' => $route, 'apply' => $apply]) { /** @var Route $route */ if ($this->isValidRoute($route) && $this->isRouteVisibleForDocumentation($route->getAction()['uses'])) { $parsedRoutes[] = $generator->processRoute($route, $apply); $this->info('Processed route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route)); } else { $this->warn('Skipping route: ['.implode(',', $generator->getMethods($route)).'] '.$generator->getUri($route)); } } return $parsedRoutes; } /** * @param $route * * @return bool */ private function isValidRoute(Route $route) { return ! is_callable($route->getAction()['uses']) && ! is_null($route->getAction()['uses']); } /** * @param $route * * @return bool */ private function isRouteVisibleForDocumentation($route) { list($class, $method) = explode('@', $route); $reflection = new ReflectionClass($class); $comment = $reflection->getMethod($method)->getDocComment(); if ($comment) { $phpdoc = new DocBlock($comment); return collect($phpdoc->getTags()) ->filter(function ($tag) use ($route) { return $tag->getName() === 'hideFromAPIDocumentation'; }) ->isEmpty(); } return true; } /** * Generate Postman collection JSON file. * * @param Collection $routes * * @return string */ private function generatePostmanCollection(Collection $routes) { $writer = new CollectionWriter($routes); return $writer->getCollection(); } }