Completed
Pull Request — 2.x (#2251)
by Christian
03:46 queued 12s
created

RestActionReader::hasVersionPlaceholder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 23
    public function setRoutePrefix($prefix = null)
99
    {
100 23
        $this->routePrefix = $prefix;
101 23
    }
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 28
    public function setNamePrefix($prefix = null)
115
    {
116 28
        $this->namePrefix = $prefix;
117 28
    }
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 16
    public function setVersions($versions = null)
131
    {
132 16
        $this->versions = (array) $versions;
133 16
    }
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 17
    public function setPluralize($pluralize)
147
    {
148 17
        $this->pluralize = $pluralize;
149 17
    }
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 22
    public function setParents(array $parents)
163
    {
164 22
        $this->parents = $parents;
165 22
    }
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
    public function getIgnoredClasses(): array
194
    {
195
        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
            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 12
            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 16
            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 26
        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 18
            $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 26
        if (count($this->parents)) {
236
            $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 26
        if (empty($resources)) {
240 1
            $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 26
        $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 26
        $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 26
        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 17
            $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 17
            $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 26
        $routeName = strtolower($routeName);
256 26
        $path = implode('/', $urlParts);
257 26
        $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 26
        $requirements = [];
259 26
        $options = [];
260 26
        $host = '';
261 26
        $versionCondition = $this->getVersionCondition();
262 26
        $versionRequirement = $this->getVersionRequirement();
263
264 26
        $annotations = $this->readRouteAnnotation($method);
265 26
        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 26
            if ($this->hasVersionPlaceholder($path)) {
311
                $versionCondition = null;
312
                $requirements = $versionRequirement;
313
            }
314
315 26
            $this->includeFormatIfNeeded($path, $requirements);
316
317 26
            $methods = explode('|', strtoupper($httpMethod));
318
319
            // add route to collection
320 26
            $route = new Route(
321 26
                $path,
322
                $defaults,
323
                $requirements,
324
                $options,
325
                $host,
326 26
                [],
327
                $methods,
328
                $versionCondition
329
            );
330 26
            $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 26
    }
333
334 26
    private function getVersionCondition(): ?string
335
    {
336 26
        if (empty($this->versions)) {
337 25
            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 26
    private function getVersionRequirement(): array
357
    {
358 26
        if (empty($this->versions)) {
359 25
            return [];
360
        }
361
362 1
        return ['version' => implode('|', $this->versions)];
363
    }
364
365 26
    private function hasVersionPlaceholder(string $path): bool
366
    {
367 26
        return false !== strpos($path, '{version}');
368
    }
369
370 26
    private function includeFormatIfNeeded(string &$path, array &$requirements)
371
    {
372 26
        if (true === $this->includeFormat) {
373 26
            $path .= '.{_format}';
374
375 26
            if (!isset($requirements['_format']) && !empty($this->formats)) {
376 2
                $requirements['_format'] = implode('|', array_keys($this->formats));
377
            }
378
        }
379 26
    }
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 8
            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 4
            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 16
            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 26
        } elseif ('options' === $httpMethod) {
431 14
            $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->getType();
471 26
            if ($argumentClass && !$argument->isBuiltIn()) {
472
                $className = method_exists($argument, 'getName') ? $argumentClass->getName() : (string) $argument;
473
                foreach ($this->getIgnoredClasses() as $class) {
474
                    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
                        continue 2;
476
                    }
477
                }
478
            }
479
480 21
            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
                continue;
482
            }
483
484 21
            $arguments[] = $argument;
485
        }
486
487 26
        return $arguments;
488
    }
489
490
    /**
491
     * @param string|bool $resource
492
     */
493 27
    private function generateResourceName($resource): string
494
    {
495 27
        if (false === $this->pluralize) {
496 1
            return $resource;
497
        }
498
499 26
        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 26
    private function generateRouteName(array $resources): string
506
    {
507 26
        $routeName = '';
508 26
        foreach ($resources as $resource) {
509 26
            if (null !== $resource) {
510 26
                $routeName .= '_'.basename($resource);
511
            }
512
        }
513
514 26
        return $routeName;
515
    }
516
517
    /**
518
     * @param string[]               $resources
519
     * @param \ReflectionParameter[] $arguments
520
     */
521 26
    private function generateUrlParts(array $resources, array $arguments, string $httpMethod): array
522
    {
523 26
        $urlParts = [];
524 26
        foreach ($resources as $i => $resource) {
525
            // if we already added all parent routes paths to URL & we have
526
            // prefix - add it
527 26
            if (!empty($this->routePrefix) && $i === count($this->parents)) {
528 2
                $urlParts[] = $this->routePrefix;
529
            }
530
531
            // if we have argument for current resource, then it's object.
532
            // otherwise - it's collection
533 26
            if (isset($arguments[$i])) {
534 21
                if (null !== $resource) {
535 21
                    $urlParts[] =
536 21
                        strtolower($this->generateResourceName($resource))
537 21
                        .'/{'.$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 21
                    $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 26
            } elseif (null !== $resource) {
542 25
                if ((0 === count($arguments) && !in_array($httpMethod, $this->availableHTTPMethods))
543 25
                    || 'new' === $httpMethod
544 25
                    || 'post' === $httpMethod
545
                ) {
546 17
                    $urlParts[] = $this->generateResourceName(strtolower($resource));
547
                } else {
548 25
                    $urlParts[] = strtolower($resource);
549
                }
550
            }
551
        }
552
553 26
        return $urlParts;
554
    }
555
556
    /**
557
     * @param string[]               $resources
558
     * @param \ReflectionParameter[] $arguments
559
     */
560 17
    private function getCustomHttpMethod(string $httpMethod, array $resources, array $arguments): string
561
    {
562 17
        if (in_array($httpMethod, $this->availableConventionalActions)) {
563
            // allow hypertext as the engine of application state
564
            // through conventional GET actions
565 10
            return 'get';
566
        }
567
568 15
        if (count($arguments) < count($resources)) {
569
            // resource collection
570 15
            return 'get';
571
        }
572
573
        // custom object
574 12
        return 'patch';
575
    }
576
577
    /**
578
     * @return RouteAnnotation[]
579
     */
580 26
    private function readRouteAnnotation(\ReflectionMethod $reflectionMethod): array
581
    {
582 26
        $annotations = [];
583
584 26
        if ($newAnnotations = $this->readMethodAnnotations($reflectionMethod, 'Route')) {
585 9
            $annotations = array_merge($annotations, $newAnnotations);
586
        }
587
588 26
        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 26
    private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, string $annotationName): array
617
    {
618 26
        $annotations = [];
619 26
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
620
621 26
        if ($annotations_new = $this->annotationReader->getMethodAnnotations($reflectionMethod)) {
622 9
            foreach ($annotations_new as $annotation) {
623 9
                if ($annotation instanceof $annotationClass) {
624 9
                    $annotations[] = $annotation;
625
                }
626
            }
627
        }
628
629 26
        return $annotations;
630
    }
631
632 26
    private function addRoute(RestRouteCollection $collection, string $routeName, Route $route, bool $isCollection, bool $isInflectable, RouteAnnotation $annotation = null)
633
    {
634 26
        if ($annotation && null !== $annotation->getName()) {
635 4
            $options = $annotation->getOptions();
636
637 4
            if (false === $this->hasMethodPrefix || (isset($options['method_prefix']) && false === $options['method_prefix'])) {
638 4
                $routeName = $annotation->getName();
639
            } else {
640 3
                $routeName .= $annotation->getName();
641
            }
642
        }
643
644 26
        $fullRouteName = $this->namePrefix.$routeName;
645
646 26
        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 24
            $collection->add($fullRouteName, $route);
653
        }
654 26
    }
655
}
656