Completed
Push — master ( 489317...969861 )
by
unknown
12s queued 11s
created

ResponseCalls::finish()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 = $this->makeApiCall($request);
47
            $response = [$response->getStatusCode() => $response->getContent()];
48
        } catch (\Exception $e) {
49
            echo 'Exception thrown during response call for ['.implode(',', $route->methods)."] {$route->uri}.\n";
50
            if (Flags::$shouldBeVerbose) {
51
                Utils::dumpException($e);
52
            } else {
53
                echo "Run this again with the --verbose flag to see the exception.\n";
54
            }
55
            $response = null;
56
        } finally {
57
            $this->finish();
58
        }
59
60
        return $response;
61
    }
62
63
    /**
64
     * @param array $rulesToApply
65
     *
66
     * @return void
67
     */
68
    private function configureEnvironment(array $rulesToApply)
69
    {
70
        $this->startDbTransaction();
71
        $this->setEnvironmentVariables($rulesToApply['env'] ?? []);
0 ignored issues
show
Deprecated Code introduced by
The method Mpociot\ApiDoc\Strategie...tEnvironmentVariables() has been deprecated with message: Not guaranteed to overwrite application's env. Use Laravel config variables instead.

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...
72
        $this->setLaravelConfigs($rulesToApply['config'] ?? []);
73
    }
74
75
    /**
76
     * @param Route $route
77
     * @param array $rulesToApply
78
     * @param array $bodyParams
79
     * @param array $queryParams
80
     *
81
     * @return Request
82
     */
83
    protected function prepareRequest(Route $route, array $rulesToApply, array $bodyParams, array $queryParams)
84
    {
85
        $uri = Utils::getFullUrl($route, $rulesToApply['bindings'] ?? []);
86
        $routeMethods = $this->getMethods($route);
87
        $method = array_shift($routeMethods);
88
        $cookies = isset($rulesToApply['cookies']) ? $rulesToApply['cookies'] : [];
89
        $request = Request::create($uri, $method, [], $cookies, [], $this->transformHeadersToServerVars($rulesToApply['headers'] ?? []));
90
        $request = $this->addHeaders($request, $route, $rulesToApply['headers'] ?? []);
91
92
        $request = $this->addQueryParameters($request, $queryParams);
93
        $request = $this->addBodyParameters($request, $bodyParams);
94
95
        return $request;
96
    }
97
98
    /**
99
     * @param array $env
100
     *
101
     * @return void
102
     *
103
     * @deprecated Not guaranteed to overwrite application's env. Use Laravel config variables instead.
104
     */
105
    private function setEnvironmentVariables(array $env)
106
    {
107
        foreach ($env as $name => $value) {
108
            putenv("$name=$value");
109
110
            $_ENV[$name] = $value;
111
            $_SERVER[$name] = $value;
112
        }
113
    }
114
115
    /**
116
     * @param array $config
117
     *
118
     * @return void
119
     */
120
    private function setLaravelConfigs(array $config)
121
    {
122
        if (empty($config)) {
123
            return;
124
        }
125
126
        foreach ($config as $name => $value) {
127
            config([$name => $value]);
128
        }
129
    }
130
131
    /**
132
     * @return void
133
     */
134
    private function startDbTransaction()
135
    {
136
        try {
137
            app('db')->beginTransaction();
138
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
139
        }
140
    }
141
142
    /**
143
     * @return void
144
     */
145
    private function endDbTransaction()
146
    {
147
        try {
148
            app('db')->rollBack();
149
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
150
        }
151
    }
152
153
    /**
154
     * @return void
155
     */
156
    private function finish()
157
    {
158
        $this->endDbTransaction();
159
    }
160
161
    /**
162
     * @param Request $request
163
     *
164
     * @return \Illuminate\Http\JsonResponse|mixed
165
     */
166
    public function callDingoRoute(Request $request)
167
    {
168
        /** @var Dispatcher $dispatcher */
169
        $dispatcher = app(\Dingo\Api\Dispatcher::class);
170
171
        foreach ($request->headers as $header => $value) {
172
            $dispatcher->header($header, $value);
173
        }
174
175
        // set domain and body parameters
176
        $dispatcher->on($request->header('SERVER_NAME'))
177
            ->with($request->request->all());
178
179
        // set URL and query parameters
180
        $uri = $request->getRequestUri();
181
        $query = $request->getQueryString();
182
        if (! empty($query)) {
183
            $uri .= "?$query";
184
        }
185
        $response = call_user_func_array([$dispatcher, strtolower($request->method())], [$uri]);
186
187
        // the response from the Dingo dispatcher is the 'raw' response from the controller,
188
        // so we have to ensure it's JSON first
189
        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...
190
            $response = response()->json($response);
191
        }
192
193
        return $response;
194
    }
195
196
    /**
197
     * @param Route $route
198
     *
199
     * @return array
200
     */
201
    public function getMethods(Route $route)
202
    {
203
        return array_diff($route->methods(), ['HEAD']);
204
    }
205
206
    /**
207
     * @param Request $request
208
     * @param Route $route
209
     * @param array|null $headers
210
     *
211
     * @return Request
212
     */
213
    private function addHeaders(Request $request, Route $route, $headers)
214
    {
215
        // set the proper domain
216
        if ($route->getDomain()) {
217
            $request->headers->add([
218
                'HOST' => $route->getDomain(),
219
            ]);
220
            $request->server->add([
221
                'HTTP_HOST' => $route->getDomain(),
222
                'SERVER_NAME' => $route->getDomain(),
223
            ]);
224
        }
225
226
        $headers = collect($headers);
227
228
        if (($headers->get('Accept') ?: $headers->get('accept')) === 'application/json') {
229
            $request->setRequestFormat('json');
230
        }
231
232
        return $request;
233
    }
234
235
    /**
236
     * @param Request $request
237
     * @param array $query
238
     *
239
     * @return Request
240
     */
241
    private function addQueryParameters(Request $request, array $query)
242
    {
243
        $request->query->add($query);
244
        $request->server->add(['QUERY_STRING' => http_build_query($query)]);
245
246
        return $request;
247
    }
248
249
    /**
250
     * @param Request $request
251
     * @param array $body
252
     *
253
     * @return Request
254
     */
255
    private function addBodyParameters(Request $request, array $body)
256
    {
257
        $request->request->add($body);
258
259
        return $request;
260
    }
261
262
    /**
263
     * @param Request $request
264
     *
265
     * @throws \Exception
266
     *
267
     * @return \Illuminate\Http\JsonResponse|mixed|\Symfony\Component\HttpFoundation\Response
268
     */
269
    protected function makeApiCall(Request $request)
270
    {
271
        if (config('apidoc.router') == 'dingo') {
272
            $response = $this->callDingoRoute($request);
273
        } else {
274
            $response = $this->callLaravelRoute($request);
275
        }
276
277
        return $response;
278
    }
279
280
    /**
281
     * @param Request $request
282
     *
283
     * @throws \Exception
284
     *
285
     * @return \Symfony\Component\HttpFoundation\Response
286
     */
287
    protected function callLaravelRoute(Request $request): \Symfony\Component\HttpFoundation\Response
288
    {
289
        // Confirm we're running in Laravel, not Lumen
290
        if (app()->bound(\Illuminate\Contracts\Http\Kernel::class)) {
291
            $kernel = app(\Illuminate\Contracts\Http\Kernel::class);
292
            $response = $kernel->handle($request);
293
            $kernel->terminate($request, $response);
294
        } else {
295
            // Handle the request using the Lumen application.
296
            $kernel = app();
297
            $response = $kernel->handle($request);
298
        }
299
300
        return $response;
301
    }
302
303
    /**
304
     * @param Route $route
305
     * @param array $rulesToApply
306
     *
307
     * @return bool
308
     */
309
    private function shouldMakeApiCall(Route $route, array $rulesToApply, array $context): bool
310
    {
311
        $allowedMethods = $rulesToApply['methods'] ?? [];
312
        if (empty($allowedMethods)) {
313
            return false;
314
        }
315
316
        if (! empty($context['responses'])) {
317
            // Don't attempt a response call if there are already responses
318
            return false;
319
        }
320
321
        if (is_string($allowedMethods) && $allowedMethods == '*') {
322
            return true;
323
        }
324
325
        if (array_search('*', $allowedMethods) !== false) {
326
            return true;
327
        }
328
329
        $routeMethods = $this->getMethods($route);
330
        if (in_array(array_shift($routeMethods), $allowedMethods)) {
331
            return true;
332
        }
333
334
        return false;
335
    }
336
337
    /**
338
     * Transform headers array to array of $_SERVER vars with HTTP_* format.
339
     *
340
     * @param  array  $headers
341
     *
342
     * @return array
343
     */
344
    protected function transformHeadersToServerVars(array $headers)
345
    {
346
        $server = [];
347
        $prefix = 'HTTP_';
348
        foreach ($headers as $name => $value) {
349
            $name = strtr(strtoupper($name), '-', '_');
350
            if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') {
351
                $name = $prefix.$name;
352
            }
353
            $server[$name] = $value;
354
        }
355
356
        return $server;
357
    }
358
}
359