Completed
Pull Request — master (#334)
by
unknown
01:50
created

AbstractGenerator::fancyImplode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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