GenerateDocumentation.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. namespace Mpociot\ApiDoc\Commands;
  3. use ReflectionClass;
  4. use ReflectionException;
  5. use Illuminate\Routing\Route;
  6. use Illuminate\Console\Command;
  7. use Mpociot\ApiDoc\Tools\Flags;
  8. use Mpociot\ApiDoc\Tools\Utils;
  9. use Mpociot\Reflection\DocBlock;
  10. use Illuminate\Support\Collection;
  11. use Mpociot\ApiDoc\Writing\Writer;
  12. use Illuminate\Support\Facades\URL;
  13. use Mpociot\ApiDoc\Extracting\Generator;
  14. use Mpociot\ApiDoc\Matching\RouteMatcher;
  15. use Mpociot\ApiDoc\Tools\DocumentationConfig;
  16. class GenerateDocumentation extends Command
  17. {
  18. /**
  19. * The name and signature of the console command.
  20. *
  21. * @var string
  22. */
  23. protected $signature = 'apidoc:generate
  24. {--force : Force rewriting of existing routes}
  25. ';
  26. /**
  27. * The console command description.
  28. *
  29. * @var string
  30. */
  31. protected $description = 'Generate your API documentation from existing Laravel routes.';
  32. /**
  33. * @var DocumentationConfig
  34. */
  35. private $docConfig;
  36. /**
  37. * @var string
  38. */
  39. private $baseUrl;
  40. public function __construct()
  41. {
  42. parent::__construct();
  43. }
  44. /**
  45. * Execute the console command.
  46. *
  47. * @return void
  48. */
  49. public function handle()
  50. {
  51. // Using a global static variable here, so fuck off if you don't like it.
  52. // Also, the --verbose option is included with all Artisan commands.
  53. Flags::$shouldBeVerbose = $this->option('verbose');
  54. $this->docConfig = new DocumentationConfig(config('apidoc'));
  55. $this->baseUrl = $this->docConfig->get('base_url') ?? config('app.url');
  56. URL::forceRootUrl($this->baseUrl);
  57. $routeMatcher = new RouteMatcher($this->docConfig->get('routes'), $this->docConfig->get('router'));
  58. $routes = $routeMatcher->getRoutes();
  59. $generator = new Generator($this->docConfig);
  60. $parsedRoutes = $this->processRoutes($generator, $routes);
  61. $groupedRoutes = collect($parsedRoutes)
  62. ->groupBy('metadata.groupName')
  63. ->sortBy(static function ($group) {
  64. /* @var $group Collection */
  65. return $group->first()['metadata']['groupName'];
  66. }, SORT_NATURAL);
  67. $writer = new Writer(
  68. $groupedRoutes,
  69. $this->option('force'),
  70. $this,
  71. $this->docConfig
  72. );
  73. $writer->writeDocs();
  74. }
  75. /**
  76. * @param \Mpociot\ApiDoc\Extracting\Generator $generator
  77. * @param array $routes
  78. *
  79. * @return array
  80. */
  81. private function processRoutes(Generator $generator, array $routes)
  82. {
  83. $parsedRoutes = [];
  84. foreach ($routes as $routeItem) {
  85. $route = $routeItem['route'];
  86. /** @var Route $route */
  87. $messageFormat = '%s route: [%s] %s';
  88. $routeMethods = implode(',', $generator->getMethods($route));
  89. $routePath = $generator->getUri($route);
  90. if (! $this->isValidRoute($route) || ! $this->isRouteVisibleForDocumentation($route->getAction())) {
  91. $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath));
  92. continue;
  93. }
  94. try {
  95. $parsedRoutes[] = $generator->processRoute($route, $routeItem['apply'] ?? []);
  96. $this->info(sprintf($messageFormat, 'Processed', $routeMethods, $routePath));
  97. } catch (\Exception $exception) {
  98. $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath).' - '.$exception->getMessage());
  99. }
  100. }
  101. return $parsedRoutes;
  102. }
  103. /**
  104. * @param Route $route
  105. *
  106. * @return bool
  107. */
  108. private function isValidRoute(Route $route)
  109. {
  110. $action = Utils::getRouteClassAndMethodNames($route->getAction());
  111. if (is_array($action)) {
  112. $action = implode('@', $action);
  113. }
  114. return ! is_callable($action) && ! is_null($action);
  115. }
  116. /**
  117. * @param array $action
  118. *
  119. * @throws ReflectionException
  120. *
  121. * @return bool
  122. */
  123. private function isRouteVisibleForDocumentation(array $action)
  124. {
  125. list($class, $method) = Utils::getRouteClassAndMethodNames($action);
  126. $reflection = new ReflectionClass($class);
  127. if (! $reflection->hasMethod($method)) {
  128. return false;
  129. }
  130. $comment = $reflection->getMethod($method)->getDocComment();
  131. if ($comment) {
  132. $phpdoc = new DocBlock($comment);
  133. return collect($phpdoc->getTags())
  134. ->filter(function ($tag) {
  135. return $tag->getName() === 'hideFromAPIDocumentation';
  136. })
  137. ->isEmpty();
  138. }
  139. return true;
  140. }
  141. }