Generator.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. namespace Mpociot\ApiDoc\Tools;
  3. use ReflectionClass;
  4. use ReflectionMethod;
  5. use Illuminate\Support\Arr;
  6. use Illuminate\Support\Str;
  7. use Illuminate\Routing\Route;
  8. class Generator
  9. {
  10. /**
  11. * @var DocumentationConfig
  12. */
  13. private $config;
  14. public function __construct(DocumentationConfig $config = null)
  15. {
  16. // If no config is injected, pull from global
  17. $this->config = $config ?: new DocumentationConfig(config('apidoc'));
  18. }
  19. /**
  20. * @param Route $route
  21. *
  22. * @return mixed
  23. */
  24. public function getUri(Route $route)
  25. {
  26. return $route->uri();
  27. }
  28. /**
  29. * @param Route $route
  30. *
  31. * @return mixed
  32. */
  33. public function getMethods(Route $route)
  34. {
  35. return array_diff($route->methods(), ['HEAD']);
  36. }
  37. /**
  38. * @param \Illuminate\Routing\Route $route
  39. * @param array $routeRules Rules to apply when generating documentation for this route
  40. *
  41. * @return array
  42. */
  43. public function processRoute(Route $route, array $routeRules = [])
  44. {
  45. list($controllerName, $methodName) = Utils::getRouteClassAndMethodNames($route->getAction());
  46. $controller = new ReflectionClass($controllerName);
  47. $method = $controller->getMethod($methodName);
  48. $parsedRoute = [
  49. 'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))),
  50. 'methods' => $this->getMethods($route),
  51. 'uri' => $this->getUri($route),
  52. ];
  53. $metadata = $this->fetchMetadata($controller, $method, $route, $routeRules, $parsedRoute);
  54. $parsedRoute['metadata'] = $metadata;
  55. $urlParameters = $this->fetchUrlParameters($controller, $method, $route, $routeRules, $parsedRoute);
  56. $parsedRoute['urlParameters'] = $urlParameters;
  57. $parsedRoute['cleanUrlParameters'] = $this->cleanParams($urlParameters);
  58. $parsedRoute['boundUri'] = Utils::getFullUrl($route, $parsedRoute['cleanUrlParameters']);
  59. $queryParameters = $this->fetchQueryParameters($controller, $method, $route, $routeRules, $parsedRoute);
  60. $parsedRoute['queryParameters'] = $queryParameters;
  61. $parsedRoute['cleanQueryParameters'] = $this->cleanParams($queryParameters);
  62. $bodyParameters = $this->fetchBodyParameters($controller, $method, $route, $routeRules, $parsedRoute);
  63. $parsedRoute['bodyParameters'] = $bodyParameters;
  64. $parsedRoute['cleanBodyParameters'] = $this->cleanParams($bodyParameters);
  65. $responses = $this->fetchResponses($controller, $method, $route, $routeRules, $parsedRoute);
  66. $parsedRoute['responses'] = $responses;
  67. $parsedRoute['showresponse'] = ! empty($responses);
  68. $parsedRoute['headers'] = $routeRules['headers'] ?? [];
  69. $parsedRoute += $metadata;
  70. return $parsedRoute;
  71. }
  72. protected function fetchMetadata(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
  73. {
  74. $context['metadata'] = [
  75. 'groupName' => $this->config->get('default_group', ''),
  76. 'groupDescription' => '',
  77. 'title' => '',
  78. 'description' => '',
  79. 'authenticated' => false,
  80. ];
  81. return $this->iterateThroughStrategies('metadata', $context, [$route, $controller, $method, $rulesToApply]);
  82. }
  83. protected function fetchUrlParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
  84. {
  85. return $this->iterateThroughStrategies('urlParameters', $context, [$route, $controller, $method, $rulesToApply]);
  86. }
  87. protected function fetchQueryParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
  88. {
  89. return $this->iterateThroughStrategies('queryParameters', $context, [$route, $controller, $method, $rulesToApply]);
  90. }
  91. protected function fetchBodyParameters(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
  92. {
  93. return $this->iterateThroughStrategies('bodyParameters', $context, [$route, $controller, $method, $rulesToApply]);
  94. }
  95. protected function fetchResponses(ReflectionClass $controller, ReflectionMethod $method, Route $route, array $rulesToApply, array $context = [])
  96. {
  97. $responses = $this->iterateThroughStrategies('responses', $context, [$route, $controller, $method, $rulesToApply]);
  98. if (count($responses)) {
  99. return array_filter($responses, function ($response) {
  100. return $response['content'] != null;
  101. });
  102. }
  103. return null;
  104. }
  105. protected function iterateThroughStrategies(string $stage, array $context, array $arguments)
  106. {
  107. $defaultStrategies = [
  108. 'metadata' => [
  109. \Mpociot\ApiDoc\Strategies\Metadata\GetFromDocBlocks::class,
  110. ],
  111. 'urlParameters' => [
  112. \Mpociot\ApiDoc\Strategies\UrlParameters\GetFromUrlParamTag::class,
  113. ],
  114. 'queryParameters' => [
  115. \Mpociot\ApiDoc\Strategies\QueryParameters\GetFromQueryParamTag::class,
  116. ],
  117. 'bodyParameters' => [
  118. \Mpociot\ApiDoc\Strategies\BodyParameters\GetFromBodyParamTag::class,
  119. ],
  120. 'responses' => [
  121. \Mpociot\ApiDoc\Strategies\Responses\UseResponseTag::class,
  122. \Mpociot\ApiDoc\Strategies\Responses\UseResponseFileTag::class,
  123. \Mpociot\ApiDoc\Strategies\Responses\UseApiResourceTags::class,
  124. \Mpociot\ApiDoc\Strategies\Responses\UseTransformerTags::class,
  125. \Mpociot\ApiDoc\Strategies\Responses\ResponseCalls::class,
  126. ],
  127. ];
  128. // Use the default strategies for the stage, unless they were explicitly set
  129. $strategies = $this->config->get("strategies.$stage", $defaultStrategies[$stage]);
  130. $context[$stage] = $context[$stage] ?? [];
  131. foreach ($strategies as $strategyClass) {
  132. $strategy = new $strategyClass($stage, $this->config);
  133. $strategyArgs = $arguments;
  134. $strategyArgs[] = $context;
  135. $results = $strategy(...$strategyArgs);
  136. if (! is_null($results)) {
  137. foreach ($results as $index => $item) {
  138. if ($stage == "responses") {
  139. // Responses are additive
  140. $context[$stage][] = $item;
  141. continue;
  142. }
  143. // Using a for loop rather than array_merge or +=
  144. // so it does not renumber numeric keys
  145. // and also allows values to be overwritten
  146. // Don't allow overwriting if an empty value is trying to replace a set one
  147. if (! in_array($context[$stage], [null, ''], true) && in_array($item, [null, ''], true)) {
  148. continue;
  149. } else {
  150. $context[$stage][$index] = $item;
  151. }
  152. }
  153. }
  154. }
  155. return $context[$stage];
  156. }
  157. /**
  158. * Create samples at index 0 for array parameters.
  159. * Also filter out parameters which were excluded from having examples.
  160. *
  161. * @param array $params
  162. *
  163. * @return array
  164. */
  165. protected function cleanParams(array $params)
  166. {
  167. $values = [];
  168. // Remove params which have no examples.
  169. $params = array_filter($params, function ($details) {
  170. return ! is_null($details['value']);
  171. });
  172. foreach ($params as $paramName => $details) {
  173. $this->generateConcreteSampleForArrayKeys(
  174. $paramName, $details['value'], $values
  175. );
  176. }
  177. return $values;
  178. }
  179. /**
  180. * For each array notation parameter (eg user.*, item.*.name, object.*.*, user[])
  181. * generate concrete sample (user.0, item.0.name, object.0.0, user.0) with example as value.
  182. *
  183. * @param string $paramName
  184. * @param mixed $paramExample
  185. * @param array $values The array that holds the result
  186. *
  187. * @return void
  188. */
  189. protected function generateConcreteSampleForArrayKeys($paramName, $paramExample, array &$values = [])
  190. {
  191. if (Str::contains($paramName, '[')) {
  192. // Replace usages of [] with dot notation
  193. $paramName = str_replace(['][', '[', ']', '..'], ['.', '.', '', '.*.'], $paramName);
  194. }
  195. // Then generate a sample item for the dot notation
  196. Arr::set($values, str_replace('.*', '.0', $paramName), $paramExample);
  197. }
  198. }