Completed
Push — master ( ecec44...358ce1 )
by
unknown
34:43 queued 14:58
created

AbstractGenerator::normalizeRule()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
namespace Mpociot\ApiDoc\Generators;
4
5
use Faker\Factory;
6
use ReflectionClass;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Str;
9
use Mpociot\Reflection\DocBlock;
10
use Mpociot\Reflection\DocBlock\Tag;
11
use Illuminate\Support\Facades\Validator;
12
use Illuminate\Foundation\Http\FormRequest;
13
use Mpociot\ApiDoc\Parsers\RuleDescriptionParser as Description;
14
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
15
16
abstract class AbstractGenerator
17
{
18
    /**
19
     * @param $route
20
     *
21
     * @return mixed
22
     */
23
    abstract public function getDomain($route);
24
25
    /**
26
     * @param $route
27
     *
28
     * @return mixed
29
     */
30
    abstract public function getUri($route);
31
32
    /**
33
     * @param $route
34
     *
35
     * @return mixed
36
     */
37
    abstract public function getMethods($route);
38
39
    /**
40
     * @param  \Illuminate\Routing\Route $route
41
     * @param array $bindings
42
     * @param bool $withResponse
43
     *
44
     * @return array
45
     */
46
    public function processRoute($route, $bindings = [], $headers = [], $withResponse = true)
47
    {
48
        $routeDomain = $route->domain();
49
        $routeAction = $route->getAction();
50
        $routeGroup = $this->getRouteGroup($routeAction['uses']);
51
        $routeDescription = $this->getRouteDescription($routeAction['uses']);
52
        $showresponse = null;
53
54
        // set correct route domain
55
        $headers[] = "HTTP_HOST: {$routeDomain}";
56
        $headers[] = "SERVER_NAME: {$routeDomain}";
57
58
        $response = null;
59
        $docblockResponse = $this->getDocblockResponse($routeDescription['tags']);
60
        if ($docblockResponse) {
61
            // we have a response from the docblock ( @response )
62
            $response = $docblockResponse;
63
            $showresponse = true;
64
        }
65
        if (! $response) {
66
            $transformerResponse = $this->getTransformerResponse($routeDescription['tags']);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Mpociot\ApiDoc\Generators\AbstractGenerator as the method getTransformerResponse() does only exist in the following sub-classes of Mpociot\ApiDoc\Generators\AbstractGenerator: Mpociot\ApiDoc\Generators\LaravelGenerator. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
67
            if ($transformerResponse) {
68
                // we have a transformer response from the docblock ( @transformer || @transformercollection )
69
                $response = $transformerResponse;
70
                $showresponse = true;
71
            }
72
        }
73
        if (! $response && $withResponse) {
74
            try {
75
                $response = $this->getRouteResponse($route, $bindings, $headers);
76
            } catch (\Exception $e) {
77
                dump("Couldn't get response for route: ".implode(',', $this->getMethods($route)).'] '.$route->uri().'', $e);
78
            }
79
        }
80
81
        $content = $this->getResponseContent($response);
82
        return $this->getParameters([
83
            'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))),
84
            'resource' => $routeGroup,
85
            'title' => $routeDescription['short'],
86
            'description' => $routeDescription['long'],
87
            'methods' => $this->getMethods($route),
88
            'uri' => $this->getUri($route),
89
            'parameters' => [],
90
            'response' => $content,
91
            'showresponse' => $showresponse,
92
        ], $routeAction, $bindings);
93
    }
94
95
    /**
96
     * Prepares / Disables route middlewares.
97
     *
98
     * @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...
99
     *
100
     * @return  void
101
     */
102
    abstract public function prepareMiddleware($enable = false);
103
104
    /**
105
     * Get the response from the docblock if available.
106
     *
107
     * @param array $tags
108
     *
109
     * @return mixed
110
     */
111
    protected function getDocblockResponse($tags)
112
    {
113
        $responseTags = array_filter($tags, function ($tag) {
114
            if (! ($tag instanceof Tag)) {
115
                return false;
116
            }
117
118
            return \strtolower($tag->getName()) == 'response';
119
        });
120
        if (empty($responseTags)) {
121
            return;
122
        }
123
        $responseTag = \array_first($responseTags);
124
125
        return \response(json_encode($responseTag->getContent()), 200, ['Content-Type' => 'application/json']);
126
    }
127
128
    /**
129
     * @param array $routeData
130
     * @param array $routeAction
131
     * @param array $bindings
132
     *
133
     * @return mixed
134
     */
135
    protected function getParameters($routeData, $routeAction, $bindings)
136
    {
137
        $rules = $this->simplifyRules($this->getRouteRules($routeData['methods'], $routeAction['uses'], $bindings));
138
139
        foreach ($rules as $attribute => $ruleset) {
140
            $attributeData = [
141
                'required' => false,
142
                'type' => null,
143
                'default' => '',
144
                'value' => '',
145
                'description' => [],
146
            ];
147
            foreach ($ruleset as $rule) {
148
                $this->parseRule($rule, $attribute, $attributeData, $routeData['id'], $routeData);
149
            }
150
            $routeData['parameters'][$attribute] = $attributeData;
151
        }
152
153
        return $routeData;
154
    }
155
156
    /**
157
     * Format the validation rules as plain array.
158
     *
159
     * @param array $rules
160
     *
161
     * @return array
162
     */
163
    protected function simplifyRules($rules)
164
    {
165
        $simplifiedRules = Validator::make([], $rules)->getRules();
166
167
        if (count($simplifiedRules) === 0) {
168
            return $simplifiedRules;
169
        }
170
171
        $values = collect($simplifiedRules)
172
            ->filter(function ($values) {
173
                return in_array('array', $values);
174
            })->map(function ($val, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $val 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...
Unused Code introduced by
The parameter $key 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...
175
                return [''];
176
            })->all();
177
178
        return Validator::make($values, $rules)->getRules();
179
    }
180
181
    /**
182
     * @param  $route
183
     * @param  $bindings
184
     * @param  $headers
185
     *
186
     * @return \Illuminate\Http\Response
187
     */
188
    protected function getRouteResponse($route, $bindings, $headers = [])
189
    {
190
        $uri = $this->addRouteModelBindings($route, $bindings);
191
192
        $methods = $this->getMethods($route);
193
194
        // Split headers into key - value pairs
195
        $headers = collect($headers)->map(function ($value) {
196
            $split = explode(':', $value); // explode to get key + values
197
            $key = array_shift($split); // extract the key and keep the values in the array
198
            $value = implode(':', $split); // implode values into string again
199
200
            return [trim($key) => trim($value)];
201
        })->collapse()->toArray();
202
203
        //Changes url with parameters like /users/{user} to /users/1
204
        $uri = preg_replace('/{(.*?)}/', 1, $uri); // 1 is the default value for route parameters
205
206
        return $this->callRoute(array_shift($methods), $uri, [], [], [], $headers);
0 ignored issues
show
Bug introduced by
It seems like $uri defined by preg_replace('/{(.*?)}/', 1, $uri) on line 204 can also be of type array<integer,string>; however, Mpociot\ApiDoc\Generator...tGenerator::callRoute() does only seem to accept string, 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...
207
    }
208
209
    /**
210
     * @param $route
211
     * @param array $bindings
212
     *
213
     * @return mixed
214
     */
215
    protected function addRouteModelBindings($route, $bindings)
216
    {
217
        $uri = $this->getUri($route);
218
        foreach ($bindings as $model => $id) {
219
            $uri = str_replace('{'.$model.'}', $id, $uri);
220
            $uri = str_replace('{'.$model.'?}', $id, $uri);
221
        }
222
223
        return $uri;
224
    }
225
226
    /**
227
     * @param  \Illuminate\Routing\Route  $route
228
     *
229
     * @return array
230
     */
231
    protected function getRouteDescription($route)
232
    {
233
        list($class, $method) = explode('@', $route);
234
        $reflection = new ReflectionClass($class);
235
        $reflectionMethod = $reflection->getMethod($method);
236
237
        $comment = $reflectionMethod->getDocComment();
238
        $phpdoc = new DocBlock($comment);
239
240
        return [
241
            'short' => $phpdoc->getShortDescription(),
242
            'long' => $phpdoc->getLongDescription()->getContents(),
243
            'tags' => $phpdoc->getTags(),
244
        ];
245
    }
246
247
    /**
248
     * @param  string  $route
249
     *
250
     * @return string
251
     */
252
    protected function getRouteGroup($route)
253
    {
254
        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...
255
        $reflection = new ReflectionClass($class);
256
        $comment = $reflection->getDocComment();
257
        if ($comment) {
258
            $phpdoc = new DocBlock($comment);
259
            foreach ($phpdoc->getTags() as $tag) {
260
                if ($tag->getName() === 'resource') {
261
                    return $tag->getContent();
262
                }
263
            }
264
        }
265
266
        return 'general';
267
    }
268
269
    /**
270
     * @param  array $routeMethods
271
     * @param  string $routeAction
272
     * @param  array $bindings
273
     *
274
     * @return array
275
     */
276
    protected function getRouteRules(array $routeMethods, $routeAction, $bindings)
277
    {
278
        list($class, $method) = explode('@', $routeAction);
279
        $reflection = new ReflectionClass($class);
280
        $reflectionMethod = $reflection->getMethod($method);
281
282
        foreach ($reflectionMethod->getParameters() as $parameter) {
283
            $parameterType = $parameter->getClass();
284
            if (! is_null($parameterType) && class_exists($parameterType->name)) {
285
                $className = $parameterType->name;
286
287
                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...
288
                    /** @var FormRequest $formRequest */
289
                    $formRequest = new $className;
290
                    // Add route parameter bindings
291
                    $formRequest->setContainer(app());
292
                    $formRequest->request->add($bindings);
293
                    $formRequest->query->add($bindings);
294
                    $formRequest->setMethod($routeMethods[0]);
295
296
                    if (method_exists($formRequest, 'validator')) {
297
                        $factory = app(ValidationFactory::class);
298
299
                        return call_user_func_array([$formRequest, 'validator'], [$factory])
300
                            ->getRules();
301
                    } else {
302
                        return call_user_func_array([$formRequest, 'rules'], []);
303
                    }
304
                }
305
            }
306
        }
307
308
        return [];
309
    }
310
311
    /**
312
     * @param  array  $arr
313
     * @param  string  $first
314
     * @param  string  $last
315
     *
316
     * @return string
317
     */
318
    protected function fancyImplode($arr, $first, $last)
319
    {
320
        $arr = array_map(function ($value) {
321
            return '`'.$value.'`';
322
        }, $arr);
323
        array_push($arr, implode($last, array_splice($arr, -2)));
324
325
        return implode($first, $arr);
326
    }
327
328
    protected function splitValuePairs($parameters, $first = 'is ', $last = 'or ')
329
    {
330
        $attribute = '';
331
        collect($parameters)->map(function ($item, $key) use (&$attribute, $first, $last) {
332
            $attribute .= '`'.$item.'` ';
333
            if (($key + 1) % 2 === 0) {
334
                $attribute .= $last;
335
            } else {
336
                $attribute .= $first;
337
            }
338
        });
339
        $attribute = rtrim($attribute, $last);
340
341
        return $attribute;
342
    }
343
344
    /**
345
     * @param  string  $rule
346
     * @param  string  $ruleName
347
     * @param  array  $attributeData
348
     * @param  int  $seed
349
     *
350
     * @return void
351
     */
352
    protected function parseRule($rule, $ruleName, &$attributeData, $seed, $routeData)
353
    {
354
        $faker = Factory::create();
355
        $faker->seed(crc32($seed));
356
357
        $parsedRule = $this->parseStringRule($rule);
358
        $parsedRule[0] = $this->normalizeRule($parsedRule[0]);
359
        list($rule, $parameters) = $parsedRule;
360
361
        switch ($rule) {
362
            case 'required':
363
                $attributeData['required'] = true;
364
                break;
365
            case 'accepted':
366
                $attributeData['required'] = true;
367
                $attributeData['type'] = 'boolean';
368
                $attributeData['value'] = true;
369
                break;
370 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...
371
                $attributeData['type'] = 'date';
372
                $format = isset($attributeData['format']) ? $attributeData['format'] : DATE_RFC850;
373
374
                if (strtotime($parameters[0]) === false) {
375
                    // the `after` date refers to another parameter in the request
376
                    $paramName = $parameters[0];
377
                    $attributeData['description'][] = Description::parse($rule)->with($paramName)->getDescription();
378
                    $attributeData['value'] = date($format, strtotime('+1 day', strtotime($routeData['parameters'][$paramName]['value'])));
379
                } else {
380
                    $attributeData['description'][] = Description::parse($rule)->with(date($format, strtotime($parameters[0])))->getDescription();
381
                    $attributeData['value'] = date($format, strtotime('+1 day', strtotime($parameters[0])));
382
                }
383
                break;
384 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...
385
                $attributeData['description'][] = Description::parse($rule)->getDescription();
386
                $attributeData['value'] = $faker->word;
387
                break;
388
            case 'alpha_dash':
389
                $attributeData['description'][] = Description::parse($rule)->getDescription();
390
                break;
391
            case 'alpha_num':
392
                $attributeData['description'][] = Description::parse($rule)->getDescription();
393
                break;
394 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...
395
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
396
                $attributeData['value'] = $faker->randomElement($parameters);
397
                break;
398 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...
399
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
400
                $attributeData['value'] = $faker->word;
401
                break;
402 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...
403
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
404
                if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') {
405
                    $attributeData['value'] = $faker->numberBetween($parameters[0]);
406
                }
407
                break;
408 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...
409
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
410
                if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') {
411
                    $attributeData['value'] = $faker->numberBetween(0, $parameters[0]);
412
                }
413
                break;
414 View Code Duplication
            case 'between':
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...
415
                if (! isset($attributeData['type'])) {
416
                    $attributeData['type'] = 'numeric';
417
                }
418
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
419
                $attributeData['value'] = $faker->numberBetween($parameters[0], $parameters[1]);
420
                break;
421 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...
422
                $attributeData['type'] = 'date';
423
                $format = isset($attributeData['format']) ? $attributeData['format'] : DATE_RFC850;
424
425
                if (strtotime($parameters[0]) === false) {
426
                    // the `before` date refers to another parameter in the request
427
                    $paramName = $parameters[0];
428
                    $attributeData['description'][] = Description::parse($rule)->with($paramName)->getDescription();
429
                    $attributeData['value'] = date($format, strtotime('-1 day', strtotime($routeData['parameters'][$paramName]['value'])));
430
                } else {
431
                    $attributeData['description'][] = Description::parse($rule)->with(date($format, strtotime($parameters[0])))->getDescription();
432
                    $attributeData['value'] = date($format, strtotime('-1 day', strtotime($parameters[0])));
433
                }
434
                break;
435 View Code Duplication
            case 'date_format':
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...
436
                $attributeData['type'] = 'date';
437
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
438
                $attributeData['format'] = $parameters[0];
439
                $attributeData['value'] = date($attributeData['format']);
440
                break;
441
            case 'different':
442
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
443
                break;
444
            case 'digits':
445
                $attributeData['type'] = 'numeric';
446
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
447
                $attributeData['value'] = ($parameters[0] < 9) ? $faker->randomNumber($parameters[0], true) : substr(mt_rand(100000000, mt_getrandmax()), 0, $parameters[0]);
448
                break;
449
            case 'digits_between':
450
                $attributeData['type'] = 'numeric';
451
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
452
                break;
453 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...
454
                $attributeData['type'] = 'file';
455
                $attributeData['description'][] = Description::parse($rule)->getDescription();
456
                break;
457 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...
458
                $attributeData['type'] = 'image';
459
                $attributeData['description'][] = Description::parse($rule)->getDescription();
460
                break;
461
            case 'json':
462
                $attributeData['type'] = 'string';
463
                $attributeData['description'][] = Description::parse($rule)->getDescription();
464
                $attributeData['value'] = json_encode(['foo', 'bar', 'baz']);
465
                break;
466
            case 'mimetypes':
467 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...
468
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
469
                break;
470 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...
471
                $attributeData['description'][] = Description::parse($rule)->with($this->splitValuePairs($parameters))->getDescription();
472
                break;
473 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...
474
                $attributeData['description'][] = Description::parse($rule)->with($this->splitValuePairs($parameters))->getDescription();
475
                break;
476 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...
477
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
478
                break;
479 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...
480
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription();
481
                break;
482 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...
483
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
484
                break;
485 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...
486
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription();
487
                break;
488
            case 'same':
489
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
490
                break;
491
            case 'size':
492
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
493
                break;
494 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...
495
                $attributeData['description'][] = Description::parse($rule)->getDescription();
496
                $attributeData['value'] = $faker->timezone;
497
                break;
498
            case 'exists':
499
                $fieldName = isset($parameters[1]) ? $parameters[1] : $ruleName;
500
                $attributeData['description'][] = Description::parse($rule)->with([Str::singular($parameters[0]), $fieldName])->getDescription();
501
                break;
502
            case 'active_url':
503
                $attributeData['type'] = 'url';
504
                $attributeData['value'] = $faker->url;
505
                break;
506
            case 'regex':
507
                $attributeData['type'] = 'string';
508
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
509
                break;
510
            case 'boolean':
511
                $attributeData['value'] = true;
512
                $attributeData['type'] = $rule;
513
                break;
514
            case 'array':
515
                $attributeData['value'] = $faker->word;
516
                $attributeData['type'] = $rule;
517
                break;
518
            case 'date':
519
                $attributeData['value'] = $faker->date();
520
                $attributeData['type'] = $rule;
521
                break;
522
            case 'email':
523
                $attributeData['value'] = $faker->safeEmail;
524
                $attributeData['type'] = $rule;
525
                break;
526
            case 'string':
527
                $attributeData['value'] = $faker->word;
528
                $attributeData['type'] = $rule;
529
                break;
530
            case 'integer':
531
                $attributeData['value'] = $faker->randomNumber();
532
                $attributeData['type'] = $rule;
533
                break;
534
            case 'numeric':
535
                $attributeData['value'] = $faker->randomNumber();
536
                $attributeData['type'] = $rule;
537
                break;
538
            case 'url':
539
                $attributeData['value'] = $faker->url;
540
                $attributeData['type'] = $rule;
541
                break;
542
            case 'ip':
543
                $attributeData['value'] = $faker->ipv4;
544
                $attributeData['type'] = $rule;
545
                break;
546
            default:
547
                $unknownRuleDescription = Description::parse($rule)->getDescription();
548
                if ($unknownRuleDescription) {
549
                    $attributeData['description'][] = $unknownRuleDescription;
550
                }
551
                break;
552
        }
553
554
        if ($attributeData['value'] === '') {
555
            $attributeData['value'] = $faker->word;
556
        }
557
558
        if (is_null($attributeData['type'])) {
559
            $attributeData['type'] = 'string';
560
        }
561
    }
562
563
    /**
564
     * Call the given URI and return the Response.
565
     *
566
     * @param  string  $method
567
     * @param  string  $uri
568
     * @param  array  $parameters
569
     * @param  array  $cookies
570
     * @param  array  $files
571
     * @param  array  $server
572
     * @param  string  $content
573
     *
574
     * @return \Illuminate\Http\Response
575
     */
576
    abstract public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null);
577
578
    /**
579
     * Transform headers array to array of $_SERVER vars with HTTP_* format.
580
     *
581
     * @param  array  $headers
582
     *
583
     * @return array
584
     */
585
    protected function transformHeadersToServerVars(array $headers)
586
    {
587
        $server = [];
588
        $prefix = 'HTTP_';
589
590
        foreach ($headers as $name => $value) {
591
            $name = strtr(strtoupper($name), '-', '_');
592
593
            if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') {
594
                $name = $prefix.$name;
595
            }
596
597
            $server[$name] = $value;
598
        }
599
600
        return $server;
601
    }
602
603
    /**
604
     * Parse a string based rule.
605
     *
606
     * @param  string  $rules
607
     *
608
     * @return array
609
     */
610
    protected function parseStringRule($rules)
611
    {
612
        $parameters = [];
613
614
        // The format for specifying validation rules and parameters follows an
615
        // easy {rule}:{parameters} formatting convention. For instance the
616
        // rule "Max:3" states that the value may only be three letters.
617
        if (strpos($rules, ':') !== false) {
618
            list($rules, $parameter) = explode(':', $rules, 2);
619
620
            $parameters = $this->parseParameters($rules, $parameter);
621
        }
622
623
        return [strtolower(trim($rules)), $parameters];
624
    }
625
626
    /**
627
     * Parse a parameter list.
628
     *
629
     * @param  string  $rule
630
     * @param  string  $parameter
631
     *
632
     * @return array
633
     */
634
    protected function parseParameters($rule, $parameter)
635
    {
636
        if (strtolower($rule) === 'regex') {
637
            return [$parameter];
638
        }
639
640
        return str_getcsv($parameter);
641
    }
642
643
    /**
644
     * Normalizes a rule so that we can accept short types.
645
     *
646
     * @param  string $rule
647
     *
648
     * @return string
649
     */
650
    protected function normalizeRule($rule)
651
    {
652
        switch ($rule) {
653
            case 'int':
654
                return 'integer';
655
            case 'bool':
656
                return 'boolean';
657
            default:
658
                return $rule;
659
        }
660
    }
661
662
    /**
663
     * @param $response
664
     * @return mixed
665
     */
666
    private function getResponseContent($response)
667
    {
668
        if (empty($response)) {
669
            return '';
670
        }
671
        if ($response->headers->get('Content-Type') === 'application/json') {
672
            $content = json_decode($response->getContent(), JSON_PRETTY_PRINT);
673
        } else {
674
            $content = $response->getContent();
675
        }
676
        return $content;
677
    }
678
}
679