Completed
Pull Request — master (#570)
by
unknown
01:17
created

ResponseCalls::callDingoRoute()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 9.456
c 0
b 0
f 0
cc 4
nc 8
nop 1
1
<?php
2
3
namespace Mpociot\ApiDoc\Strategies\Responses;
4
5
use Dingo\Api\Dispatcher;
6
use Illuminate\Http\Request;
7
use Illuminate\Http\Response;
8
use Illuminate\Routing\Route;
9
use Mpociot\ApiDoc\Tools\Flags;
10
use Mpociot\ApiDoc\Tools\Utils;
11
use Mpociot\ApiDoc\Strategies\Strategy;
12
use Mpociot\ApiDoc\Tools\Traits\ParamHelpers;
13
14
/**
15
 * Make a call to the route and retrieve its response.
16
 */
17
class ResponseCalls extends Strategy
18
{
19
    use ParamHelpers;
20
21
    /**
22
     * @param Route $route
23
     * @param \ReflectionClass $controller
24
     * @param \ReflectionMethod $method
25
     * @param array $routeRules
26
     * @param array $context
27
     *
28
     * @return array|null
29
     */
30
    public function __invoke(Route $route, \ReflectionClass $controller, \ReflectionMethod $method, array $routeRules, array $context = [])
31
    {
32
        $rulesToApply = $routeRules['response_calls'] ?? [];
33
        if (! $this->shouldMakeApiCall($route, $rulesToApply, $context)) {
34
            return null;
35
        }
36
37
        $this->configureEnvironment($rulesToApply);
38
39
        // Mix in parsed parameters with manually specified parameters.
40
        $bodyParameters = array_merge($context['cleanBodyParameters'], $rulesToApply['body'] ?? []);
41
        $queryParameters = array_merge($context['cleanQueryParameters'], $rulesToApply['query'] ?? []);
42
        $request = $this->prepareRequest($route, $rulesToApply, $bodyParameters, $queryParameters);
43
44
        try {
45
            $response = [200 => $this->makeApiCall($request)->getContent()];
46
        } catch (\Exception $e) {
47
            echo 'Exception thrown during response call for ['.implode(',', $route->methods)."] {$route->uri}.\n";
48
            if (Flags::$shouldBeVerbose) {
49
                Utils::dumpException($e);
50
            } else {
51
                echo "Run this again with the --verbose flag to see the exception.\n";
52
            }
53
            $response = null;
54
        } finally {
55
            $this->finish();
56
        }
57
58
        return $response;
59
    }
60
61
    /**
62
     * @param array $rulesToApply
63
     *
64
     * @return void
65
     */
66
    private function configureEnvironment(array $rulesToApply)
67
    {
68
        $this->startDbTransaction();
69
        $this->setEnvironmentVariables($rulesToApply['env'] ?? []);
0 ignored issues
show
Deprecated Code introduced by
The method Mpociot\ApiDoc\Strategie...tEnvironmentVariables() has been deprecated with message: in favour of Laravel config variables

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.

Loading history...
70
        $this->setLaravelConfigs($rulesToApply['config'] ?? []);
71
    }
72
73
    /**
74
     * @param Route $route
75
     * @param array $rulesToApply
76
     * @param array $bodyParams
77
     * @param array $queryParams
78
     *
79
     * @return Request
80
     */
81
    protected function prepareRequest(Route $route, array $rulesToApply, array $bodyParams, array $queryParams)
82
    {
83
        $uri = Utils::getFullUrl($route, $rulesToApply['bindings'] ?? []);
84
        $routeMethods = $this->getMethods($route);
85
        $method = array_shift($routeMethods);
86
        $cookies = isset($rulesToApply['cookies']) ? $rulesToApply['cookies'] : [];
87
        $request = Request::create($uri, $method, [], $cookies, [], $this->transformHeadersToServerVars($rulesToApply['headers'] ?? []));
88
        $request = $this->addHeaders($request, $route, $rulesToApply['headers'] ?? []);
89
90
        $request = $this->addQueryParameters($request, $queryParams);
91
        $request = $this->addBodyParameters($request, $bodyParams);
92
93
        return $request;
94
    }
95
96
    /**
97
     * @param array $env
98
     *
99
     * @return void
100
     *
101
     * @deprecated in favour of Laravel config variables
102
     */
103
    private function setEnvironmentVariables(array $env)
104
    {
105
        foreach ($env as $name => $value) {
106
            putenv("$name=$value");
107
108
            $_ENV[$name] = $value;
109
            $_SERVER[$name] = $value;
110
        }
111
    }
112
113
    /**
114
     * @param array $config
115
     *
116
     * @return void
117
     */
118
    private function setLaravelConfigs(array $config)
119
    {
120
        if (empty($config)) {
121
            return;
122
        }
123
124
        foreach ($config as $name => $value) {
125
            config([$name => $value]);
126
        }
127
    }
128
129
    /**
130
     * @return void
131
     */
132
    private function startDbTransaction()
133
    {
134
        try {
135
            app('db')->beginTransaction();
136
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
137
        }
138
    }
139
140
    /**
141
     * @return void
142
     */
143
    private function endDbTransaction()
144
    {
145
        try {
146
            app('db')->rollBack();
147
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
148
        }
149
    }
150
151
    /**
152
     * @return void
153
     */
154
    private function finish()
155
    {
156
        $this->endDbTransaction();
157
    }
158
159
    /**
160
     * @param Request $request
161
     *
162
     * @return \Illuminate\Http\JsonResponse|mixed
163
     */
164
    public function callDingoRoute(Request $request)
165
    {
166
        /** @var Dispatcher $dispatcher */
167
        $dispatcher = app(\Dingo\Api\Dispatcher::class);
168
169
        foreach ($request->headers as $header => $value) {
170
            $dispatcher->header($header, $value);
171
        }
172
173
        // set domain and body parameters
174
        $dispatcher->on($request->header('SERVER_NAME'))
175
            ->with($request->request->all());
176
177
        // set URL and query parameters
178
        $uri = $request->getRequestUri();
179
        $query = $request->getQueryString();
180
        if (! empty($query)) {
181
            $uri .= "?$query";
182
        }
183
        $response = call_user_func_array([$dispatcher, strtolower($request->method())], [$uri]);
184
185
        // the response from the Dingo dispatcher is the 'raw' response from the controller,
186
        // so we have to ensure it's JSON first
187
        if (! $response instanceof Response) {
0 ignored issues
show
Bug introduced by
The class Illuminate\Http\Response does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
188
            $response = response()->json($response);
189
        }
190
191
        return $response;
192
    }
193
194
    /**
195
     * @param Route $route
196
     *
197
     * @return array
198
     */
199
    public function getMethods(Route $route)
200
    {
201
        return array_diff($route->methods(), ['HEAD']);
202
    }
203
204
    /**
205
     * @param Request $request
206
     * @param Route $route
207
     * @param array|null $headers
208
     *
209
     * @return Request
210
     */
211
    private function addHeaders(Request $request, Route $route, $headers)
212
    {
213
        // set the proper domain
214
        if ($route->getDomain()) {
215
            $request->headers->add([
216
                'HOST' => $route->getDomain(),
217
            ]);
218
            $request->server->add([
219
                'HTTP_HOST' => $route->getDomain(),
220
                'SERVER_NAME' => $route->getDomain(),
221
            ]);
222
        }
223
224
        $headers = collect($headers);
225
226
        if (($headers->get('Accept') ?: $headers->get('accept')) === 'application/json') {
227
            $request->setRequestFormat('json');
228
        }
229
230
        return $request;
231
    }
232
233
    /**
234
     * @param Request $request
235
     * @param array $query
236
     *
237
     * @return Request
238
     */
239
    private function addQueryParameters(Request $request, array $query)
240
    {
241
        $request->query->add($query);
242
        $request->server->add(['QUERY_STRING' => http_build_query($query)]);
243
244
        return $request;
245
    }
246
247
    /**
248
     * @param Request $request
249
     * @param array $body
250
     *
251
     * @return Request
252
     */
253
    private function addBodyParameters(Request $request, array $body)
254
    {
255
        $request->request->add($body);
256
257
        return $request;
258
    }
259
260
    /**
261
     * @param Request $request
262
     *
263
     * @throws \Exception
264
     *
265
     * @return \Illuminate\Http\JsonResponse|mixed|\Symfony\Component\HttpFoundation\Response
266
     */
267
    protected function makeApiCall(Request $request)
268
    {
269
        if (config('apidoc.router') == 'dingo') {
270
            $response = $this->callDingoRoute($request);
271
        } else {
272
            $response = $this->callLaravelRoute($request);
273
        }
274
275
        return $response;
276
    }
277
278
    /**
279
     * @param Request $request
280
     *
281
     * @throws \Exception
282
     *
283
     * @return \Symfony\Component\HttpFoundation\Response
284
     */
285
    protected function callLaravelRoute(Request $request): \Symfony\Component\HttpFoundation\Response
286
    {
287
        $kernel = app(\Illuminate\Contracts\Http\Kernel::class);
288
        $response = $kernel->handle($request);
289
        $kernel->terminate($request, $response);
290
291
        return $response;
292
    }
293
294
    /**
295
     * @param Route $route
296
     * @param array $rulesToApply
297
     *
298
     * @return bool
299
     */
300
    private function shouldMakeApiCall(Route $route, array $rulesToApply, array $context): bool
301
    {
302
        $allowedMethods = $rulesToApply['methods'] ?? [];
303
        if (empty($allowedMethods)) {
304
            return false;
305
        }
306
307
        if (! empty($context['responses'])) {
308
            // Don't attempt a response call if there are already responses
309
            return false;
310
        }
311
312
        if (is_string($allowedMethods) && $allowedMethods == '*') {
313
            return true;
314
        }
315
316
        if (array_search('*', $allowedMethods) !== false) {
317
            return true;
318
        }
319
320
        $routeMethods = $this->getMethods($route);
321
        if (in_array(array_shift($routeMethods), $allowedMethods)) {
322
            return true;
323
        }
324
325
        return false;
326
    }
327
328
    /**
329
     * Transform headers array to array of $_SERVER vars with HTTP_* format.
330
     *
331
     * @param  array  $headers
332
     *
333
     * @return array
334
     */
335
    protected function transformHeadersToServerVars(array $headers)
336
    {
337
        $server = [];
338
        $prefix = 'HTTP_';
339
        foreach ($headers as $name => $value) {
340
            $name = strtr(strtoupper($name), '-', '_');
341
            if (! starts_with($name, $prefix) && $name !== 'CONTENT_TYPE') {
342
                $name = $prefix.$name;
343
            }
344
            $server[$name] = $value;
345
        }
346
347
        return $server;
348
    }
349
}
350