GenerateDocumentation.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. <?php
  2. namespace Mpociot\ApiDoc\Commands;
  3. use Illuminate\Console\Command;
  4. use Illuminate\Routing\Route;
  5. use Illuminate\Support\Collection;
  6. use Illuminate\Support\Facades\URL;
  7. use Mpociot\ApiDoc\Extracting\Generator;
  8. use Mpociot\ApiDoc\Matching\RouteMatcher;
  9. use Mpociot\ApiDoc\Tools\DocumentationConfig;
  10. use Mpociot\ApiDoc\Tools\Flags;
  11. use Mpociot\ApiDoc\Tools\Utils;
  12. use Mpociot\ApiDoc\Writing\Writer;
  13. use Mpociot\Reflection\DocBlock;
  14. use ReflectionClass;
  15. use ReflectionException;
  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. $this,
  69. $this->docConfig,
  70. $this->option('force')
  71. );
  72. $writer->writeDocs($groupedRoutes);
  73. }
  74. /**
  75. * @param \Mpociot\ApiDoc\Extracting\Generator $generator
  76. * @param array $routes
  77. *
  78. * @return array
  79. */
  80. private function processRoutes(Generator $generator, array $routes)
  81. {
  82. $parsedRoutes = [];
  83. foreach ($routes as $routeItem) {
  84. $route = $routeItem['route'];
  85. /** @var Route $route */
  86. $messageFormat = '%s route: [%s] %s';
  87. $routeMethods = implode(',', $generator->getMethods($route));
  88. $routePath = $generator->getUri($route);
  89. if (! $this->isValidRoute($route) || ! $this->isRouteVisibleForDocumentation($route->getAction())) {
  90. $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath));
  91. continue;
  92. }
  93. try {
  94. $parsedRoutes[] = $generator->processRoute($route, $routeItem['apply'] ?? []);
  95. $this->info(sprintf($messageFormat, 'Processed', $routeMethods, $routePath));
  96. } catch (\Exception $exception) {
  97. $this->warn(sprintf($messageFormat, 'Skipping', $routeMethods, $routePath).' - '.$exception->getMessage());
  98. }
  99. }
  100. return $parsedRoutes;
  101. }
  102. /**
  103. * @param Route $route
  104. *
  105. * @return bool
  106. */
  107. private function isValidRoute(Route $route)
  108. {
  109. $action = Utils::getRouteClassAndMethodNames($route->getAction());
  110. if (is_array($action)) {
  111. $action = implode('@', $action);
  112. }
  113. return ! is_callable($action) && ! is_null($action);
  114. }
  115. /**
  116. * @param array $action
  117. *
  118. * @throws ReflectionException
  119. *
  120. * @return bool
  121. */
  122. private function isRouteVisibleForDocumentation(array $action)
  123. {
  124. list($class, $method) = Utils::getRouteClassAndMethodNames($action);
  125. $reflection = new ReflectionClass($class);
  126. if (! $reflection->hasMethod($method)) {
  127. return false;
  128. }
  129. $comment = $reflection->getMethod($method)->getDocComment();
  130. if ($comment) {
  131. $phpdoc = new DocBlock($comment);
  132. return collect($phpdoc->getTags())
  133. ->filter(function ($tag) {
  134. return $tag->getName() === 'hideFromAPIDocumentation';
  135. })
  136. ->isEmpty();
  137. }
  138. return true;
  139. }
  140. }