Completed
Pull Request — master (#351)
by
unknown
06:32
created

AbstractGenerator::getRouteGroup()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 4
nc 3
nop 1
1
<?php
2
3
namespace Mpociot\ApiDoc\Generators;
4
5
use ReflectionClass;
6
use Illuminate\Support\Str;
7
use League\Fractal\Manager;
8
use Illuminate\Routing\Route;
9
use Mpociot\Reflection\DocBlock;
10
use League\Fractal\Resource\Item;
11
use Mpociot\Reflection\DocBlock\Tag;
12
use League\Fractal\Resource\Collection;
13
14
abstract class AbstractGenerator
15
{
16
    /**
17
     * @param Route $route
18
     *
19
     * @return mixed
20
     */
21
    public function getDomain(Route $route)
22
    {
23
        return $route->domain() == null ? '*' : $route->domain();
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $route->domain() of type string|null|Illuminate\Routing\Route against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
24
    }
25
26
    /**
27
     * @param Route $route
28
     *
29
     * @return mixed
30
     */
31
    public function getUri(Route $route)
32
    {
33
        return $route->uri();
34
    }
35
36
    /**
37
     * @param Route $route
38
     *
39
     * @return mixed
40
     */
41
    public function getMethods(Route $route)
42
    {
43
        return array_diff($route->methods(), ['HEAD']);
44
    }
45
46
    /**
47
     * @param  \Illuminate\Routing\Route $route
48
     * @param array $apply Rules to apply when generating documentation for this route
49
     *
50
     * @return array
51
     */
52
    public function processRoute($route, $apply = [])
0 ignored issues
show
Unused Code introduced by
The parameter $apply is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
53
    {
54
        $routeAction = $route->getAction();
55
        $routeGroup = $this->getRouteGroup($routeAction['uses']);
56
        $routeDescription = $this->getRouteDescription($routeAction['uses']);
57
        $showresponse = null;
58
59
        $response = null;
60
        $docblockResponse = $this->getDocblockResponse($routeDescription['tags']);
61
        if ($docblockResponse) {
62
            // we have a response from the docblock ( @response )
63
            $response = $docblockResponse;
64
            $showresponse = true;
65
        }
66
        if (! $response) {
67
            $transformerResponse = $this->getTransformerResponse($routeDescription['tags']);
68
            if ($transformerResponse) {
69
                // we have a transformer response from the docblock ( @transformer || @transformercollection )
70
                $response = $transformerResponse;
71
                $showresponse = true;
72
            }
73
        }
74
75
        $content = $this->getResponseContent($response);
76
77
        return [
78
            'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))),
79
            'resource' => $routeGroup,
80
            'title' => $routeDescription['short'],
81
            'description' => $routeDescription['long'],
82
            'methods' => $this->getMethods($route),
83
            'uri' => $this->getUri($route),
84
            'parameters' => $this->getParametersFromDocBlock($routeAction['uses']),
85
            'response' => $content,
86
            'showresponse' => $showresponse,
87
        ];
88
    }
89
90
    /**
91
     * Prepares / Disables route middlewares.
92
     *
93
     * @param  bool $disable
0 ignored issues
show
Bug introduced by
There is no parameter named $disable. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
94
     *
95
     * @return  void
96
     */
97
    abstract public function prepareMiddleware($enable = false);
98
99
    /**
100
     * Get the response from the docblock if available.
101
     *
102
     * @param array $tags
103
     *
104
     * @return mixed
105
     */
106
    protected function getDocblockResponse($tags)
107
    {
108
        $responseTags = array_filter($tags, function ($tag) {
109
            if (! ($tag instanceof Tag)) {
110
                return false;
111
            }
112
113
            return \strtolower($tag->getName()) == 'response';
114
        });
115
        if (empty($responseTags)) {
116
            return;
117
        }
118
        $responseTag = \array_first($responseTags);
119
120
        return \response(json_encode($responseTag->getContent()), 200, ['Content-Type' => 'application/json']);
121
    }
122
123
    /**
124
     * @param array $routeAction
125
     * @return array
126
     */
127
    protected function getParametersFromDocBlock($routeAction)
0 ignored issues
show
Unused Code introduced by
The parameter $routeAction is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
128
    {
129
        return [];
130
    }
131
132
    /**
133
     * @param  $route
134
     * @param  $bindings
135
     * @param  $headers
136
     *
137
     * @return \Illuminate\Http\Response
138
     */
139
    protected function getRouteResponse($route, $bindings, $headers = [])
140
    {
141
        $uri = $this->addRouteModelBindings($route, $bindings);
142
143
        $methods = $this->getMethods($route);
144
145
        // Split headers into key - value pairs
146
        $headers = collect($headers)->map(function ($value) {
147
            $split = explode(':', $value); // explode to get key + values
148
            $key = array_shift($split); // extract the key and keep the values in the array
149
            $value = implode(':', $split); // implode values into string again
150
151
            return [trim($key) => trim($value)];
152
        })->collapse()->toArray();
153
154
        //Changes url with parameters like /users/{user} to /users/1
155
        $uri = preg_replace('/{(.*?)}/', 1, $uri); // 1 is the default value for route parameters
156
157
        return $this->callRoute(array_shift($methods), $uri, [], [], [], $headers);
158
    }
159
160
    /**
161
     * @param $route
162
     * @param array $bindings
163
     *
164
     * @return mixed
165
     */
166
    protected function addRouteModelBindings($route, $bindings)
167
    {
168
        $uri = $this->getUri($route);
169
        foreach ($bindings as $model => $id) {
170
            $uri = str_replace('{'.$model.'}', $id, $uri);
171
            $uri = str_replace('{'.$model.'?}', $id, $uri);
172
        }
173
174
        return $uri;
175
    }
176
177
    /**
178
     * @param  \Illuminate\Routing\Route  $route
179
     *
180
     * @return array
181
     */
182
    protected function getRouteDescription($route)
183
    {
184
        list($class, $method) = explode('@', $route);
185
        $reflection = new ReflectionClass($class);
186
        $reflectionMethod = $reflection->getMethod($method);
187
188
        $comment = $reflectionMethod->getDocComment();
189
        $phpdoc = new DocBlock($comment);
190
191
        return [
192
            'short' => $phpdoc->getShortDescription(),
193
            'long' => $phpdoc->getLongDescription()->getContents(),
194
            'tags' => $phpdoc->getTags(),
195
        ];
196
    }
197
198
    /**
199
     * @param  string  $route
200
     *
201
     * @return string
202
     */
203
    protected function getRouteGroup($route)
204
    {
205
        list($class, $method) = explode('@', $route);
0 ignored issues
show
Unused Code introduced by
The assignment to $method is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
206
        $reflection = new ReflectionClass($class);
207
        $comment = $reflection->getDocComment();
208
        if ($comment) {
209
            $phpdoc = new DocBlock($comment);
210
            foreach ($phpdoc->getTags() as $tag) {
211
                if ($tag->getName() === 'resource') {
212
                    return $tag->getContent();
213
                }
214
            }
215
        }
216
217
        return 'general';
218
    }
219
220
    /**
221
     * Call the given URI and return the Response.
222
     *
223
     * @param  string  $method
224
     * @param  string  $uri
225
     * @param  array  $parameters
226
     * @param  array  $cookies
227
     * @param  array  $files
228
     * @param  array  $server
229
     * @param  string  $content
230
     *
231
     * @return \Illuminate\Http\Response
232
     */
233
    abstract public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null);
234
235
    /**
236
     * Transform headers array to array of $_SERVER vars with HTTP_* format.
237
     *
238
     * @param  array  $headers
239
     *
240
     * @return array
241
     */
242
    protected function transformHeadersToServerVars(array $headers)
243
    {
244
        $server = [];
245
        $prefix = 'HTTP_';
246
247
        foreach ($headers as $name => $value) {
248
            $name = strtr(strtoupper($name), '-', '_');
249
250
            if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') {
251
                $name = $prefix.$name;
252
            }
253
254
            $server[$name] = $value;
255
        }
256
257
        return $server;
258
    }
259
260
    /**
261
     * @param $response
262
     *
263
     * @return mixed
264
     */
265
    private function getResponseContent($response)
266
    {
267
        if (empty($response)) {
268
            return '';
269
        }
270
        if ($response->headers->get('Content-Type') === 'application/json') {
271
            $content = json_decode($response->getContent(), JSON_PRETTY_PRINT);
272
        } else {
273
            $content = $response->getContent();
274
        }
275
276
        return $content;
277
    }
278
279
    /**
280
     * Get a response from the transformer tags.
281
     *
282
     * @param array $tags
283
     *
284
     * @return mixed
285
     */
286
    protected function getTransformerResponse($tags)
287
    {
288
        try {
289
            $transFormerTags = array_filter($tags, function ($tag) {
290
                if (! ($tag instanceof Tag)) {
291
                    return false;
292
                }
293
294
                return \in_array(\strtolower($tag->getName()), ['transformer', 'transformercollection']);
295
            });
296
            if (empty($transFormerTags)) {
297
                // we didn't have any of the tags so goodbye
298
                return false;
299
            }
300
301
            $modelTag = array_first(array_filter($tags, function ($tag) {
302
                if (! ($tag instanceof Tag)) {
303
                    return false;
304
                }
305
306
                return \in_array(\strtolower($tag->getName()), ['transformermodel']);
307
            }));
308
            $tag = \array_first($transFormerTags);
309
            $transformer = $tag->getContent();
310
            if (! \class_exists($transformer)) {
311
                // if we can't find the transformer we can't generate a response
312
                return;
313
            }
314
            $demoData = [];
315
316
            $reflection = new ReflectionClass($transformer);
317
            $method = $reflection->getMethod('transform');
318
            $parameter = \array_first($method->getParameters());
319
            $type = null;
320
            if ($modelTag) {
321
                $type = $modelTag->getContent();
322
            }
323
            if (version_compare(PHP_VERSION, '7.0.0') >= 0 && \is_null($type)) {
324
                // we can only get the type with reflection for PHP 7
325
                if ($parameter->hasType() &&
326
                    ! $parameter->getType()->isBuiltin() &&
327
                    \class_exists((string) $parameter->getType())) {
328
                    //we have a type
329
                    $type = (string) $parameter->getType();
330
                }
331
            }
332
            if ($type) {
333
                // we have a class so we try to create an instance
334
                $demoData = new $type;
335
                try {
336
                    // try a factory
337
                    $demoData = \factory($type)->make();
338
                } catch (\Exception $e) {
339
                    if ($demoData instanceof \Illuminate\Database\Eloquent\Model) {
340
                        // we can't use a factory but can try to get one from the database
341
                        try {
342
                            // check if we can find one
343
                            $newDemoData = $type::first();
344
                            if ($newDemoData) {
345
                                $demoData = $newDemoData;
346
                            }
347
                        } catch (\Exception $e) {
348
                            // do nothing
349
                        }
350
                    }
351
                }
352
            }
353
354
            $fractal = new Manager();
355
            $resource = [];
356
            if ($tag->getName() == 'transformer') {
357
                // just one
358
                $resource = new Item($demoData, new $transformer);
359
            }
360
            if ($tag->getName() == 'transformercollection') {
361
                // a collection
362
                $resource = new Collection([$demoData, $demoData], new $transformer);
363
            }
364
365
            return \response($fractal->createData($resource)->toJson());
0 ignored issues
show
Bug introduced by
It seems like $resource defined by array() on line 355 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...
366
        } catch (\Exception $e) {
367
            // it isn't possible to parse the transformer
368
            return;
369
        }
370
    }
371
}
372