Completed
Push — master ( 2b9896...da9759 )
by Marcel
08:44 queued 54s
created

AbstractGenerator::splitValuePairs()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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