1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Mpociot\ApiDoc\Strategies\Responses; |
4
|
|
|
|
5
|
|
|
use Dingo\Api\Dispatcher; |
6
|
|
|
use Illuminate\Support\Str; |
7
|
|
|
use Illuminate\Http\Request; |
8
|
|
|
use Illuminate\Http\Response; |
9
|
|
|
use Illuminate\Routing\Route; |
10
|
|
|
use Mpociot\ApiDoc\Tools\Flags; |
11
|
|
|
use Mpociot\ApiDoc\Tools\Utils; |
12
|
|
|
use Mpociot\ApiDoc\Strategies\Strategy; |
13
|
|
|
use Mpociot\ApiDoc\Tools\Traits\ParamHelpers; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Make a call to the route and retrieve its response. |
17
|
|
|
*/ |
18
|
|
|
class ResponseCalls extends Strategy |
19
|
|
|
{ |
20
|
|
|
use ParamHelpers; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* @param Route $route |
24
|
|
|
* @param \ReflectionClass $controller |
25
|
|
|
* @param \ReflectionMethod $method |
26
|
|
|
* @param array $routeRules |
27
|
|
|
* @param array $context |
28
|
|
|
* |
29
|
|
|
* @return array|null |
30
|
|
|
*/ |
31
|
|
|
public function __invoke(Route $route, \ReflectionClass $controller, \ReflectionMethod $method, array $routeRules, array $context = []) |
32
|
|
|
{ |
33
|
|
|
$rulesToApply = $routeRules['response_calls'] ?? []; |
34
|
|
|
if (! $this->shouldMakeApiCall($route, $rulesToApply, $context)) { |
35
|
|
|
return null; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
$this->configureEnvironment($rulesToApply); |
39
|
|
|
|
40
|
|
|
// Mix in parsed parameters with manually specified parameters. |
41
|
|
|
$bodyParameters = array_merge($context['cleanBodyParameters'], $rulesToApply['body'] ?? []); |
42
|
|
|
$queryParameters = array_merge($context['cleanQueryParameters'], $rulesToApply['query'] ?? []); |
43
|
|
|
$request = $this->prepareRequest($route, $rulesToApply, $bodyParameters, $queryParameters); |
44
|
|
|
|
45
|
|
|
try { |
46
|
|
|
$response = [200 => $this->makeApiCall($request)->getContent()]; |
47
|
|
|
} catch (\Exception $e) { |
48
|
|
|
echo 'Exception thrown during response call for ['.implode(',', $route->methods)."] {$route->uri}.\n"; |
49
|
|
|
if (Flags::$shouldBeVerbose) { |
50
|
|
|
Utils::dumpException($e); |
51
|
|
|
} else { |
52
|
|
|
echo "Run this again with the --verbose flag to see the exception.\n"; |
53
|
|
|
} |
54
|
|
|
$response = null; |
55
|
|
|
} finally { |
56
|
|
|
$this->finish(); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return $response; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @param array $rulesToApply |
64
|
|
|
* |
65
|
|
|
* @return void |
66
|
|
|
*/ |
67
|
|
|
private function configureEnvironment(array $rulesToApply) |
68
|
|
|
{ |
69
|
|
|
$this->startDbTransaction(); |
70
|
|
|
$this->setEnvironmentVariables($rulesToApply['env'] ?? []); |
|
|
|
|
71
|
|
|
$this->setLaravelConfigs($rulesToApply['config'] ?? []); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* @param Route $route |
76
|
|
|
* @param array $rulesToApply |
77
|
|
|
* @param array $bodyParams |
78
|
|
|
* @param array $queryParams |
79
|
|
|
* |
80
|
|
|
* @return Request |
81
|
|
|
*/ |
82
|
|
|
protected function prepareRequest(Route $route, array $rulesToApply, array $bodyParams, array $queryParams) |
83
|
|
|
{ |
84
|
|
|
$uri = Utils::getFullUrl($route, $rulesToApply['bindings'] ?? []); |
85
|
|
|
$routeMethods = $this->getMethods($route); |
86
|
|
|
$method = array_shift($routeMethods); |
87
|
|
|
$cookies = isset($rulesToApply['cookies']) ? $rulesToApply['cookies'] : []; |
88
|
|
|
$request = Request::create($uri, $method, [], $cookies, [], $this->transformHeadersToServerVars($rulesToApply['headers'] ?? [])); |
89
|
|
|
$request = $this->addHeaders($request, $route, $rulesToApply['headers'] ?? []); |
90
|
|
|
|
91
|
|
|
$request = $this->addQueryParameters($request, $queryParams); |
92
|
|
|
$request = $this->addBodyParameters($request, $bodyParams); |
93
|
|
|
|
94
|
|
|
return $request; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @param array $env |
99
|
|
|
* |
100
|
|
|
* @return void |
101
|
|
|
* |
102
|
|
|
* @deprecated in favour of Laravel config variables |
103
|
|
|
*/ |
104
|
|
|
private function setEnvironmentVariables(array $env) |
105
|
|
|
{ |
106
|
|
|
foreach ($env as $name => $value) { |
107
|
|
|
putenv("$name=$value"); |
108
|
|
|
|
109
|
|
|
$_ENV[$name] = $value; |
110
|
|
|
$_SERVER[$name] = $value; |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* @param array $config |
116
|
|
|
* |
117
|
|
|
* @return void |
118
|
|
|
*/ |
119
|
|
|
private function setLaravelConfigs(array $config) |
120
|
|
|
{ |
121
|
|
|
if (empty($config)) { |
122
|
|
|
return; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
foreach ($config as $name => $value) { |
126
|
|
|
config([$name => $value]); |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @return void |
132
|
|
|
*/ |
133
|
|
|
private function startDbTransaction() |
134
|
|
|
{ |
135
|
|
|
try { |
136
|
|
|
app('db')->beginTransaction(); |
137
|
|
|
} catch (\Exception $e) { |
|
|
|
|
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @return void |
143
|
|
|
*/ |
144
|
|
|
private function endDbTransaction() |
145
|
|
|
{ |
146
|
|
|
try { |
147
|
|
|
app('db')->rollBack(); |
148
|
|
|
} catch (\Exception $e) { |
|
|
|
|
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @return void |
154
|
|
|
*/ |
155
|
|
|
private function finish() |
156
|
|
|
{ |
157
|
|
|
$this->endDbTransaction(); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @param Request $request |
162
|
|
|
* |
163
|
|
|
* @return \Illuminate\Http\JsonResponse|mixed |
164
|
|
|
*/ |
165
|
|
|
public function callDingoRoute(Request $request) |
166
|
|
|
{ |
167
|
|
|
/** @var Dispatcher $dispatcher */ |
168
|
|
|
$dispatcher = app(\Dingo\Api\Dispatcher::class); |
169
|
|
|
|
170
|
|
|
foreach ($request->headers as $header => $value) { |
171
|
|
|
$dispatcher->header($header, $value); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
// set domain and body parameters |
175
|
|
|
$dispatcher->on($request->header('SERVER_NAME')) |
176
|
|
|
->with($request->request->all()); |
177
|
|
|
|
178
|
|
|
// set URL and query parameters |
179
|
|
|
$uri = $request->getRequestUri(); |
180
|
|
|
$query = $request->getQueryString(); |
181
|
|
|
if (! empty($query)) { |
182
|
|
|
$uri .= "?$query"; |
183
|
|
|
} |
184
|
|
|
$response = call_user_func_array([$dispatcher, strtolower($request->method())], [$uri]); |
185
|
|
|
|
186
|
|
|
// the response from the Dingo dispatcher is the 'raw' response from the controller, |
187
|
|
|
// so we have to ensure it's JSON first |
188
|
|
|
if (! $response instanceof Response) { |
|
|
|
|
189
|
|
|
$response = response()->json($response); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
return $response; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** |
196
|
|
|
* @param Route $route |
197
|
|
|
* |
198
|
|
|
* @return array |
199
|
|
|
*/ |
200
|
|
|
public function getMethods(Route $route) |
201
|
|
|
{ |
202
|
|
|
return array_diff($route->methods(), ['HEAD']); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* @param Request $request |
207
|
|
|
* @param Route $route |
208
|
|
|
* @param array|null $headers |
209
|
|
|
* |
210
|
|
|
* @return Request |
211
|
|
|
*/ |
212
|
|
|
private function addHeaders(Request $request, Route $route, $headers) |
213
|
|
|
{ |
214
|
|
|
// set the proper domain |
215
|
|
|
if ($route->getDomain()) { |
216
|
|
|
$request->headers->add([ |
217
|
|
|
'HOST' => $route->getDomain(), |
218
|
|
|
]); |
219
|
|
|
$request->server->add([ |
220
|
|
|
'HTTP_HOST' => $route->getDomain(), |
221
|
|
|
'SERVER_NAME' => $route->getDomain(), |
222
|
|
|
]); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
$headers = collect($headers); |
226
|
|
|
|
227
|
|
|
if (($headers->get('Accept') ?: $headers->get('accept')) === 'application/json') { |
228
|
|
|
$request->setRequestFormat('json'); |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return $request; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @param Request $request |
236
|
|
|
* @param array $query |
237
|
|
|
* |
238
|
|
|
* @return Request |
239
|
|
|
*/ |
240
|
|
|
private function addQueryParameters(Request $request, array $query) |
241
|
|
|
{ |
242
|
|
|
$request->query->add($query); |
243
|
|
|
$request->server->add(['QUERY_STRING' => http_build_query($query)]); |
244
|
|
|
|
245
|
|
|
return $request; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* @param Request $request |
250
|
|
|
* @param array $body |
251
|
|
|
* |
252
|
|
|
* @return Request |
253
|
|
|
*/ |
254
|
|
|
private function addBodyParameters(Request $request, array $body) |
255
|
|
|
{ |
256
|
|
|
$request->request->add($body); |
257
|
|
|
|
258
|
|
|
return $request; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @param Request $request |
263
|
|
|
* |
264
|
|
|
* @throws \Exception |
265
|
|
|
* |
266
|
|
|
* @return \Illuminate\Http\JsonResponse|mixed|\Symfony\Component\HttpFoundation\Response |
267
|
|
|
*/ |
268
|
|
|
protected function makeApiCall(Request $request) |
269
|
|
|
{ |
270
|
|
|
if (config('apidoc.router') == 'dingo') { |
271
|
|
|
$response = $this->callDingoRoute($request); |
272
|
|
|
} else { |
273
|
|
|
$response = $this->callLaravelRoute($request); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
return $response; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* @param Request $request |
281
|
|
|
* |
282
|
|
|
* @throws \Exception |
283
|
|
|
* |
284
|
|
|
* @return \Symfony\Component\HttpFoundation\Response |
285
|
|
|
*/ |
286
|
|
|
protected function callLaravelRoute(Request $request): \Symfony\Component\HttpFoundation\Response |
287
|
|
|
{ |
288
|
|
|
$kernel = app(\Illuminate\Contracts\Http\Kernel::class); |
289
|
|
|
$response = $kernel->handle($request); |
290
|
|
|
$kernel->terminate($request, $response); |
291
|
|
|
|
292
|
|
|
return $response; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* @param Route $route |
297
|
|
|
* @param array $rulesToApply |
298
|
|
|
* |
299
|
|
|
* @return bool |
300
|
|
|
*/ |
301
|
|
|
private function shouldMakeApiCall(Route $route, array $rulesToApply, array $context): bool |
302
|
|
|
{ |
303
|
|
|
$allowedMethods = $rulesToApply['methods'] ?? []; |
304
|
|
|
if (empty($allowedMethods)) { |
305
|
|
|
return false; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
if (! empty($context['responses'])) { |
309
|
|
|
// Don't attempt a response call if there are already responses |
310
|
|
|
return false; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
if (is_string($allowedMethods) && $allowedMethods == '*') { |
314
|
|
|
return true; |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
if (array_search('*', $allowedMethods) !== false) { |
318
|
|
|
return true; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
$routeMethods = $this->getMethods($route); |
322
|
|
|
if (in_array(array_shift($routeMethods), $allowedMethods)) { |
323
|
|
|
return true; |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
return false; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Transform headers array to array of $_SERVER vars with HTTP_* format. |
331
|
|
|
* |
332
|
|
|
* @param array $headers |
333
|
|
|
* |
334
|
|
|
* @return array |
335
|
|
|
*/ |
336
|
|
|
protected function transformHeadersToServerVars(array $headers) |
337
|
|
|
{ |
338
|
|
|
$server = []; |
339
|
|
|
$prefix = 'HTTP_'; |
340
|
|
|
foreach ($headers as $name => $value) { |
341
|
|
|
$name = strtr(strtoupper($name), '-', '_'); |
342
|
|
|
if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') { |
343
|
|
|
$name = $prefix.$name; |
344
|
|
|
} |
345
|
|
|
$server[$name] = $value; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
return $server; |
349
|
|
|
} |
350
|
|
|
} |
351
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.