Completed
Pull Request — master (#334)
by
unknown
02:48
created

AbstractGenerator::getTransformerResponse()   F

Complexity

Conditions 19
Paths 575

Size

Total Lines 85

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 85
rs 0.9402
c 0
b 0
f 0
cc 19
nc 575
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 League\Fractal\Manager;
10
use Illuminate\Routing\Route;
11
use Mpociot\Reflection\DocBlock;
12
use League\Fractal\Resource\Item;
13
use Mpociot\Reflection\DocBlock\Tag;
14
use League\Fractal\Resource\Collection;
15
use Illuminate\Support\Facades\Validator;
16
use Illuminate\Foundation\Http\FormRequest;
17
use Mpociot\ApiDoc\Parsers\RuleDescriptionParser as Description;
18
use Illuminate\Contracts\Validation\Factory as ValidationFactory;
19
20
abstract class AbstractGenerator
21
{
22
    /**
23
     * @param Route $route
24
     *
25
     * @return mixed
26
     */
27
    public function getDomain(Route $route)
28
    {
29
        return $route->domain();
30
    }
31
32
    /**
33
     * @param Route $route
34
     *
35
     * @return mixed
36
     */
37
    public function getUri(Route $route)
38
    {
39
        return $route->uri();
40
    }
41
42
    /**
43
     * @param Route $route
44
     *
45
     * @return mixed
46
     */
47
    public function getMethods(Route $route)
48
    {
49
        return array_diff($route->methods(), ['HEAD']);
50
    }
51
52
    /**
53
     * @param  \Illuminate\Routing\Route $route
54
     * @param array $bindings
55
     * @param bool $withResponse
56
     *
57
     * @return array
58
     */
59
    public function processRoute($route, $bindings = [], $headers = [], $withResponse = true)
60
    {
61
        $routeDomain = $route->domain();
62
        $routeAction = $route->getAction();
63
        $routeGroup = $this->getRouteGroup($routeAction['uses']);
64
        $routeDescription = $this->getRouteDescription($routeAction['uses']);
65
        $showresponse = null;
66
67
        // set correct route domain
68
        $headers[] = "HTTP_HOST: {$routeDomain}";
69
        $headers[] = "SERVER_NAME: {$routeDomain}";
70
71
        $response = null;
72
        $docblockResponse = $this->getDocblockResponse($routeDescription['tags']);
73
        if ($docblockResponse) {
74
            // we have a response from the docblock ( @response )
75
            $response = $docblockResponse;
76
            $showresponse = true;
77
        }
78
        if (! $response) {
79
            $transformerResponse = $this->getTransformerResponse($routeDescription['tags']);
80
            if ($transformerResponse) {
81
                // we have a transformer response from the docblock ( @transformer || @transformercollection )
82
                $response = $transformerResponse;
83
                $showresponse = true;
84
            }
85
        }
86
        if (! $response && $withResponse) {
87
            try {
88
                // we have a queryParameters array from the docblock
89
                $parameters = $this->getQueryParameters($routeDescription['tags']);
90
                $response = $this->getRouteResponse($route, $bindings, $parameters, $headers);
91
            } catch (\Exception $e) {
92
                echo "Couldn't get response for route: ".implode(',', $this->getMethods($route)).$route->uri().']: '.$e->getMessage()."\n";
93
            }
94
        }
95
96
        $content = $this->getResponseContent($response);
97
98
        return $this->getParameters([
99
            'id' => md5($this->getUri($route).':'.implode($this->getMethods($route))),
100
            'resource' => $routeGroup,
101
            'title' => $routeDescription['short'],
102
            'description' => $routeDescription['long'],
103
            'methods' => $this->getMethods($route),
104
            'uri' => $this->getUri($route),
105
            'parameters' => [],
106
            'response' => $content,
107
            'showresponse' => $showresponse,
108
        ], $routeAction, $bindings);
109
    }
110
111
    /**
112
     * Prepares / Disables route middlewares.
113
     *
114
     * @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...
115
     *
116
     * @return  void
117
     */
118
    abstract public function prepareMiddleware($enable = false);
119
120
    /**
121
     * Get the response from the docblock if available.
122
     *
123
     * @param array $tags
124
     *
125
     * @return mixed
126
     */
127
    protected function getDocblockResponse($tags)
128
    {
129
        $responseTags = array_filter($tags, function ($tag) {
130
            if (! ($tag instanceof Tag)) {
131
                return false;
132
            }
133
134
            return \strtolower($tag->getName()) == 'response';
135
        });
136
        if (empty($responseTags)) {
137
            return;
138
        }
139
        $responseTag = \array_first($responseTags);
140
141
        return \response(json_encode($responseTag->getContent()), 200, ['Content-Type' => 'application/json']);
142
    }
143
144
    /**
145
     * @param array $routeData
146
     * @param array $routeAction
147
     * @param array $bindings
148
     *
149
     * @return mixed
150
     */
151
    protected function getParameters($routeData, $routeAction, $bindings)
152
    {
153
        $validationRules = $this->getRouteValidationRules($routeData['methods'], $routeAction['uses'], $bindings);
154
        $rules = $this->simplifyRules($validationRules);
155
156
        foreach ($rules as $attribute => $ruleset) {
157
            $attributeData = [
158
                'required' => false,
159
                'type' => null,
160
                'default' => '',
161
                'value' => '',
162
                'description' => [],
163
            ];
164
            foreach ($ruleset as $rule) {
165
                $this->parseRule($rule, $attribute, $attributeData, $routeData['id'], $routeData);
166
            }
167
            $routeData['parameters'][$attribute] = $attributeData;
168
        }
169
170
        return $routeData;
171
    }
172
173
    /**
174
     * Format the validation rules as a plain array.
175
     *
176
     * @param array $rules
177
     *
178
     * @return array
179
     */
180
    protected function simplifyRules($rules)
181
    {
182
        // this will split all string rules into arrays of strings
183
        $rules = Validator::make([], $rules)->getRules();
184
185
        if (count($rules) === 0) {
186
            return $rules;
187
        }
188
189
        // Laravel will ignore the nested array rules unless the key referenced exists and is an array
190
        // So we'll to create an empty array for each array attribute
191
        $values = collect($rules)
192
            ->filter(function ($values) {
193
                return in_array('array', $values);
194
            })->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...
195
                return [''];
196
            })->all();
197
198
        // Now this will return the complete ruleset.
199
        // Nested array parameters will be present, with '*' replaced by '0'
200
        return Validator::make($values, $rules)->getRules();
201
    }
202
203
    /**
204
     * @param  $route
205
     * @param  $bindings
206
     * @param  $headers
207
     *
208
     * @return \Illuminate\Http\Response
209
     */
210
    protected function getRouteResponse($route, $bindings, $parameters = [], $headers = [])
211
    {
212
        $uri = $this->addRouteModelBindings($route, $bindings);
213
214
        $methods = $this->getMethods($route);
215
216
        // Split headers into key - value pairs
217
        $headers = collect($headers)->map(function ($value) {
218
            $split = explode(':', $value); // explode to get key + values
219
            $key = array_shift($split); // extract the key and keep the values in the array
220
            $value = implode(':', $split); // implode values into string again
221
222
            return [trim($key) => trim($value)];
223
        })->collapse()->toArray();
224
225
        //Changes url with parameters like /users/{user} to /users/1
226
        $uri = preg_replace('/{(.*?)}/', 1, $uri); // 1 is the default value for route parameters
227
228
        return $this->callRoute(array_shift($methods), $uri, $parameters, [], [], $headers);
229
    }
230
231
    /**
232
     * Get the Query Parameters if available.
233
     *
234
     * @param array $tags
235
     *
236
     * @return array
237
     */
238
    protected function getQueryParameters($tags)
239
    {
240
        $responseTags = array_filter($tags, function ($tag) {
241
            if (! ($tag instanceof Tag)) {
242
                return false;
243
            }
244
245
            return $tag->getName() == 'queryParameters';
246
        });
247
248
        if (empty($responseTags)) {
249
            return [];
250
        }
251
252
        $responseTag = \array_first($responseTags);
253
254
        $parameters = \json_decode($responseTag->getContent(), true);
255
        
256
        if(! is_array($parameters)) {
257
            $parameters = [];
258
        }
259
        
260
        return $parameters;
261
    }
262
263
    /**
264
     * @param $route
265
     * @param array $bindings
266
     *
267
     * @return mixed
268
     */
269
    protected function addRouteModelBindings($route, $bindings)
270
    {
271
        $uri = $this->getUri($route);
272
        foreach ($bindings as $model => $id) {
273
            $uri = str_replace('{'.$model.'}', $id, $uri);
274
            $uri = str_replace('{'.$model.'?}', $id, $uri);
275
        }
276
277
        return $uri;
278
    }
279
280
    /**
281
     * @param  \Illuminate\Routing\Route  $route
282
     *
283
     * @return array
284
     */
285
    protected function getRouteDescription($route)
286
    {
287
        list($class, $method) = explode('@', $route);
288
        $reflection = new ReflectionClass($class);
289
        $reflectionMethod = $reflection->getMethod($method);
290
291
        $comment = $reflectionMethod->getDocComment();
292
        $phpdoc = new DocBlock($comment);
293
294
        return [
295
            'short' => $phpdoc->getShortDescription(),
296
            'long' => $phpdoc->getLongDescription()->getContents(),
297
            'tags' => $phpdoc->getTags(),
298
        ];
299
    }
300
301
    /**
302
     * @param  string  $route
303
     *
304
     * @return string
305
     */
306
    protected function getRouteGroup($route)
307
    {
308
        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...
309
        $reflection = new ReflectionClass($class);
310
        $comment = $reflection->getDocComment();
311
        if ($comment) {
312
            $phpdoc = new DocBlock($comment);
313
            foreach ($phpdoc->getTags() as $tag) {
314
                if ($tag->getName() === 'resource') {
315
                    return $tag->getContent();
316
                }
317
            }
318
        }
319
320
        return 'general';
321
    }
322
323
    /**
324
     * @param  array $routeMethods
325
     * @param  string $routeAction
326
     * @param  array $bindings
327
     *
328
     * @return array
329
     */
330
    protected function getRouteValidationRules(array $routeMethods, $routeAction, $bindings)
331
    {
332
        list($controller, $method) = explode('@', $routeAction);
333
        $reflection = new ReflectionClass($controller);
334
        $reflectionMethod = $reflection->getMethod($method);
335
336
        foreach ($reflectionMethod->getParameters() as $parameter) {
337
            $parameterType = $parameter->getClass();
338
            if (! is_null($parameterType) && class_exists($parameterType->name)) {
339
                $className = $parameterType->name;
340
341
                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...
342
                    /** @var FormRequest $formRequest */
343
                    $formRequest = new $className;
344
                    // Add route parameter bindings
345
                    $formRequest->setContainer(app());
346
                    $formRequest->request->add($bindings);
347
                    $formRequest->query->add($bindings);
348
                    $formRequest->setMethod($routeMethods[0]);
349
350
                    if (method_exists($formRequest, 'validator')) {
351
                        $factory = app(ValidationFactory::class);
352
353
                        return call_user_func_array([$formRequest, 'validator'], [$factory])
354
                            ->getRules();
355
                    } else {
356
                        return call_user_func_array([$formRequest, 'rules'], []);
357
                    }
358
                }
359
            }
360
        }
361
362
        return [];
363
    }
364
365
    /**
366
     * @param  array  $arr
367
     * @param  string  $first
368
     * @param  string  $last
369
     *
370
     * @return string
371
     */
372
    protected function fancyImplode($arr, $first, $last)
373
    {
374
        $arr = array_map(function ($value) {
375
            return '`'.$value.'`';
376
        }, $arr);
377
        array_push($arr, implode($last, array_splice($arr, -2)));
378
379
        return implode($first, $arr);
380
    }
381
382
    protected function splitValuePairs($parameters, $first = 'is ', $last = 'or ')
383
    {
384
        $attribute = '';
385
        collect($parameters)->map(function ($item, $key) use (&$attribute, $first, $last) {
386
            $attribute .= '`'.$item.'` ';
387
            if (($key + 1) % 2 === 0) {
388
                $attribute .= $last;
389
            } else {
390
                $attribute .= $first;
391
            }
392
        });
393
        $attribute = rtrim($attribute, $last);
394
395
        return $attribute;
396
    }
397
398
    /**
399
     * @param  string  $rule
400
     * @param  string  $attribute
401
     * @param  array  $attributeData
402
     * @param  int  $seed
403
     *
404
     * @return void
405
     */
406
    protected function parseRule($rule, $attribute, &$attributeData, $seed, $routeData)
407
    {
408
        $faker = Factory::create();
409
        $faker->seed(crc32($seed));
410
411
        $parsedRule = $this->parseStringRule($rule);
412
        $parsedRule[0] = $this->normalizeRule($parsedRule[0]);
413
        list($rule, $parameters) = $parsedRule;
414
415
        switch ($rule) {
416
            case 'required':
417
                $attributeData['required'] = true;
418
                break;
419
            case 'accepted':
420
                $attributeData['required'] = true;
421
                $attributeData['type'] = 'boolean';
422
                $attributeData['value'] = true;
423
                break;
424 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...
425
                $attributeData['type'] = 'date';
426
                $format = isset($attributeData['format']) ? $attributeData['format'] : DATE_RFC850;
427
428
                if (strtotime($parameters[0]) === false) {
429
                    // the `after` date refers to another parameter in the request
430
                    $paramName = $parameters[0];
431
                    $attributeData['description'][] = Description::parse($rule)->with($paramName)->getDescription();
432
                    $attributeData['value'] = date($format, strtotime('+1 day', strtotime($routeData['parameters'][$paramName]['value'])));
433
                } else {
434
                    $attributeData['description'][] = Description::parse($rule)->with(date($format, strtotime($parameters[0])))->getDescription();
435
                    $attributeData['value'] = date($format, strtotime('+1 day', strtotime($parameters[0])));
436
                }
437
                break;
438 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...
439
                $attributeData['description'][] = Description::parse($rule)->getDescription();
440
                $attributeData['value'] = $faker->word;
441
                break;
442
            case 'alpha_dash':
443
                $attributeData['description'][] = Description::parse($rule)->getDescription();
444
                break;
445
            case 'alpha_num':
446
                $attributeData['description'][] = Description::parse($rule)->getDescription();
447
                break;
448 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...
449
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
450
                $attributeData['value'] = $faker->randomElement($parameters);
451
                break;
452 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...
453
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
454
                $attributeData['value'] = $faker->word;
455
                break;
456 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...
457
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
458
                if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') {
459
                    $attributeData['value'] = $faker->numberBetween($parameters[0]);
460
                }
461
                break;
462 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...
463
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
464
                if (Arr::get($attributeData, 'type') === 'numeric' || Arr::get($attributeData, 'type') === 'integer') {
465
                    $attributeData['value'] = $faker->numberBetween(0, $parameters[0]);
466
                }
467
                break;
468 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...
469
                if (! isset($attributeData['type'])) {
470
                    $attributeData['type'] = 'numeric';
471
                }
472
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
473
                $attributeData['value'] = $faker->numberBetween($parameters[0], $parameters[1]);
474
                break;
475 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...
476
                $attributeData['type'] = 'date';
477
                $format = isset($attributeData['format']) ? $attributeData['format'] : DATE_RFC850;
478
479
                if (strtotime($parameters[0]) === false) {
480
                    // the `before` date refers to another parameter in the request
481
                    $paramName = $parameters[0];
482
                    $attributeData['description'][] = Description::parse($rule)->with($paramName)->getDescription();
483
                    $attributeData['value'] = date($format, strtotime('-1 day', strtotime($routeData['parameters'][$paramName]['value'])));
484
                } else {
485
                    $attributeData['description'][] = Description::parse($rule)->with(date($format, strtotime($parameters[0])))->getDescription();
486
                    $attributeData['value'] = date($format, strtotime('-1 day', strtotime($parameters[0])));
487
                }
488
                break;
489 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...
490
                $attributeData['type'] = 'date';
491
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
492
                $attributeData['format'] = $parameters[0];
493
                $attributeData['value'] = date($attributeData['format']);
494
                break;
495
            case 'different':
496
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
497
                break;
498
            case 'digits':
499
                $attributeData['type'] = 'numeric';
500
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
501
                $attributeData['value'] = ($parameters[0] < 9) ? $faker->randomNumber($parameters[0], true) : substr(mt_rand(100000000, mt_getrandmax()), 0, $parameters[0]);
502
                break;
503
            case 'digits_between':
504
                $attributeData['type'] = 'numeric';
505
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
506
                break;
507 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...
508
                $attributeData['type'] = 'file';
509
                $attributeData['description'][] = Description::parse($rule)->getDescription();
510
                break;
511 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...
512
                $attributeData['type'] = 'image';
513
                $attributeData['description'][] = Description::parse($rule)->getDescription();
514
                break;
515
            case 'json':
516
                $attributeData['type'] = 'string';
517
                $attributeData['description'][] = Description::parse($rule)->getDescription();
518
                $attributeData['value'] = json_encode(['foo', 'bar', 'baz']);
519
                break;
520
            case 'mimetypes':
521 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...
522
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
523
                break;
524 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...
525
                $attributeData['description'][] = Description::parse($rule)->with($this->splitValuePairs($parameters))->getDescription();
526
                break;
527 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...
528
                $attributeData['description'][] = Description::parse($rule)->with($this->splitValuePairs($parameters))->getDescription();
529
                break;
530 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...
531
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
532
                break;
533 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...
534
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription();
535
                break;
536 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...
537
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' or '))->getDescription();
538
                break;
539 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...
540
                $attributeData['description'][] = Description::parse($rule)->with($this->fancyImplode($parameters, ', ', ' and '))->getDescription();
541
                break;
542
            case 'same':
543
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
544
                break;
545
            case 'size':
546
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
547
                break;
548 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...
549
                $attributeData['description'][] = Description::parse($rule)->getDescription();
550
                $attributeData['value'] = $faker->timezone;
551
                break;
552
            case 'exists':
553
                $fieldName = isset($parameters[1]) ? $parameters[1] : $attribute;
554
                $attributeData['description'][] = Description::parse($rule)->with([Str::singular($parameters[0]), $fieldName])->getDescription();
555
                break;
556
            case 'active_url':
557
                $attributeData['type'] = 'url';
558
                $attributeData['value'] = $faker->url;
559
                break;
560
            case 'regex':
561
                $attributeData['type'] = 'string';
562
                $attributeData['description'][] = Description::parse($rule)->with($parameters)->getDescription();
563
                break;
564
            case 'boolean':
565
                $attributeData['value'] = true;
566
                $attributeData['type'] = $rule;
567
                break;
568
            case 'array':
569
                $attributeData['value'] = $faker->word;
570
                $attributeData['type'] = $rule;
571
                break;
572
            case 'date':
573
                $attributeData['value'] = $faker->date();
574
                $attributeData['type'] = $rule;
575
                break;
576
            case 'email':
577
                $attributeData['value'] = $faker->safeEmail;
578
                $attributeData['type'] = $rule;
579
                break;
580
            case 'string':
581
                $attributeData['value'] = $faker->word;
582
                $attributeData['type'] = $rule;
583
                break;
584
            case 'integer':
585
                $attributeData['value'] = $faker->randomNumber();
586
                $attributeData['type'] = $rule;
587
                break;
588
            case 'numeric':
589
                $attributeData['value'] = $faker->randomNumber();
590
                $attributeData['type'] = $rule;
591
                break;
592
            case 'url':
593
                $attributeData['value'] = $faker->url;
594
                $attributeData['type'] = $rule;
595
                break;
596
            case 'ip':
597
                $attributeData['value'] = $faker->ipv4;
598
                $attributeData['type'] = $rule;
599
                break;
600
            default:
601
                $unknownRuleDescription = Description::parse($rule)->getDescription();
602
                if ($unknownRuleDescription) {
603
                    $attributeData['description'][] = $unknownRuleDescription;
604
                }
605
                break;
606
        }
607
608
        if ($attributeData['value'] === '') {
609
            $attributeData['value'] = $faker->word;
610
        }
611
612
        if (is_null($attributeData['type'])) {
613
            $attributeData['type'] = 'string';
614
        }
615
    }
616
617
    /**
618
     * Call the given URI and return the Response.
619
     *
620
     * @param  string  $method
621
     * @param  string  $uri
622
     * @param  array  $parameters
623
     * @param  array  $cookies
624
     * @param  array  $files
625
     * @param  array  $server
626
     * @param  string  $content
627
     *
628
     * @return \Illuminate\Http\Response
629
     */
630
    abstract public function callRoute($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null);
631
632
    /**
633
     * Transform headers array to array of $_SERVER vars with HTTP_* format.
634
     *
635
     * @param  array  $headers
636
     *
637
     * @return array
638
     */
639
    protected function transformHeadersToServerVars(array $headers)
640
    {
641
        $server = [];
642
        $prefix = 'HTTP_';
643
644
        foreach ($headers as $name => $value) {
645
            $name = strtr(strtoupper($name), '-', '_');
646
647
            if (! Str::startsWith($name, $prefix) && $name !== 'CONTENT_TYPE') {
648
                $name = $prefix.$name;
649
            }
650
651
            $server[$name] = $value;
652
        }
653
654
        return $server;
655
    }
656
657
    /**
658
     * Parse a string based rule.
659
     *
660
     * @param  string  $rules
661
     *
662
     * @return array
663
     */
664
    protected function parseStringRule($rules)
665
    {
666
        $parameters = [];
667
668
        // The format for specifying validation rules and parameters follows an
669
        // easy {rule}:{parameters} formatting convention. For instance the
670
        // rule "max:3" states that the value may only be three letters.
671
        if (strpos($rules, ':') !== false) {
672
            list($rules, $parameter) = explode(':', $rules, 2);
673
674
            $parameters = $this->parseParameters($rules, $parameter);
675
        }
676
677
        return [strtolower(trim($rules)), $parameters];
678
    }
679
680
    /**
681
     * Parse a parameter list.
682
     *
683
     * @param  string  $rule
684
     * @param  string  $parameter
685
     *
686
     * @return array
687
     */
688
    protected function parseParameters($rule, $parameter)
689
    {
690
        if (strtolower($rule) === 'regex') {
691
            return [$parameter];
692
        }
693
694
        return str_getcsv($parameter);
695
    }
696
697
    /**
698
     * Normalizes a rule so that we can accept short types.
699
     *
700
     * @param  string $rule
701
     *
702
     * @return string
703
     */
704
    protected function normalizeRule($rule)
705
    {
706
        switch ($rule) {
707
            case 'int':
708
                return 'integer';
709
            case 'bool':
710
                return 'boolean';
711
            default:
712
                return $rule;
713
        }
714
    }
715
716
    /**
717
     * @param $response
718
     *
719
     * @return mixed
720
     */
721
    private function getResponseContent($response)
722
    {
723
        if (empty($response)) {
724
            return '';
725
        }
726
        if ($response->headers->get('Content-Type') === 'application/json') {
727
            $content = json_decode($response->getContent(), JSON_PRETTY_PRINT);
728
        } else {
729
            $content = $response->getContent();
730
        }
731
732
        return $content;
733
    }
734
735
    /**
736
     * Get a response from the transformer tags.
737
     *
738
     * @param array $tags
739
     *
740
     * @return mixed
741
     */
742
    protected function getTransformerResponse($tags)
743
    {
744
        try {
745
            $transFormerTags = array_filter($tags, function ($tag) {
746
                if (! ($tag instanceof Tag)) {
747
                    return false;
748
                }
749
750
                return \in_array(\strtolower($tag->getName()), ['transformer', 'transformercollection']);
751
            });
752
            if (empty($transFormerTags)) {
753
                // we didn't have any of the tags so goodbye
754
                return false;
755
            }
756
757
            $modelTag = array_first(array_filter($tags, function ($tag) {
758
                if (! ($tag instanceof Tag)) {
759
                    return false;
760
                }
761
762
                return \in_array(\strtolower($tag->getName()), ['transformermodel']);
763
            }));
764
            $tag = \array_first($transFormerTags);
765
            $transformer = $tag->getContent();
766
            if (! \class_exists($transformer)) {
767
                // if we can't find the transformer we can't generate a response
768
                return;
769
            }
770
            $demoData = [];
771
772
            $reflection = new ReflectionClass($transformer);
773
            $method = $reflection->getMethod('transform');
774
            $parameter = \array_first($method->getParameters());
775
            $type = null;
776
            if ($modelTag) {
777
                $type = $modelTag->getContent();
778
            }
779
            if (version_compare(PHP_VERSION, '7.0.0') >= 0 && \is_null($type)) {
780
                // we can only get the type with reflection for PHP 7
781
                if ($parameter->hasType() &&
782
                    ! $parameter->getType()->isBuiltin() &&
783
                    \class_exists((string) $parameter->getType())) {
784
                    //we have a type
785
                    $type = (string) $parameter->getType();
786
                }
787
            }
788
            if ($type) {
789
                // we have a class so we try to create an instance
790
                $demoData = new $type;
791
                try {
792
                    // try a factory
793
                    $demoData = \factory($type)->make();
794
                } catch (\Exception $e) {
795
                    if ($demoData instanceof \Illuminate\Database\Eloquent\Model) {
796
                        // we can't use a factory but can try to get one from the database
797
                        try {
798
                            // check if we can find one
799
                            $newDemoData = $type::first();
800
                            if ($newDemoData) {
801
                                $demoData = $newDemoData;
802
                            }
803
                        } catch (\Exception $e) {
804
                            // do nothing
805
                        }
806
                    }
807
                }
808
            }
809
810
            $fractal = new Manager();
811
            $resource = [];
812
            if ($tag->getName() == 'transformer') {
813
                // just one
814
                $resource = new Item($demoData, new $transformer);
815
            }
816
            if ($tag->getName() == 'transformercollection') {
817
                // a collection
818
                $resource = new Collection([$demoData, $demoData], new $transformer);
819
            }
820
821
            return \response($fractal->createData($resource)->toJson());
0 ignored issues
show
Bug introduced by
It seems like $resource defined by array() on line 811 can also be of type array; however, League\Fractal\Manager::createData() does only seem to accept object<League\Fractal\Resource\ResourceInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
822
        } catch (\Exception $e) {
823
            // it isn't possible to parse the transformer
824
            return;
825
        }
826
    }
827
}
828