Completed
Push — 2.x ( 143417...6a730f )
by Tobias
03:25
created

RestActionReader::getMethodArguments()   B

Complexity

Conditions 11
Paths 14

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 11.0113

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 21
cts 22
cp 0.9545
rs 7.3166
c 0
b 0
f 0
cc 11
nc 14
nop 1
crap 11.0113

How to fix   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
/*
4
 * This file is part of the FOSRestBundle package.
5
 *
6
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace FOS\RestBundle\Routing\Loader\Reader;
13
14 1
@trigger_error(sprintf('The %s\RestActionReader class is deprecated since FOSRestBundle 2.8.', __NAMESPACE__), E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
15
16
use Doctrine\Common\Annotations\Reader;
17
use FOS\RestBundle\Controller\Annotations\Route as RouteAnnotation;
18
use FOS\RestBundle\Inflector\InflectorInterface;
19
use FOS\RestBundle\Request\ParamFetcherInterface;
20
use FOS\RestBundle\Request\ParamReaderInterface;
21
use FOS\RestBundle\Routing\RestRouteCollection;
22
use Psr\Http\Message\MessageInterface;
23
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
24
use Symfony\Component\HttpFoundation\Request;
25
use Symfony\Component\HttpFoundation\Session\SessionInterface;
26
use Symfony\Component\Routing\Route;
27
use Symfony\Component\Security\Core\User\UserInterface;
28
use Symfony\Component\Validator\ConstraintViolationListInterface;
29
30
/**
31
 * REST controller actions reader.
32
 *
33
 * @author Konstantin Kudryashov <[email protected]>
34
 *
35
 * @deprecated since 2.8
36
 */
37
class RestActionReader
38
{
39
    const COLLECTION_ROUTE_PREFIX = 'c';
40
41
    private $annotationReader;
42
    private $paramReader;
43
    private $inflector;
44
    private $formats;
45
    private $includeFormat;
46
    private $routePrefix;
47
    private $namePrefix;
48
    private $versions;
49
    private $pluralize;
50
    private $parents = [];
51
    private $availableHTTPMethods = [
52
        'get',
53
        'post',
54
        'put',
55
        'patch',
56
        'delete',
57
        'link',
58
        'unlink',
59
        'head',
60
        'options',
61
        'mkcol',
62
        'propfind',
63
        'proppatch',
64
        'move',
65
        'copy',
66
        'lock',
67
        'unlock',
68
    ];
69
    private $availableConventionalActions = ['new', 'edit', 'remove'];
70
    private $hasMethodPrefix;
71
72
    /**
73
     * ignore several type hinted arguments.
74
     */
75
    private $ignoredClasses = [
76
        ConstraintViolationListInterface::class,
77
        MessageInterface::class,
78
        ParamConverter::class,
79
        ParamFetcherInterface::class,
80
        Request::class,
81
        SessionInterface::class,
82
        UserInterface::class,
83
    ];
84
85 50
    public function __construct(Reader $annotationReader, ParamReaderInterface $paramReader, InflectorInterface $inflector, bool $includeFormat, array $formats = [], bool $hasMethodPrefix = true)
86
    {
87 50
        $this->annotationReader = $annotationReader;
88 50
        $this->paramReader = $paramReader;
89 50
        $this->inflector = $inflector;
90 50
        $this->includeFormat = $includeFormat;
91 50
        $this->formats = $formats;
92 50
        $this->hasMethodPrefix = $hasMethodPrefix;
93 50
    }
94
95
    /**
96
     * @param string|null $prefix
97
     */
98 29
    public function setRoutePrefix($prefix = null)
99
    {
100 29
        $this->routePrefix = $prefix;
101 29
    }
102
103
    /**
104
     * @return string
105
     */
106 29
    public function getRoutePrefix()
107
    {
108 29
        return $this->routePrefix;
109
    }
110
111
    /**
112
     * @param string|null $prefix
113
     */
114 29
    public function setNamePrefix($prefix = null)
115
    {
116 29
        $this->namePrefix = $prefix;
117 29
    }
118
119
    /**
120
     * @return string
121
     */
122
    public function getNamePrefix()
123
    {
124
        return $this->namePrefix;
125
    }
126
127
    /**
128
     * @param string[]|string|null $versions
129
     */
130 29
    public function setVersions($versions = null)
131
    {
132 29
        $this->versions = (array) $versions;
133 29
    }
134
135
    /**
136
     * @return string[]|null
137
     */
138
    public function getVersions()
139
    {
140
        return $this->versions;
141
    }
142
143
    /**
144
     * @param bool|null $pluralize
145
     */
146 29
    public function setPluralize($pluralize)
147
    {
148 29
        $this->pluralize = $pluralize;
149 29
    }
150
151
    /**
152
     * @return bool|null
153
     */
154
    public function getPluralize()
155
    {
156
        return $this->pluralize;
157
    }
158
159
    /**
160
     * @param string[] $parents Array of parent resources names
161
     */
162 29
    public function setParents(array $parents)
163
    {
164 29
        $this->parents = $parents;
165 29
    }
166
167
    /**
168
     * @return string[]
169
     */
170
    public function getParents()
171
    {
172
        return $this->parents;
173
    }
174
175
    /**
176
     * Set ignored classes.
177
     *
178
     * These classes will be ignored for route construction when
179
     * type hinted as method argument.
180
     *
181
     * @param string[] $ignoredClasses
182
     */
183
    public function setIgnoredClasses(array $ignoredClasses): void
184
    {
185
        $this->ignoredClasses = $ignoredClasses;
186
    }
187
188
    /**
189
     * Get ignored classes.
190
     *
191
     * @return string[]
192
     */
193 13
    public function getIgnoredClasses(): array
194
    {
195 13
        return $this->ignoredClasses;
196
    }
197
198
    /**
199
     * @param string[] $resource
200
     *
201
     * @throws \InvalidArgumentException
202
     *
203
     * @return Route
204
     */
205 29
    public function read(RestRouteCollection $collection, \ReflectionMethod $method, $resource)
206
    {
207
        // check that every route parent has non-empty singular name
208 29
        foreach ($this->parents as $parent) {
209 5
            if (empty($parent) || '/' === substr($parent, -1)) {
210
                throw new \InvalidArgumentException('Every parent controller must have `get{SINGULAR}Action(\$id)` method where {SINGULAR} is a singular form of associated object');
211
            }
212
        }
213
214
        // if method is not readable - skip
215 29
        if (!$this->isMethodReadable($method)) {
216 15
            return;
217
        }
218
219
        // if we can't get http-method and resources from method name - skip
220 29
        $httpMethodAndResources = $this->getHttpMethodAndResourcesFromMethod($method, $resource);
221 29
        if (!$httpMethodAndResources) {
222 26
            return;
223
        }
224
225 29
        [$httpMethod, $resources, $isCollection, $isInflectable] = $httpMethodAndResources;
0 ignored issues
show
Bug introduced by
The variable $httpMethod seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $resources seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $isCollection does not exist. Did you mean $collection?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $isInflectable does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
226 29
        $arguments = $this->getMethodArguments($method);
227
228
        // if we have only 1 resource & 1 argument passed, then it's object call, so
229
        // we can set collection singular name
230 29
        if (1 === count($resources) && 1 === count($arguments) - count($this->parents)) {
0 ignored issues
show
Bug introduced by
The variable $resources seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
231 22
            $collection->setSingularName($resources[0]);
0 ignored issues
show
Bug introduced by
The variable $resources seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
232
        }
233
234
        // if we have parents passed - merge them with own resource names
235 29
        if (count($this->parents)) {
236 5
            $resources = array_merge($this->parents, $resources);
0 ignored issues
show
Bug introduced by
The variable $resources seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
237
        }
238
239 29
        if (empty($resources)) {
240
            $resources[] = null;
0 ignored issues
show
Bug introduced by
The variable $resources does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
241
        }
242
243 29
        $routeName = $httpMethod.$this->generateRouteName($resources);
0 ignored issues
show
Bug introduced by
The variable $httpMethod seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
244 29
        $urlParts = $this->generateUrlParts($resources, $arguments, $httpMethod);
0 ignored issues
show
Bug introduced by
The variable $httpMethod seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
245
246
        // if passed method is not valid HTTP method then it's either
247
        // a hypertext driver, a custom object (PUT) or collection (GET)
248
        // method
249 29
        if (!in_array($httpMethod, $this->availableHTTPMethods)) {
0 ignored issues
show
Bug introduced by
The variable $httpMethod seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
250 18
            $urlParts[] = $httpMethod;
0 ignored issues
show
Bug introduced by
The variable $httpMethod seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
251 18
            $httpMethod = $this->getCustomHttpMethod($httpMethod, $resources, $arguments);
0 ignored issues
show
Bug introduced by
The variable $httpMethod seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
252
        }
253
254
        // generated parameters
255 29
        $routeName = strtolower($routeName);
256 29
        $path = implode('/', $urlParts);
257 29
        $defaults = ['_controller' => $method->getName()];
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
258 29
        $requirements = [];
259 29
        $options = [];
260 29
        $host = '';
261 29
        $versionCondition = $this->getVersionCondition();
262 29
        $versionRequirement = $this->getVersionRequirement();
263
264 29
        $annotations = $this->readRouteAnnotation($method);
265 29
        if (!empty($annotations)) {
266 9
            foreach ($annotations as $annotation) {
267 9
                $path = implode('/', $urlParts);
268 9
                $defaults = ['_controller' => $method->getName()];
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
269 9
                $requirements = [];
270 9
                $options = [];
271 9
                $methods = explode('|', $httpMethod);
0 ignored issues
show
Bug introduced by
The variable $httpMethod does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
272
273 9
                $annoRequirements = $annotation->getRequirements();
274 9
                $annoMethods = $annotation->getMethods();
275
276 9
                if (!empty($annoMethods)) {
277 9
                    $methods = $annoMethods;
278
                }
279
280 9
                $path = null !== $annotation->getPath() ? $this->routePrefix.$annotation->getPath() : $path;
281 9
                $requirements = array_merge($requirements, $annoRequirements);
282 9
                $options = array_merge($options, $annotation->getOptions());
283 9
                $defaults = array_merge($defaults, $annotation->getDefaults());
284 9
                $host = $annotation->getHost();
285 9
                $schemes = $annotation->getSchemes();
286
287 9
                if ($this->hasVersionPlaceholder($path)) {
288 1
                    $combinedCondition = $annotation->getCondition();
289 1
                    $requirements = array_merge($versionRequirement, $requirements);
290
                } else {
291 9
                    $combinedCondition = $this->combineConditions($versionCondition, $annotation->getCondition());
292
                }
293
294 9
                $this->includeFormatIfNeeded($path, $requirements);
295
296
                // add route to collection
297 9
                $route = new Route(
298 9
                    $path,
299
                    $defaults,
300
                    $requirements,
301
                    $options,
302
                    $host,
303
                    $schemes,
304
                    $methods,
305
                    $combinedCondition
306
                );
307 9
                $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable, $annotation);
0 ignored issues
show
Bug introduced by
The variable $isCollection does not exist. Did you mean $collection?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
308
            }
309
        } else {
310 29
            if ($this->hasVersionPlaceholder($path)) {
311
                $versionCondition = null;
312
                $requirements = $versionRequirement;
313
            }
314
315 29
            $this->includeFormatIfNeeded($path, $requirements);
316
317 29
            $methods = explode('|', strtoupper($httpMethod));
318
319
            // add route to collection
320 29
            $route = new Route(
321 29
                $path,
322
                $defaults,
323
                $requirements,
324
                $options,
325
                $host,
326 29
                [],
327
                $methods,
328
                $versionCondition
329
            );
330 29
            $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable);
0 ignored issues
show
Bug introduced by
The variable $isCollection does not exist. Did you mean $collection?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
331
        }
332 29
    }
333
334 29
    private function getVersionCondition(): ?string
335
    {
336 29
        if (empty($this->versions)) {
337 28
            return null;
338
        }
339
340 1
        return sprintf("request.attributes.get('version') in ['%s']", implode("', '", $this->versions));
341
    }
342
343 9
    private function combineConditions(?string $conditionOne, ?string $conditionTwo): ?string
344
    {
345 9
        if (null === $conditionOne) {
346 8
            return $conditionTwo;
347
        }
348
349 1
        if (null === $conditionTwo) {
350 1
            return $conditionOne;
351
        }
352
353 1
        return sprintf('(%s) and (%s)', $conditionOne, $conditionTwo);
354
    }
355
356 29
    private function getVersionRequirement(): array
357
    {
358 29
        if (empty($this->versions)) {
359 28
            return [];
360
        }
361
362 1
        return ['version' => implode('|', $this->versions)];
363
    }
364
365 29
    private function hasVersionPlaceholder(string $path): bool
366
    {
367 29
        return false !== strpos($path, '{version}');
368
    }
369
370 29
    private function includeFormatIfNeeded(string &$path, array &$requirements)
371
    {
372 29
        if (true === $this->includeFormat) {
373 29
            $path .= '.{_format}';
374
375 29
            if (!isset($requirements['_format']) && !empty($this->formats)) {
376 2
                $requirements['_format'] = implode('|', array_keys($this->formats));
377
            }
378
        }
379 29
    }
380
381 29
    private function isMethodReadable(\ReflectionMethod $method): bool
382
    {
383
        // if method starts with _ - skip
384 29
        if ('_' === substr($method->getName(), 0, 1)) {
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
385 10
            return false;
386
        }
387
388 29
        $hasNoRouteMethod = (bool) $this->readMethodAnnotation($method, 'NoRoute');
389 29
        $hasNoRouteClass = (bool) $this->readClassAnnotation($method->getDeclaringClass(), 'NoRoute');
390
391 29
        $hasNoRoute = $hasNoRouteMethod || $hasNoRouteClass;
392
        // since NoRoute extends Route we need to exclude all the method NoRoute annotations
393 29
        $hasRoute = (bool) $this->readMethodAnnotation($method, 'Route') && !$hasNoRouteMethod;
394
395
        // if method has NoRoute annotation and does not have Route annotation - skip
396 29
        if ($hasNoRoute && !$hasRoute) {
397 5
            return false;
398
        }
399
400 29
        return true;
401
    }
402
403
    /**
404
     * @param string[] $resource
405
     *
406
     * @return bool|array
407
     */
408 29
    private function getHttpMethodAndResourcesFromMethod(\ReflectionMethod $method, array $resource)
409
    {
410
        // if method doesn't match regex - skip
411 29
        if (!preg_match('/([a-z][_a-z0-9]+)(.*)Action/', $method->getName(), $matches)) {
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
412 26
            return false;
413
        }
414
415 29
        $httpMethod = strtolower($matches[1]);
416 29
        $resources = preg_split(
417 29
            '/([A-Z][^A-Z]*)/',
418 29
            $matches[2],
419 29
            -1,
420 29
            PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
421
        );
422 29
        $isCollection = false;
423 29
        $isInflectable = true;
424
425 29
        if (0 === strpos($httpMethod, self::COLLECTION_ROUTE_PREFIX)
426 29
            && in_array(substr($httpMethod, 1), $this->availableHTTPMethods)
427
        ) {
428 6
            $isCollection = true;
429 6
            $httpMethod = substr($httpMethod, 1);
430 27
        } elseif ('options' === $httpMethod) {
431 15
            $isCollection = true;
432
        }
433
434 29
        if ($isCollection && !empty($resource)) {
435 6
            $resourcePluralized = $this->generateResourceName(end($resource));
436 6
            $isInflectable = ($resourcePluralized != $resource[count($resource) - 1]);
437 6
            $resource[count($resource) - 1] = $resourcePluralized;
438
        }
439
440 29
        $resources = array_merge($resource, $resources);
441
442 29
        return [$httpMethod, $resources, $isCollection, $isInflectable];
443
    }
444
445
    /**
446
     * @return \ReflectionParameter[]
447
     */
448 29
    private function getMethodArguments(\ReflectionMethod $method): array
449
    {
450
        // ignore all query params
451 29
        $params = $this->paramReader->getParamsFromMethod($method);
452
453
        // check if a parameter is coming from the request body
454 29
        $ignoreParameters = [];
455 29
        if (class_exists(ParamConverter::class)) {
456
            $ignoreParameters = array_map(function ($annotation) {
457
                return
458 10
                    $annotation instanceof ParamConverter &&
459 10
                    'fos_rest.request_body' === $annotation->getConverter()
460 10
                        ? $annotation->getName() : null;
461 29
            }, $this->annotationReader->getMethodAnnotations($method));
462
        }
463
464 29
        $arguments = [];
465 29
        foreach ($method->getParameters() as $argument) {
466 26
            if (isset($params[$argument->getName()])) {
0 ignored issues
show
Bug introduced by
Consider using $argument->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
467
                continue;
468
            }
469
470 26
            $argumentClass = $argument->getClass();
471 26
            if ($argumentClass) {
472 13
                $className = $argumentClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $argumentClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
473 13
                foreach ($this->getIgnoredClasses() as $class) {
474 13
                    if ($className === $class || is_subclass_of($className, $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 $class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
475 11
                        continue 2;
476
                    }
477
                }
478
            }
479
480 26
            if (in_array($argument->getName(), $ignoreParameters, true)) {
0 ignored issues
show
Bug introduced by
Consider using $argument->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
481 1
                continue;
482
            }
483
484 25
            $arguments[] = $argument;
485
        }
486
487 29
        return $arguments;
488
    }
489
490
    /**
491
     * @param string|bool $resource
492
     */
493 29
    private function generateResourceName($resource): string
494
    {
495 29
        if (false === $this->pluralize) {
496 1
            return $resource;
497
        }
498
499 28
        return $this->inflector->pluralize($resource);
0 ignored issues
show
Bug introduced by
It seems like $resource defined by parameter $resource on line 493 can also be of type boolean; however, FOS\RestBundle\Inflector...rInterface::pluralize() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
500
    }
501
502
    /**
503
     * @param string[] $resources
504
     */
505 29
    private function generateRouteName(array $resources): string
506
    {
507 29
        $routeName = '';
508 29
        foreach ($resources as $resource) {
509 29
            if (null !== $resource) {
510 29
                $routeName .= '_'.basename($resource);
511
            }
512
        }
513
514 29
        return $routeName;
515
    }
516
517
    /**
518
     * @param string[]               $resources
519
     * @param \ReflectionParameter[] $arguments
520
     */
521 29
    private function generateUrlParts(array $resources, array $arguments, string $httpMethod): array
522
    {
523 29
        $urlParts = [];
524 29
        foreach ($resources as $i => $resource) {
525
            // if we already added all parent routes paths to URL & we have
526
            // prefix - add it
527 29
            if (!empty($this->routePrefix) && $i === count($this->parents)) {
528 3
                $urlParts[] = $this->routePrefix;
529
            }
530
531
            // if we have argument for current resource, then it's object.
532
            // otherwise - it's collection
533 29
            if (isset($arguments[$i])) {
534 25
                if (null !== $resource) {
535 25
                    $urlParts[] =
536 25
                        strtolower($this->generateResourceName($resource))
537 25
                        .'/{'.$arguments[$i]->getName().'}';
0 ignored issues
show
Bug introduced by
Consider using $arguments[$i]->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
538
                } else {
539 25
                    $urlParts[] = '{'.$arguments[$i]->getName().'}';
0 ignored issues
show
Bug introduced by
Consider using $arguments[$i]->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
540
                }
541 28
            } elseif (null !== $resource) {
542 28
                if ((0 === count($arguments) && !in_array($httpMethod, $this->availableHTTPMethods))
543 28
                    || 'new' === $httpMethod
544 28
                    || 'post' === $httpMethod
545
                ) {
546 21
                    $urlParts[] = $this->generateResourceName(strtolower($resource));
547
                } else {
548 27
                    $urlParts[] = strtolower($resource);
549
                }
550
            }
551
        }
552
553 29
        return $urlParts;
554
    }
555
556
    /**
557
     * @param string[]               $resources
558
     * @param \ReflectionParameter[] $arguments
559
     */
560 18
    private function getCustomHttpMethod(string $httpMethod, array $resources, array $arguments): string
561
    {
562 18
        if (in_array($httpMethod, $this->availableConventionalActions)) {
563
            // allow hypertext as the engine of application state
564
            // through conventional GET actions
565 12
            return 'get';
566
        }
567
568 16
        if (count($arguments) < count($resources)) {
569
            // resource collection
570 16
            return 'get';
571
        }
572
573
        // custom object
574 15
        return 'patch';
575
    }
576
577
    /**
578
     * @return RouteAnnotation[]
579
     */
580 29
    private function readRouteAnnotation(\ReflectionMethod $reflectionMethod): array
581
    {
582 29
        $annotations = [];
583
584 29
        if ($newAnnotations = $this->readMethodAnnotations($reflectionMethod, 'Route')) {
585 9
            $annotations = array_merge($annotations, $newAnnotations);
586
        }
587
588 29
        return $annotations;
589
    }
590
591 29
    private function readClassAnnotation(\ReflectionClass $reflectionClass, string $annotationName): ?RouteAnnotation
592
    {
593 29
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
594
595 29
        if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, $annotationClass)) {
596
            return $annotation;
597
        }
598
599 29
        return null;
600
    }
601
602 29
    private function readMethodAnnotation(\ReflectionMethod $reflectionMethod, string $annotationName): ?RouteAnnotation
603
    {
604 29
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
605
606 29
        if ($annotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $annotationClass)) {
607 9
            return $annotation;
608
        }
609
610 29
        return null;
611
    }
612
613
    /**
614
     * @return RouteAnnotation[]
615
     */
616 29
    private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, string $annotationName): array
617
    {
618 29
        $annotations = [];
619 29
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
620
621 29
        if ($annotations_new = $this->annotationReader->getMethodAnnotations($reflectionMethod)) {
622 10
            foreach ($annotations_new as $annotation) {
623 10
                if ($annotation instanceof $annotationClass) {
624 9
                    $annotations[] = $annotation;
625
                }
626
            }
627
        }
628
629 29
        return $annotations;
630
    }
631
632 29
    private function addRoute(RestRouteCollection $collection, string $routeName, Route $route, bool $isCollection, bool $isInflectable, RouteAnnotation $annotation = null)
633
    {
634 29
        if ($annotation && null !== $annotation->getName()) {
635 5
            $options = $annotation->getOptions();
636
637 5
            if (false === $this->hasMethodPrefix || (isset($options['method_prefix']) && false === $options['method_prefix'])) {
638 4
                $routeName = $annotation->getName();
639
            } else {
640 4
                $routeName .= $annotation->getName();
641
            }
642
        }
643
644 29
        $fullRouteName = $this->namePrefix.$routeName;
645
646 29
        if ($isCollection && !$isInflectable) {
647 4
            $collection->add($this->namePrefix.self::COLLECTION_ROUTE_PREFIX.$routeName, $route);
648 4
            if (!$collection->get($fullRouteName)) {
649 4
                $collection->add($fullRouteName, clone $route);
650
            }
651
        } else {
652 27
            $collection->add($fullRouteName, $route);
653
        }
654 29
    }
655
}
656