Completed
Push — master ( 303c41...204eee )
by Marcel
02:19
created

AbstractGenerator::getRouteResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
namespace Mpociot\ApiDoc\Generators;
4
5
use Faker\Factory;
6
use ReflectionClass;
7
use Illuminate\Support\Str;
8
use Illuminate\Routing\Route;
9
use phpDocumentor\Reflection\DocBlock;
10
use Illuminate\Support\Facades\Validator;
11
use Illuminate\Foundation\Http\FormRequest;
12
13
abstract class AbstractGenerator
14
{
15
16
    /**
17
     * @param Route $route
18
     * @return mixed
19
     */
20
    abstract protected function getUri(Route $route);
21
22
    /**
23
     * @param  \Illuminate\Routing\Route $route
24
     * @param array $bindings
25
     *
26
     * @return array
27
     */
28
    abstract public function processRoute(Route $route, $bindings = []);
29
30
    /**
31
     * @param array $routeData
32
     * @param array $routeAction
33
     * @return mixed
34
     */
35
    protected function getParameters($routeData, $routeAction) {
36
        $validator = Validator::make([], $this->getRouteRules($routeAction['uses']));
37
        foreach ($validator->getRules() as $attribute => $rules) {
38
            $attributeData = [
39
                'required' => false,
40
                'type' => 'string',
41
                'default' => '',
42
                'value' => '',
43
                'description' => [],
44
            ];
45
            foreach ($rules as $rule) {
46
                $this->parseRule($rule, $attributeData);
47
            }
48
            $routeData['parameters'][$attribute] = $attributeData;
49
        }
50
51
        return $routeData;
52
    }
53
54
    /**
55
     * @param  \Illuminate\Routing\Route  $route
56
     *
57
     * @return \Illuminate\Http\Response
58
     */
59
    protected function getRouteResponse(Route $route, $bindings)
60
    {
61
        $uri = $this->addRouteModelBindings($route, $bindings);
62
63
        $methods = $route->getMethods();
64
65
        return $this->callRoute(array_shift($methods), $uri);
66
    }
67
68
69
    /**
70
     * @param Route $route
71
     * @param array $bindings
72
     * @return mixed
73
     */
74
    protected function addRouteModelBindings(Route $route, $bindings)
75
    {
76
        $uri = $this->getUri($route);
77
        foreach ($bindings as $model => $id) {
78
            $uri = str_replace('{'.$model.'}', $id, $uri);
79
        }
80
        return $uri;
81
    }
82
83
    /**
84
     * @param  \Illuminate\Routing\Route  $route
85
     *
86
     * @return string
87
     */
88
    protected function getRouteDescription($route)
89
    {
90
        list($class, $method) = explode('@', $route);
91
        $reflection = new ReflectionClass($class);
92
        $reflectionMethod = $reflection->getMethod($method);
93
94
        $comment = $reflectionMethod->getDocComment();
95
        $phpdoc = new DocBlock($comment);
96
97
        return [
98
            'short' => $phpdoc->getShortDescription(),
99
            'long' => $phpdoc->getLongDescription()->getContents(),
100
        ];
101
    }
102
103
104
    /**
105
     * @param  string  $route
106
     *
107
     * @return string
108
     */
109
    protected function getRouteGroup($route)
110
    {
111
        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...
112
        $reflection = new ReflectionClass($class);
113
        $comment = $reflection->getDocComment();
114
        $phpdoc = new DocBlock($comment);
115
        foreach ($phpdoc->getTags() as $tag) {
116
            if ($tag->getName() === 'resource') {
117
                return $tag->getContent();
118
            }
119
        }
120
        return 'general';
121
    }
122
123
    /**
124
     * @param  $route
125
     *
126
     * @return array
127
     */
128
    protected function getRouteRules($route)
129
    {
130
        list($class, $method) = explode('@', $route);
131
        $reflection = new ReflectionClass($class);
132
        $reflectionMethod = $reflection->getMethod($method);
133
134
        foreach ($reflectionMethod->getParameters() as $parameter) {
135
            $parameterType = $parameter->getClass();
136
            if (! is_null($parameterType) && class_exists($parameterType->name)) {
137
                $className = $parameterType->name;
138
                $parameterReflection = new $className;
139
                if ($parameterReflection instanceof FormRequest) {
140
                    if (method_exists($parameterReflection, 'validator')) {
141
                        return $parameterReflection->validator()->getRules();
0 ignored issues
show
Bug introduced by
The method validator() does not exist on Illuminate\Foundation\Http\FormRequest. Did you maybe mean getValidatorInstance()?

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...
142
                    } else {
143
                        return $parameterReflection->rules();
144
                    }
145
                }
146
            }
147
        }
148
149
        return [];
150
    }
151
152
    /**
153
     * @param  array  $arr
154
     * @param  string  $first
155
     * @param  string  $last
156
     *
157
     * @return string
158
     */
159
    protected function fancyImplode($arr, $first, $last)
160
    {
161
        $arr = array_map(function ($value) {
162
            return '`'.$value.'`';
163
        }, $arr);
164
        array_push($arr, implode($last, array_splice($arr, -2)));
165
166
        return implode($first, $arr);
167
    }
168
169
    /**
170
     * @param  string  $rule
171
     * @param  array  $attributeData
172
     *
173
     * @return void
174
     */
175
    protected function parseRule($rule, &$attributeData)
176
    {
177
        $faker = Factory::create();
178
179
        $parsedRule = $this->parseStringRule($rule);
180
        $parsedRule[0] = $this->normalizeRule($parsedRule[0]);
181
        list($rule, $parameters) = $parsedRule;
182
183
        switch ($rule) {
184
            case 'required':
185
                $attributeData['required'] = true;
186
                break;
187
            case 'accepted':
188
                $attributeData['required'] = true;
189
                $attributeData['type'] = 'boolean';
190
                $attributeData['value'] = true;
191
                break;
192 View Code Duplication
            case 'after':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
                $attributeData['type'] = 'date';
194
                $attributeData['description'][] = 'Must be a date after: `'.date(DATE_RFC850, strtotime($parameters[0])).'`';
195
                $attributeData['value'] = date(DATE_RFC850, strtotime('+1 day', strtotime($parameters[0])));
196
                break;
197
            case 'alpha':
198
                $attributeData['description'][] = 'Only alphabetic characters allowed';
199
                $attributeData['value'] = $faker->word;
200
                break;
201
            case 'alpha_dash':
202
                $attributeData['description'][] = 'Allowed: alpha-numeric characters, as well as dashes and underscores.';
203
                break;
204
            case 'alpha_num':
205
                $attributeData['description'][] = 'Only alpha-numeric characters allowed';
206
                break;
207
            case 'in':
208
                $attributeData['description'][] = $this->fancyImplode($parameters, ', ', ' or ');
209
                $attributeData['value'] = $faker->randomElement($parameters);
0 ignored issues
show
Bug introduced by
The call to randomElement() misses some required arguments starting with $'b'.
Loading history...
210
                break;
211
            case 'not_in':
212
                $attributeData['description'][] = 'Not in: '.$this->fancyImplode($parameters, ', ', ' or ');
213
                $attributeData['value'] = $faker->word;
214
                break;
215
            case 'min':
216
                $attributeData['description'][] = 'Minimum: `'.$parameters[0].'`';
217
                break;
218
            case 'max':
219
                $attributeData['description'][] = 'Maximum: `'.$parameters[0].'`';
220
                break;
221
            case 'between':
222
                $attributeData['type'] = 'numeric';
223
                $attributeData['description'][] = 'Between: `'.$parameters[0].'` and `'.$parameters[1].'`';
224
                $attributeData['value'] = $faker->numberBetween($parameters[0], $parameters[1]);
225
                break;
226 View Code Duplication
            case 'before':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
                $attributeData['type'] = 'date';
228
                $attributeData['description'][] = 'Must be a date preceding: `'.date(DATE_RFC850, strtotime($parameters[0])).'`';
229
                $attributeData['value'] = date(DATE_RFC850, strtotime('-1 day', strtotime($parameters[0])));
230
                break;
231
            case 'date_format':
232
                $attributeData['type'] = 'date';
233
                $attributeData['description'][] = 'Date format: `'.$parameters[0].'`';
234
                break;
235
            case 'different':
236
                $attributeData['description'][] = 'Must have a different value than parameter: `'.$parameters[0].'`';
237
                break;
238
            case 'digits':
239
                $attributeData['type'] = 'numeric';
240
                $attributeData['description'][] = 'Must have an exact length of `'.$parameters[0].'`';
241
                $attributeData['value'] = $faker->randomNumber($parameters[0], true);
242
                break;
243
            case 'digits_between':
244
                $attributeData['type'] = 'numeric';
245
                $attributeData['description'][] = 'Must have a length between `'.$parameters[0].'` and `'.$parameters[1].'`';
246
                break;
247
            case 'image':
248
                $attributeData['type'] = 'image';
249
                $attributeData['description'][] = 'Must be an image (jpeg, png, bmp, gif, or svg)';
250
                break;
251
            case 'json':
252
                $attributeData['type'] = 'string';
253
                $attributeData['description'][] = 'Must be a valid JSON string.';
254
                $attributeData['value'] = json_encode(['foo', 'bar', 'baz']);
255
                break;
256
            case 'mimetypes':
257
            case 'mimes':
258
                $attributeData['description'][] = 'Allowed mime types: '.$this->fancyImplode($parameters, ', ', ' or ');
259
                break;
260 View Code Duplication
            case 'required_if':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
261
                $attributeData['description'][] = 'Required if `'.$parameters[0].'` is `'.$parameters[1].'`';
262
                break;
263 View Code Duplication
            case 'required_unless':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
264
                $attributeData['description'][] = 'Required unless `'.$parameters[0].'` is `'.$parameters[1].'`';
265
                break;
266
            case 'required_with':
267
                $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' or ').' are present.';
268
                break;
269
            case 'required_with_all':
270
                $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' and ').' are present.';
271
                break;
272
            case 'required_without':
273
                $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' or ').' are not present.';
274
                break;
275
            case 'required_without_all':
276
                $attributeData['description'][] = 'Required if the parameters '.$this->fancyImplode($parameters, ', ', ' and ').' are not present.';
277
                break;
278
            case 'same':
279
                $attributeData['description'][] = 'Must be the same as `'.$parameters[0].'`';
280
                break;
281
            case 'size':
282
                $attributeData['description'][] = 'Must have the size of `'.$parameters[0].'`';
283
                break;
284
            case 'timezone':
285
                $attributeData['description'][] = 'Must be a valid timezone identifier';
286
                $attributeData['value'] = $faker->timezone;
287
                break;
288
            case 'exists':
289
                $attributeData['description'][] = 'Valid '.Str::singular($parameters[0]).' '.$parameters[1];
290
                break;
291
            case 'active_url':
292
                $attributeData['type'] = 'url';
293
                $attributeData['value'] = $faker->url;
294
                break;
295
            case 'regex':
296
                $attributeData['type'] = 'string';
297
                $attributeData['description'][] = 'Must match this regular expression: `'.$parameters[0].'`';
298
                break;
299
            case 'boolean':
300
                $attributeData['value'] = true;
301
                $attributeData['type'] = $rule;
302
                break;
303
            case 'array':
304
                $attributeData['value'] = $faker->word;
305
                $attributeData['type'] = $rule;
306
                break;
307
            case 'date':
308
                $attributeData['value'] = $faker->date();
309
                $attributeData['type'] = $rule;
310
                break;
311
            case 'email':
312
                $attributeData['value'] = $faker->safeEmail;
313
                $attributeData['type'] = $rule;
314
                break;
315
            case 'string':
316
                $attributeData['value'] = $faker->word;
317
                $attributeData['type'] = $rule;
318
                break;
319
            case 'integer':
320
                $attributeData['value'] = $faker->randomNumber();
321
                $attributeData['type'] = $rule;
322
                break;
323
            case 'numeric':
324
                $attributeData['value'] = $faker->randomNumber();
325
                $attributeData['type'] = $rule;
326
                break;
327
            case 'url':
328
                $attributeData['value'] = $faker->url;
329
                $attributeData['type'] = $rule;
330
                break;
331
            case 'ip':
332
                $attributeData['value'] = $faker->ipv4;
333
                $attributeData['type'] = $rule;
334
                break;
335
        }
336
337
        if ($attributeData['value'] === '') {
338
            $attributeData['value'] = $faker->word;
339
        }
340
    }
341
342
    /**
343
     * Call the given URI and return the Response.
344
     *
345
     * @param  string  $method
346
     * @param  string  $uri
347
     * @param  array  $parameters
348
     * @param  array  $cookies
349
     * @param  array  $files
350
     * @param  array  $server
351
     * @param  string  $content
352
     *
353
     * @return \Illuminate\Http\Response
354
     */
355
    abstract public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null);
356
357
    /**
358
     * Transform headers array to array of $_SERVER vars with HTTP_* format.
359
     *
360
     * @param  array  $headers
361
     *
362
     * @return array
363
     */
364
    protected function transformHeadersToServerVars(array $headers)
365
    {
366
        $server = [];
367
        $prefix = 'HTTP_';
368
369
        foreach ($headers as $name => $value) {
370
            $name = strtr(strtoupper($name), '-', '_');
371
372
            if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') {
373
                $name = $prefix.$name;
374
            }
375
376
            $server[$name] = $value;
377
        }
378
379
        return $server;
380
    }
381
382
    /**
383
     * Parse a string based rule.
384
     *
385
     * @param  string  $rules
386
     *
387
     * @return array
388
     */
389
    protected function parseStringRule($rules)
390
    {
391
        $parameters = [];
392
393
        // The format for specifying validation rules and parameters follows an
394
        // easy {rule}:{parameters} formatting convention. For instance the
395
        // rule "Max:3" states that the value may only be three letters.
396
        if (strpos($rules, ':') !== false) {
397
            list($rules, $parameter) = explode(':', $rules, 2);
398
399
            $parameters = $this->parseParameters($rules, $parameter);
400
        }
401
402
        return [strtolower(trim($rules)), $parameters];
403
    }
404
405
    /**
406
     * Parse a parameter list.
407
     *
408
     * @param  string  $rule
409
     * @param  string  $parameter
410
     *
411
     * @return array
412
     */
413
    protected function parseParameters($rule, $parameter)
414
    {
415
        if (strtolower($rule) === 'regex') {
416
            return [$parameter];
417
        }
418
419
        return str_getcsv($parameter);
420
    }
421
422
    /**
423
     * Normalizes a rule so that we can accept short types.
424
     *
425
     * @param  string $rule
426
     *
427
     * @return string
428
     */
429
    protected function normalizeRule($rule)
430
    {
431
        switch ($rule) {
432
            case 'int':
433
                return 'integer';
434
            case 'bool':
435
                return 'boolean';
436
            default:
437
                return $rule;
438
        }
439
    }
440
}
441