ResponseCallStrategy.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. namespace Mpociot\ApiDoc\Tools\ResponseStrategies;
  3. use Dingo\Api\Dispatcher;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Http\Response;
  6. use Illuminate\Routing\Route;
  7. use Mpociot\ApiDoc\Tools\Traits\ParamHelpers;
  8. /**
  9. * Make a call to the route and retrieve its response.
  10. */
  11. class ResponseCallStrategy
  12. {
  13. use ParamHelpers;
  14. /**
  15. * @param Route $route
  16. * @param array $tags
  17. * @param array $routeProps
  18. *
  19. * @return array|null
  20. */
  21. public function __invoke(Route $route, array $tags, array $routeProps)
  22. {
  23. $rulesToApply = $routeProps['rules']['response_calls'] ?? [];
  24. if (! $this->shouldMakeApiCall($route, $rulesToApply)) {
  25. return;
  26. }
  27. $this->configureEnvironment($rulesToApply);
  28. $request = $this->prepareRequest($route, $rulesToApply, $routeProps['body'], $routeProps['query']);
  29. try {
  30. $response = [$this->makeApiCall($request)];
  31. } catch (\Exception $e) {
  32. $response = null;
  33. } finally {
  34. $this->finish();
  35. }
  36. return $response;
  37. }
  38. /**
  39. * @param array $rulesToApply
  40. *
  41. * @return void
  42. */
  43. private function configureEnvironment(array $rulesToApply)
  44. {
  45. $this->startDbTransaction();
  46. $this->setEnvironmentVariables($rulesToApply['env'] ?? []);
  47. $this->setLaravelConfigs($rulesToApply['config'] ?? []);
  48. }
  49. /**
  50. * @param Route $route
  51. * @param array $rulesToApply
  52. * @param array $bodyParams
  53. * @param array $queryParams
  54. *
  55. * @return Request
  56. */
  57. private function prepareRequest(Route $route, array $rulesToApply, array $bodyParams, array $queryParams)
  58. {
  59. $uri = $this->replaceUrlParameterBindings($route, $rulesToApply['bindings'] ?? []);
  60. $routeMethods = $this->getMethods($route);
  61. $method = array_shift($routeMethods);
  62. $cookies = isset($rulesToApply['cookies']) ? $rulesToApply['cookies'] : [];
  63. $request = Request::create($uri, $method, [], $cookies, [], $this->transformHeadersToServerVars($rulesToApply['headers'] ?? []));
  64. $request = $this->addHeaders($request, $route, $rulesToApply['headers'] ?? []);
  65. // Mix in parsed parameters with manually specified parameters.
  66. $queryParams = collect($this->cleanParams($queryParams))->merge($rulesToApply['query'] ?? [])->toArray();
  67. $bodyParams = collect($this->cleanParams($bodyParams))->merge($rulesToApply['body'] ?? [])->toArray();
  68. $request = $this->addQueryParameters($request, $queryParams);
  69. $request = $this->addBodyParameters($request, $bodyParams);
  70. return $request;
  71. }
  72. /**
  73. * Transform parameters in URLs into real values (/users/{user} -> /users/2).
  74. * Uses bindings specified by caller, otherwise just uses '1'.
  75. *
  76. * @param Route $route
  77. * @param array $bindings
  78. *
  79. * @return mixed
  80. */
  81. protected function replaceUrlParameterBindings(Route $route, $bindings)
  82. {
  83. $uri = $route->uri();
  84. foreach ($bindings as $parameter => $binding) {
  85. $uri = str_replace($parameter, $binding, $uri);
  86. }
  87. // Replace any unbound parameters with '1'
  88. $uri = preg_replace('/{(.*?)}/', 1, $uri);
  89. return $uri;
  90. }
  91. /**
  92. * @param array $config
  93. *
  94. * @return void
  95. * @deprecated in favour of Laravel config variables
  96. */
  97. private function setEnvironmentVariables(array $env)
  98. {
  99. foreach ($env as $name => $value) {
  100. putenv("$name=$value");
  101. $_ENV[$name] = $value;
  102. $_SERVER[$name] = $value;
  103. }
  104. }
  105. /**
  106. * @param array $config
  107. *
  108. * @return void
  109. */
  110. private function setLaravelConfigs(array $config)
  111. {
  112. if (empty($config)) {
  113. return;
  114. }
  115. foreach ($config as $name => $value) {
  116. config([$name => $value]);
  117. }
  118. }
  119. /**
  120. * @return void
  121. */
  122. private function startDbTransaction()
  123. {
  124. try {
  125. app('db')->beginTransaction();
  126. } catch (\Exception $e) {
  127. }
  128. }
  129. /**
  130. * @return void
  131. */
  132. private function endDbTransaction()
  133. {
  134. try {
  135. app('db')->rollBack();
  136. } catch (\Exception $e) {
  137. }
  138. }
  139. /**
  140. * @return void
  141. */
  142. private function finish()
  143. {
  144. $this->endDbTransaction();
  145. }
  146. /**
  147. * @param Request $request
  148. *
  149. * @return \Illuminate\Http\JsonResponse|mixed
  150. */
  151. public function callDingoRoute(Request $request)
  152. {
  153. /** @var Dispatcher $dispatcher */
  154. $dispatcher = app(\Dingo\Api\Dispatcher::class);
  155. foreach ($request->headers as $header => $value) {
  156. $dispatcher->header($header, $value);
  157. }
  158. // set domain and body parameters
  159. $dispatcher->on($request->header('SERVER_NAME'))
  160. ->with($request->request->all());
  161. // set URL and query parameters
  162. $uri = $request->getRequestUri();
  163. $query = $request->getQueryString();
  164. if (! empty($query)) {
  165. $uri .= "?$query";
  166. }
  167. $response = call_user_func_array([$dispatcher, strtolower($request->method())], [$uri]);
  168. // the response from the Dingo dispatcher is the 'raw' response from the controller,
  169. // so we have to ensure it's JSON first
  170. if (! $response instanceof Response) {
  171. $response = response()->json($response);
  172. }
  173. return $response;
  174. }
  175. /**
  176. * @param Route $route
  177. *
  178. * @return array
  179. */
  180. public function getMethods(Route $route)
  181. {
  182. return array_diff($route->methods(), ['HEAD']);
  183. }
  184. /**
  185. * @param Request $request
  186. * @param Route $route
  187. * @param array|null $headers
  188. *
  189. * @return Request
  190. */
  191. private function addHeaders(Request $request, Route $route, $headers)
  192. {
  193. // set the proper domain
  194. if ($route->getDomain()) {
  195. $request->headers->add([
  196. 'HOST' => $route->getDomain(),
  197. ]);
  198. $request->server->add([
  199. 'HTTP_HOST' => $route->getDomain(),
  200. 'SERVER_NAME' => $route->getDomain(),
  201. ]);
  202. }
  203. $headers = collect($headers);
  204. if (($headers->get('Accept') ?: $headers->get('accept')) === 'application/json') {
  205. $request->setRequestFormat('json');
  206. }
  207. return $request;
  208. }
  209. /**
  210. * @param Request $request
  211. * @param array $query
  212. *
  213. * @return Request
  214. */
  215. private function addQueryParameters(Request $request, array $query)
  216. {
  217. $request->query->add($query);
  218. $request->server->add(['QUERY_STRING' => http_build_query($query)]);
  219. return $request;
  220. }
  221. /**
  222. * @param Request $request
  223. * @param array $body
  224. *
  225. * @return Request
  226. */
  227. private function addBodyParameters(Request $request, array $body)
  228. {
  229. $request->request->add($body);
  230. return $request;
  231. }
  232. /**
  233. * @param Request $request
  234. *
  235. * @throws \Exception
  236. *
  237. * @return \Illuminate\Http\JsonResponse|mixed|\Symfony\Component\HttpFoundation\Response
  238. */
  239. private function makeApiCall(Request $request)
  240. {
  241. if (config('apidoc.router') == 'dingo') {
  242. $response = $this->callDingoRoute($request);
  243. } else {
  244. $response = $this->callLaravelRoute($request);
  245. }
  246. return $response;
  247. }
  248. /**
  249. * @param Request $request
  250. *
  251. * @throws \Exception
  252. *
  253. * @return \Symfony\Component\HttpFoundation\Response
  254. */
  255. private function callLaravelRoute(Request $request): \Symfony\Component\HttpFoundation\Response
  256. {
  257. $kernel = app(\Illuminate\Contracts\Http\Kernel::class);
  258. $response = $kernel->handle($request);
  259. $kernel->terminate($request, $response);
  260. return $response;
  261. }
  262. /**
  263. * @param Route $route
  264. * @param array $rulesToApply
  265. *
  266. * @return bool
  267. */
  268. private function shouldMakeApiCall(Route $route, array $rulesToApply): bool
  269. {
  270. $allowedMethods = $rulesToApply['methods'] ?? [];
  271. if (empty($allowedMethods)) {
  272. return false;
  273. }
  274. if (is_string($allowedMethods) && $allowedMethods == '*') {
  275. return true;
  276. }
  277. if (array_search('*', $allowedMethods) !== false) {
  278. return true;
  279. }
  280. $routeMethods = $this->getMethods($route);
  281. if (in_array(array_shift($routeMethods), $allowedMethods)) {
  282. return true;
  283. }
  284. return false;
  285. }
  286. /**
  287. * Transform headers array to array of $_SERVER vars with HTTP_* format.
  288. *
  289. * @param array $headers
  290. *
  291. * @return array
  292. */
  293. protected function transformHeadersToServerVars(array $headers)
  294. {
  295. $server = [];
  296. $prefix = 'HTTP_';
  297. foreach ($headers as $name => $value) {
  298. $name = strtr(strtoupper($name), '-', '_');
  299. if (! starts_with($name, $prefix) && $name !== 'CONTENT_TYPE') {
  300. $name = $prefix.$name;
  301. }
  302. $server[$name] = $value;
  303. }
  304. return $server;
  305. }
  306. }