Completed
Push — master ( 49635d...de38da )
by Marcel
04:05
created

AbstractGenerator::normalizeRule()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 1
1
<?php
2
3
namespace Mpociot\ApiDoc\Generators;
4
5
use Faker\Factory;
6
use Illuminate\Foundation\Http\FormRequest;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Facades\Validator;
9
use Illuminate\Support\Str;
10
use Mpociot\ApiDoc\Parsers\RuleDescriptionParser as Description;
11
use ReflectionClass;
12
use Mpociot\Reflection\DocBlock;
13
14
abstract class AbstractGenerator
15
{
16
    /**
17
     * @param $route
18
     *
19
     * @return mixed
20
     */
21
    abstract protected function getUri($route);
22
23
    /**
24
     * @param  \Illuminate\Routing\Route $route
25
     * @param array $bindings
26
     * @param bool $withResponse
27
     *
28
     * @return array
29
     */
30
    abstract public function processRoute($route, $bindings = [], $withResponse = true);
31
32
    /**
33
     * @param array $routeData
34
     * @param array $routeAction
35
     * @param array $bindings
36
     *
37
     * @return mixed
38
     */
39
    protected function getParameters($routeData, $routeAction, $bindings)
40
    {
41
        $validator = Validator::make([], $this->getRouteRules($routeAction['uses'], $bindings));
42
        foreach ($validator->getRules() as $attribute => $rules) {
43
            $attributeData = [
44
                'required' => false,
45
                'type' => null,
46
                'default' => '',
47
                'value' => '',
48
                'description' => [],
49
            ];
50
            foreach ($rules as $ruleName => $rule) {
51
                $this->parseRule($rule, $attribute, $attributeData, $routeData['id']);
52
            }
53
            $routeData['parameters'][$attribute] = $attributeData;
54
        }
55
56
        return $routeData;
57
    }
58
59
    /**
60
     * @param  $route
61
     *
62
     * @return \Illuminate\Http\Response
63
     */
64
    protected function getRouteResponse($route, $bindings)
65
    {
66
        $uri = $this->addRouteModelBindings($route, $bindings);
67
68
        $methods = $route->getMethods();
69
70
        return $this->callRoute(array_shift($methods), $uri);
71
    }
72
73
    /**
74
     * @param $route
75
     * @param array $bindings
76
     *
77
     * @return mixed
78
     */
79
    protected function addRouteModelBindings($route, $bindings)
80
    {
81
        $uri = $this->getUri($route);
82
        foreach ($bindings as $model => $id) {
83
            $uri = str_replace('{'.$model.'}', $id, $uri);
84
        }
85
86
        return $uri;
87
    }
88
89
    /**
90
     * @param  \Illuminate\Routing\Route  $route
91
     *
92
     * @return string
93
     */
94
    protected function getRouteDescription($route)
95
    {
96
        list($class, $method) = explode('@', $route);
97
        $reflection = new ReflectionClass($class);
98
        $reflectionMethod = $reflection->getMethod($method);
99
100
        $comment = $reflectionMethod->getDocComment();
101
        $phpdoc = new DocBlock($comment);
102
103
        return [
104
            'short' => $phpdoc->getShortDescription(),
105
            'long' => $phpdoc->getLongDescription()->getContents(),
106
        ];
107
    }
108
109
    /**
110
     * @param  string  $route
111
     *
112
     * @return string
113
     */
114
    protected function getRouteGroup($route)
115
    {
116
        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...
117
        $reflection = new ReflectionClass($class);
118
        $comment = $reflection->getDocComment();
119
        if ($comment) {
120
            $phpdoc = new DocBlock($comment);
121
            foreach ($phpdoc->getTags() as $tag) {
122
                if ($tag->getName() === 'resource') {
123
                    return $tag->getContent();
124
                }
125
            }
126
        }
127
128
        return 'general';
129
    }
130
131
    /**
132
     * @param  $route
133
     * @param  array $bindings
134
     *
135
     * @return array
136
     */
137
    protected function getRouteRules($route, $bindings)
138
    {
139
        list($class, $method) = explode('@', $route);
140
        $reflection = new ReflectionClass($class);
141
        $reflectionMethod = $reflection->getMethod($method);
142
143
        foreach ($reflectionMethod->getParameters() as $parameter) {
144
            $parameterType = $parameter->getClass();
145
            if (! is_null($parameterType) && class_exists($parameterType->name)) {
146
                $className = $parameterType->name;
147
148
                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...
149
                    $parameterReflection = new $className;
150
                    // Add route parameter bindings
151
                    $parameterReflection->query->add($bindings);
152
                    $parameterReflection->request->add($bindings);
153
154
                    if (method_exists($parameterReflection, 'validator')) {
155
                        return $parameterReflection->validator()->getRules();
156
                    } else {
157
                        return $parameterReflection->rules();
158
                    }
159
                }
160
            }
161
        }
162
163
        return [];
164
    }
165
166
    /**
167
     * @param  array  $arr
168
     * @param  string  $first
169
     * @param  string  $last
170
     *
171
     * @return string
172
     */
173
    protected function fancyImplode($arr, $first, $last)
174
    {
175
        $arr = array_map(function ($value) {
176
            return '`'.$value.'`';
177
        }, $arr);
178
        array_push($arr, implode($last, array_splice($arr, -2)));
179
180
        return implode($first, $arr);
181
    }
182
183
    /**
184
     * @param  string  $rule
185
     * @param  string  $ruleName
186
     * @param  array  $attributeData
187
     * @param  int  $seed
188
     *
189
     * @return void
190
     */
191
    protected function parseRule($rule, $ruleName, &$attributeData, $seed)
192
    {
193
        $faker = Factory::create();
194
        $faker->seed(crc32($seed));
195
196
        $parsedRule = $this->parseStringRule($rule);
197
        $parsedRule[0] = $this->normalizeRule($parsedRule[0]);
198
        list($rule, $parameters) = $parsedRule;
199
200
        switch ($rule) {
201
            case 'required':
202
                $attributeData['required'] = true;
203
                break;
204
            case 'accepted':
205
                $attributeData['required'] = true;
206
                $attributeData['type'] = 'boolean';
207
                $attributeData['value'] = true;
208
                break;
209 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...
210
                $attributeData['type'] = 'date';
211
                $attributeData['description'][] = Description::parse($rule)->with(date(DATE_RFC850, strtotime($parameters[0])))->getDescription();
212
                $attributeData['value'] = date(DATE_RFC850, strtotime('+1 day', strtotime($parameters[0])));
213
                break;
214 View Code Duplication
            case 'alpha':
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...
215
                $attributeData['description'][] = Description::parse($rule)->getDescription();
216
                $attributeData['value'] = $faker->word;
217
                break;
218
            case 'alpha_dash':
219
                $attributeData['description'][] = Description::parse($rule)->getDescription();
220
                break;
221
            case 'alpha_num':
222
                $attributeData['description'][] = Description::parse($rule)->getDescription();
223
                break;
224 View Code Duplication
            case 'in':
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...
225
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
226
                $attributeData['value'] = $faker->randomElement($parameters);
227
                break;
228 View Code Duplication
            case 'not_in':
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...
229
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
230
                $attributeData['value'] = $faker->word;
231
                break;
232 View Code Duplication
            case 'min':
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...
233
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
234
                if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') {
235
                    $attributeData['value'] = $faker->numberBetween($parameters[0]);
236
                }
237
                break;
238 View Code Duplication
            case 'max':
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...
239
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
240
                if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') {
241
                    $attributeData['value'] = $faker->numberBetween(0, $parameters[0]);
242
                }
243
                break;
244
            case 'between':
245
                if (! isset($attributeData['type'])) {
246
                    $attributeData['type'] = 'numeric';
247
                }
248
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
249
                $attributeData['value'] = $faker->numberBetween($parameters[0], $parameters[1]);
250
                break;
251 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...
252
                $attributeData['type'] = 'date';
253
                $attributeData['description'][] = Description::parse($rule)->with(date(DATE_RFC850, strtotime($parameters[0])))->getDescription();
254
                $attributeData['value'] = date(DATE_RFC850, strtotime('-1 day', strtotime($parameters[0])));
255
                break;
256
            case 'date_format':
257
                $attributeData['type'] = 'date';
258
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
259
                $attributeData['value'] = date($parameters[0]);
260
                break;
261
            case 'different':
262
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
263
                break;
264
            case 'digits':
265
                $attributeData['type'] = 'numeric';
266
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
267
                $attributeData['value'] = $faker->randomNumber($parameters[0], true);
268
                break;
269
            case 'digits_between':
270
                $attributeData['type'] = 'numeric';
271
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
272
                break;
273 View Code Duplication
            case 'file':
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...
274
                $attributeData['type'] = 'file';
275
                $attributeData['description'][] = Description::parse($rule)->getDescription();
276
                break;
277 View Code Duplication
            case 'image':
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...
278
                $attributeData['type'] = 'image';
279
                $attributeData['description'][] = Description::parse($rule)->getDescription();
280
                break;
281
            case 'json':
282
                $attributeData['type'] = 'string';
283
                $attributeData['description'][] = Description::parse($rule)->getDescription();
284
                $attributeData['value'] = json_encode(['foo', 'bar', 'baz']);
285
                break;
286
            case 'mimetypes':
287 View Code Duplication
            case 'mimes':
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...
288
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
289
                break;
290
            case 'required_if':
291
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
292
                break;
293
            case 'required_unless':
294
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
295
                break;
296 View Code Duplication
            case 'required_with':
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...
297
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
298
                break;
299 View Code Duplication
            case 'required_with_all':
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...
300
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription();
301
                break;
302 View Code Duplication
            case 'required_without':
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...
303
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
304
                break;
305 View Code Duplication
            case 'required_without_all':
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...
306
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription();
307
                break;
308
            case 'same':
309
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
310
                break;
311
            case 'size':
312
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
313
                break;
314 View Code Duplication
            case 'timezone':
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...
315
                $attributeData['description'][] = Description::parse($rule)->getDescription();
316
                $attributeData['value'] = $faker->timezone;
317
                break;
318
            case 'exists':
319
                $fieldName = isset($parameters[1]) ? $parameters[1] : $ruleName;
320
                $attributeData['description'][] = Description::parse($rule)->with([Str::singular($parameters[0]), $fieldName])->getDescription();
321
                break;
322
            case 'active_url':
323
                $attributeData['type'] = 'url';
324
                $attributeData['value'] = $faker->url;
325
                break;
326
            case 'regex':
327
                $attributeData['type'] = 'string';
328
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
329
                break;
330
            case 'boolean':
331
                $attributeData['value'] = true;
332
                $attributeData['type'] = $rule;
333
                break;
334
            case 'array':
335
                $attributeData['value'] = $faker->word;
336
                $attributeData['type'] = $rule;
337
                break;
338
            case 'date':
339
                $attributeData['value'] = $faker->date();
340
                $attributeData['type'] = $rule;
341
                break;
342
            case 'email':
343
                $attributeData['value'] = $faker->safeEmail;
344
                $attributeData['type'] = $rule;
345
                break;
346
            case 'string':
347
                $attributeData['value'] = $faker->word;
348
                $attributeData['type'] = $rule;
349
                break;
350
            case 'integer':
351
                $attributeData['value'] = $faker->randomNumber();
352
                $attributeData['type'] = $rule;
353
                break;
354
            case 'numeric':
355
                $attributeData['value'] = $faker->randomNumber();
356
                $attributeData['type'] = $rule;
357
                break;
358
            case 'url':
359
                $attributeData['value'] = $faker->url;
360
                $attributeData['type'] = $rule;
361
                break;
362
            case 'ip':
363
                $attributeData['value'] = $faker->ipv4;
364
                $attributeData['type'] = $rule;
365
                break;
366
        }
367
368
        if ($attributeData['value'] === '') {
369
            $attributeData['value'] = $faker->word;
370
        }
371
372
        if (is_null($attributeData['type'])) {
373
            $attributeData['type'] = 'string';
374
        }
375
    }
376
377
    /**
378
     * Call the given URI and return the Response.
379
     *
380
     * @param  string  $method
381
     * @param  string  $uri
382
     * @param  array  $parameters
383
     * @param  array  $cookies
384
     * @param  array  $files
385
     * @param  array  $server
386
     * @param  string  $content
387
     *
388
     * @return \Illuminate\Http\Response
389
     */
390
    abstract public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null);
391
392
    /**
393
     * Transform headers array to array of $_SERVER vars with HTTP_* format.
394
     *
395
     * @param  array  $headers
396
     *
397
     * @return array
398
     */
399
    protected function transformHeadersToServerVars(array $headers)
400
    {
401
        $server = [];
402
        $prefix = 'HTTP_';
403
404
        foreach ($headers as $name => $value) {
405
            $name = strtr(strtoupper($name), '-', '_');
406
407
            if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') {
408
                $name = $prefix.$name;
409
            }
410
411
            $server[$name] = $value;
412
        }
413
414
        return $server;
415
    }
416
417
    /**
418
     * Parse a string based rule.
419
     *
420
     * @param  string  $rules
421
     *
422
     * @return array
423
     */
424
    protected function parseStringRule($rules)
425
    {
426
        $parameters = [];
427
428
        // The format for specifying validation rules and parameters follows an
429
        // easy {rule}:{parameters} formatting convention. For instance the
430
        // rule "Max:3" states that the value may only be three letters.
431
        if (strpos($rules, ':') !== false) {
432
            list($rules, $parameter) = explode(':', $rules, 2);
433
434
            $parameters = $this->parseParameters($rules, $parameter);
435
        }
436
437
        return [strtolower(trim($rules)), $parameters];
438
    }
439
440
    /**
441
     * Parse a parameter list.
442
     *
443
     * @param  string  $rule
444
     * @param  string  $parameter
445
     *
446
     * @return array
447
     */
448
    protected function parseParameters($rule, $parameter)
449
    {
450
        if (strtolower($rule) === 'regex') {
451
            return [$parameter];
452
        }
453
454
        return str_getcsv($parameter);
455
    }
456
457
    /**
458
     * Normalizes a rule so that we can accept short types.
459
     *
460
     * @param  string $rule
461
     *
462
     * @return string
463
     */
464
    protected function normalizeRule($rule)
465
    {
466
        switch ($rule) {
467
            case 'int':
468
                return 'integer';
469
            case 'bool':
470
                return 'boolean';
471
            default:
472
                return $rule;
473
        }
474
    }
475
}
476