Completed
Pull Request — master (#212)
by Salah
01:44
created

LaravelGenerator::callRoute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 21
rs 9.3142
cc 2
eloc 13
nc 2
nop 7
1
<?php
2
3
namespace Mpociot\ApiDoc\Generators;
4
5
use ReflectionClass;
6
use League\Fractal\Manager;
7
use Illuminate\Routing\Route;
8
use League\Fractal\Resource\Item;
9
use Illuminate\Support\Facades\App;
10
use Illuminate\Support\Facades\Request;
11
use League\Fractal\Resource\Collection;
12
use Illuminate\Foundation\Http\FormRequest;
13
14
class LaravelGenerator extends AbstractGenerator
15
{
16
    /**
17
     * @param  \Illuminate\Routing\Route $route
18
     * @param array $bindings
19
     * @param array $headers
20
     * @param bool $withResponse
21
     *
22
     * @return array
23
     */
24
    public function processRoute($route, $bindings = [], $headers = [], $withResponse = true)
25
    {
26
        $content = '';
27
28
        $routeAction = $route->getAction();
29
        $routeGroup = $this->getRouteGroup($routeAction['uses']);
30
        $routeDescription = $this->getRouteDescription($routeAction['uses']);
31
        $showresponse = null;
32
33
        if ($withResponse) {
34
            $response = null;
35
            $docblockResponse = $this->getDocblockResponse($routeDescription['tags']);
36
            if ($docblockResponse) {
37
                // we have a response from the docblock ( @response )
38
                $response = $docblockResponse;
39
                $showresponse = true;
40
            }
41
            if (! $response) {
42
                $transformerResponse = $this->getTransformerResponse($routeDescription['tags']);
43
                if ($transformerResponse) {
44
                    // we have a transformer response from the docblock ( @transformer || @transformercollection )
45
                    $response = $transformerResponse;
46
                    $showresponse = true;
47
                }
48
            }
49
            if (! $response) {
50
                $responseClassResponse = $this->getResponseClassResponse($routeDescription['tags']);
51
                if ($responseClassResponse) {
52
                    // we have a class response from the docblock ( @responseClass )
53
                    $response = $responseClassResponse;
54
                    $showresponse = true;
55
                }
56
            }
57
            if (! $response) {
58
                $dataResponse = $this->getDataResponse($routeDescription['tags']);
59
                if ($dataResponse) {
60
                    // we have a data response from the docblock ( @data )
61
                    $response = $dataResponse;
62
                    $showresponse = true;
63
                }
64
            }
65
            if (! $response) {
66
                $response = $this->getRouteResponse($route, $bindings, $headers);
67
            }
68
            if ($response->headers->get('Content-Type') === 'application/json') {
69
                $content = json_encode(json_decode($response->getContent()), JSON_PRETTY_PRINT);
70
            } else {
71
                $content = $response->getContent();
72
            }
73
        }
74
75
        return $this->getParameters([
76
            'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))),
77
            'resource' => $routeGroup,
78
            'title' => $routeDescription['short'],
79
            'description' => $routeDescription['long'],
80
            'methods' => $this->getMethods($route),
81
            'uri' => $this->getUri($route),
82
            'parameters' => [],
83
            'response' => $content,
84
            'showresponse' => $showresponse,
85
        ], $routeAction, $bindings);
86
    }
87
88
    /**
89
     * Get a response from the transformer tags.
90
     *
91
     * @param array $tags
92
     *
93
     * @return mixed
94
     */
95
    protected function getTransformerResponse($tags)
96
    {
97
        try {
98
            $transFormerTag = $this->getFirstTagFromDocblock($tags, ['transformer', 'transformercollection']);
99
100
            if (empty($transFormerTag) || count($transFormerTag) == 0) {
101
                // we didn't have any of the tags so goodbye
102
                return false;
103
            }
104
105
            $modelTag = $this->getFirstTagFromDocblock($tags, ['transformermodel']);
106
            $transformer = $transFormerTag->getContent();
107
            if (! \class_exists($transformer)) {
108
                // if we can't find the transformer we can't generate a response
109
                return;
110
            }
111
            $demoData = [];
112
113
            $reflection = new ReflectionClass($transformer);
114
            $method = $reflection->getMethod('transform');
115
            $parameter = \array_first($method->getParameters());
116
            $type = null;
117
            if ($modelTag) {
118
                $type = $modelTag->getContent();
119
            }
120
            if (version_compare(PHP_VERSION, '7.0.0') >= 0 && \is_null($type)) {
121
                // we can only get the type with reflection for PHP 7
122
                if ($parameter->hasType() && ! $parameter->getType()->isBuiltin() && \class_exists((string) $parameter->getType())) {
123
                    //we have a type
124
                    $type = (string) $parameter->getType();
125
                }
126
            }
127
128
            // if we have modelTag
129
            if ($type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
130
                // we have a class so we try to create an instance
131
                $demoData = new $type;
132
                try {
133
                    // try a factory
134
                    $demoData = \factory($type)->make();
135
                } catch (\Exception $e) {
136
                    if ($demoData instanceof \Illuminate\Database\Eloquent\Model) {
137
                        // we can't use a factory but can try to get one from the database
138
                        try {
139
                            // check if we can find one
140
                            $newDemoData = $type::first();
141
                            if ($newDemoData) {
142
                                $demoData = $newDemoData;
143
                            }
144
                        } catch (\Exception $e) {
145
                            // do nothing
146
                        }
147
                    }
148
                }
149
            } else {
150
                // or try get data use ( @data ) tag
151
                $demoData = $this->getDataTag($tags);
152
            }
153
154
            $serializerTags = $this->getFirstTagFromDocblock($tags, 'serializer');
155
156
            if (is_object($serializerTags)) {
157
                $serializer = $serializerTags->getContent();
158
            }
159
160
            $fractal = new Manager();
161
            $resource = [];
162
            // allow use custom serializer
163
            if (! empty($serializer) && class_exists($serializer)) {
164
                $fractal->setSerializer(new $serializer());
165
            }
166
            if ($transFormerTag->getName() == 'transformer') {
167
                // just one
168
                $resource = new Item($demoData, new $transformer);
169
            }
170
            if ($transFormerTag->getName() == 'transformercollection') {
171
                // a collection
172
                $resource = new Collection([$demoData, $demoData], new $transformer);
173
            }
174
175
            return \response($fractal->createData($resource)->toJson());
0 ignored issues
show
Bug introduced by
It seems like $resource defined by array() on line 161 can also be of type array; however, League\Fractal\Manager::createData() does only seem to accept object<League\Fractal\Resource\ResourceInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
176
        } catch (\Exception $e) {
177
            // it isn't possible to parse the transformer
178
            return;
179
        }
180
    }
181
182
    /**
183
     * Get response content use responseclass tag.
184
     *
185
     * @param $tags
186
     *
187
     * @return bool|void
188
     */
189
    protected function getResponseClassResponse($tags)
190
    {
191
        try {
192
            $responseClassTag = $this->getFirstTagFromDocblock($tags, ['responseclass']);
193
194
            if (empty($responseClassTag) || count($responseClassTag) == 0) {
195
                // we didn't have any of the tags so goodbye
196
                return false;
197
            }
198
199
            $responseClass = $responseClassTag->getContent();
200
            if (! \class_exists($responseClass)) {
201
                // if we can't find the response class we can't generate a response
202
                return;
203
            }
204
205
            $demoData = new $responseClass($this->getDataTag($tags, null));
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
206
207
            return \response($demoData);
0 ignored issues
show
Documentation introduced by
$demoData is of type object, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
208
        } catch (\Exception $exception) {
209
            return;
210
        }
211
    }
212
213
    /**
214
     * @param Route $route
215
     *
216
     * @return mixed
217
     */
218
    public function getUri($route)
219
    {
220
        if (version_compare(app()->version(), '5.4', '<')) {
221
            return $route->getUri();
0 ignored issues
show
Bug introduced by
The method getUri() does not seem to exist on object<Illuminate\Routing\Route>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
222
        }
223
224
        return $route->uri();
225
    }
226
227
    /**
228
     * @param Route $route
229
     *
230
     * @return mixed
231
     */
232
    public function getMethods($route)
233
    {
234
        if (version_compare(app()->version(), '5.4', '<')) {
235
            return $route->getMethods();
0 ignored issues
show
Bug introduced by
The method getMethods() does not exist on Illuminate\Routing\Route. Did you maybe mean methods()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
236
        }
237
238
        return $route->methods();
239
    }
240
241
    /**
242
     * Prepares / Disables route middlewares.
243
     *
244
     * @param  bool $disable
245
     *
246
     * @return  void
247
     */
248
    public function prepareMiddleware($disable = true)
249
    {
250
        App::instance('middleware.disable', true);
0 ignored issues
show
Bug introduced by
The method instance() does not exist on Illuminate\Support\Facades\App. Did you maybe mean clearResolvedInstance()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
251
    }
252
253
    /**
254
     * Call the given URI and return the Response.
255
     *
256
     * @param  string $method
257
     * @param  string $uri
258
     * @param  array $parameters
259
     * @param  array $cookies
260
     * @param  array $files
261
     * @param  array $server
262
     * @param  string $content
263
     *
264
     * @return \Illuminate\Http\Response
265
     */
266
    public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
267
    {
268
        $server = collect([
269
            'CONTENT_TYPE' => 'application/json',
270
            'Accept' => 'application/json',
271
        ])->merge($server)->toArray();
272
273
        $request = Request::create($uri, $method, $parameters, $cookies, $files, $this->transformHeadersToServerVars($server), $content);
0 ignored issues
show
Bug introduced by
The method create() does not exist on Illuminate\Support\Facades\Request. Did you maybe mean createFreshMockInstance()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
274
275
        $kernel = App::make('Illuminate\Contracts\Http\Kernel');
276
        $response = $kernel->handle($request);
277
278
        $kernel->terminate($request, $response);
279
280
        if (file_exists($file = App::bootstrapPath().'/app.php')) {
281
            $app = require $file;
282
            $app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
283
        }
284
285
        return $response;
286
    }
287
288
    /**
289
     * @param  string $route
290
     * @param  array $bindings
291
     *
292
     * @return array
293
     */
294
    protected function getRouteRules($route, $bindings)
295
    {
296
        list($class, $method) = explode('@', $route);
297
        $reflection = new ReflectionClass($class);
298
        $reflectionMethod = $reflection->getMethod($method);
299
300
        foreach ($reflectionMethod->getParameters() as $parameter) {
301
            $parameterType = $parameter->getClass();
302
            if (! is_null($parameterType) && class_exists($parameterType->name)) {
303
                $className = $parameterType->name;
304
305
                if (is_subclass_of($className, FormRequest::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Illuminate\Foundation\Http\FormRequest::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
306
                    $parameterReflection = new $className;
307
                    $parameterReflection->setContainer(app());
308
                    // Add route parameter bindings
309
                    $parameterReflection->query->add($bindings);
310
                    $parameterReflection->request->add($bindings);
311
312
                    if (method_exists($parameterReflection, 'validator')) {
313
                        return app()->call([$parameterReflection, 'validator'])->getRules();
314
                    } else {
315
                        return app()->call([$parameterReflection, 'rules']);
316
                    }
317
                }
318
            }
319
        }
320
321
        return [];
322
    }
323
324
    /**
325
     * Get Custom Data from data tag.
326
     *
327
     * @param $tags
328
     * @param array $default
329
     *
330
     * @return array|null
331
     */
332
    protected function getDataTag($tags, $default = [])
333
    {
334
        $additionData = $this->getFirstTagFromDocblock($tags, 'data');
335
336
        if (empty($additionData) || count($additionData) == 0) {
337
            // we didn't have any of the tags so goodbye
338
            return $default;
339
        }
340
341
        $additionData = explode(',', $additionData->getContent());
342
343
        $additionData = array_column(array_map(function ($v) {
344
            return explode('|', $v);
345
        }, $additionData), 1, 0);
346
347
        return $additionData;
348
    }
349
350
    /**
351
     * Get Response use @data tag.
352
     *
353
     * @param $tags
354
     *
355
     * @return bool|\Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
356
     */
357
    protected function getDataResponse($tags)
358
    {
359
        $data = $this->getDataTag($tags, false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
360
361
        return $data ? \response($data) : false;
0 ignored issues
show
Documentation introduced by
$data is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
362
    }
363
}
364