Completed
Push — master ( 547589...ab328c )
by Tobias
09:34 queued 11s
created

RestActionReader   F

Complexity

Total Complexity 102

Size/Duplication

Total Lines 748
Duplicated Lines 0.94 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 95.04%

Importance

Changes 0
Metric Value
wmc 102
lcom 1
cbo 7
dl 7
loc 748
ccs 249
cts 262
cp 0.9504
rs 1.852
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A setRoutePrefix() 0 4 1
A getRoutePrefix() 0 4 1
A setNamePrefix() 0 4 1
A getNamePrefix() 0 4 1
A setVersions() 0 4 1
A getVersions() 0 4 1
A setPluralize() 0 4 1
A getPluralize() 0 4 1
A setParents() 0 4 1
A getParents() 0 4 1
A includeFormatIfNeeded() 7 10 4
B isMethodReadable() 0 21 6
B getHttpMethodAndResourcesFromMethod() 0 33 7
F read() 0 114 17
A getVersionCondition() 0 8 2
A combineConditions() 0 12 3
A getVersionRequirement() 0 8 2
A hasVersionPlaceholder() 0 4 1
C getMethodArguments() 0 52 11
A generateResourceName() 0 8 2
A generateRouteName() 0 11 3
B generateUrlParts() 0 34 11
A getCustomHttpMethod() 0 16 3
A readRouteAnnotation() 0 10 2
A readClassAnnotation() 0 8 2
A readMethodAnnotation() 0 8 2
A readMethodAnnotations() 0 15 4
B addRoute() 0 23 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RestActionReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RestActionReader, and based on these observations, apply Extract Interface, too.

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
use Doctrine\Common\Annotations\Reader;
15
use FOS\RestBundle\Controller\Annotations\Route as RouteAnnotation;
16
use FOS\RestBundle\Inflector\InflectorInterface;
17
use FOS\RestBundle\Request\ParamFetcherInterface;
18
use FOS\RestBundle\Request\ParamReaderInterface;
19
use FOS\RestBundle\Routing\RestRouteCollection;
20
use Psr\Http\Message\MessageInterface;
21
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Session\SessionInterface;
24
use Symfony\Component\Routing\Route;
25
use Symfony\Component\Security\Core\User\UserInterface;
26
use Symfony\Component\Validator\ConstraintViolationListInterface;
27
28
/**
29
 * REST controller actions reader.
30
 *
31
 * @author Konstantin Kudryashov <[email protected]>
32
 */
33
class RestActionReader
34
{
35
    const COLLECTION_ROUTE_PREFIX = 'c';
36
37
    /**
38
     * @var Reader
39
     */
40
    private $annotationReader;
41
42
    /**
43
     * @var ParamReaderInterface
44
     */
45
    private $paramReader;
46
47
    /**
48
     * @var InflectorInterface
49
     */
50
    private $inflector;
51
52
    /**
53
     * @var array
54
     */
55
    private $formats;
56
57
    /**
58
     * @var bool
59
     */
60
    private $includeFormat;
61
62
    /**
63
     * @var string|null
64
     */
65
    private $routePrefix;
66
67
    /**
68
     * @var string|null
69
     */
70
    private $namePrefix;
71
72
    /**
73
     * @var array|string|null
74
     */
75
    private $versions;
76
77
    /**
78
     * @var bool|null
79
     */
80
    private $pluralize;
81
82
    /**
83
     * @var array
84
     */
85
    private $parents = [];
86
87
    /**
88
     * @var array
89
     */
90
    private $availableHTTPMethods = [
91
        'get',
92
        'post',
93
        'put',
94
        'patch',
95
        'delete',
96
        'link',
97
        'unlink',
98
        'head',
99
        'options',
100
        'mkcol',
101
        'propfind',
102
        'proppatch',
103
        'move',
104
        'copy',
105
        'lock',
106
        'unlock',
107
    ];
108
109
    /**
110
     * @var array
111
     */
112
    private $availableConventionalActions = ['new', 'edit', 'remove'];
113
114
    /**
115
     * @var bool
116
     */
117 56
    private $hasMethodPrefix;
118
119 56
    /**
120 56
     * Initializes controller reader.
121 56
     *
122 56
     * @param Reader               $annotationReader
123 56
     * @param ParamReaderInterface $paramReader
124 56
     * @param InflectorInterface   $inflector
125
     * @param bool                 $includeFormat
126
     * @param array                $formats
127
     * @param bool                 $hasMethodPrefix
128
     */
129
    public function __construct(Reader $annotationReader, ParamReaderInterface $paramReader, InflectorInterface $inflector, $includeFormat, array $formats = [], $hasMethodPrefix = true)
130
    {
131 37
        $this->annotationReader = $annotationReader;
132
        $this->paramReader = $paramReader;
133 37
        $this->inflector = $inflector;
134 37
        $this->includeFormat = $includeFormat;
135
        $this->formats = $formats;
136
        $this->hasMethodPrefix = $hasMethodPrefix;
137
    }
138
139
    /**
140
     * Sets routes prefix.
141 37
     *
142
     * @param string $prefix Routes prefix
143 37
     */
144
    public function setRoutePrefix($prefix = null)
145
    {
146
        $this->routePrefix = $prefix;
147
    }
148
149
    /**
150
     * Returns route prefix.
151 37
     *
152
     * @return string
153 37
     */
154 37
    public function getRoutePrefix()
155
    {
156
        return $this->routePrefix;
157
    }
158
159
    /**
160
     * Sets route names prefix.
161
     *
162
     * @param string $prefix Route names prefix
163
     */
164
    public function setNamePrefix($prefix = null)
165
    {
166
        $this->namePrefix = $prefix;
167
    }
168
169
    /**
170
     * Returns name prefix.
171 37
     *
172
     * @return string
173 37
     */
174 37
    public function getNamePrefix()
175
    {
176
        return $this->namePrefix;
177
    }
178
179
    /**
180
     * Sets route names versions.
181
     *
182
     * @param array|string|null $versions Route names versions
183
     */
184
    public function setVersions($versions = null)
185
    {
186
        $this->versions = (array) $versions;
187
    }
188
189
    /**
190
     * Returns versions.
191 37
     *
192
     * @return array|null
193 37
     */
194 37
    public function getVersions()
195
    {
196
        return $this->versions;
197
    }
198
199
    /**
200
     * Sets pluralize.
201
     *
202
     * @param bool|null $pluralize Specify if resource name must be pluralized
203
     */
204
    public function setPluralize($pluralize)
205
    {
206
        $this->pluralize = $pluralize;
207
    }
208
209
    /**
210
     * Returns pluralize.
211 37
     *
212
     * @return bool|null
213 37
     */
214 37
    public function getPluralize()
215
    {
216
        return $this->pluralize;
217
    }
218
219
    /**
220
     * Set parent routes.
221
     *
222
     * @param array $parents Array of parent resources names
223
     */
224
    public function setParents(array $parents)
225
    {
226
        $this->parents = $parents;
227
    }
228
229
    /**
230
     * Returns parents.
231
     *
232
     * @return array
233
     */
234
    public function getParents()
235
    {
236
        return $this->parents;
237 37
    }
238
239
    /**
240 37
     * Reads action route.
241 5
     *
242
     * @param RestRouteCollection $collection
243
     * @param \ReflectionMethod   $method
244
     * @param string[]            $resource
245
     *
246
     * @throws \InvalidArgumentException
247 37
     *
248
     * @return Route
249
     */
250 37
    public function read(RestRouteCollection $collection, \ReflectionMethod $method, $resource)
251 14
    {
252
        // check that every route parent has non-empty singular name
253
        foreach ($this->parents as $parent) {
254
            if (empty($parent) || '/' === substr($parent, -1)) {
255 37
                throw new \InvalidArgumentException('Every parent controller must have `get{SINGULAR}Action(\$id)` method where {SINGULAR} is a singular form of associated object');
256 37
            }
257 35
        }
258
259
        // if method is not readable - skip
260 37
        if (!$this->isMethodReadable($method)) {
261 37
            return;
262
        }
263
264
        // if we can't get http-method and resources from method name - skip
265 37
        $httpMethodAndResources = $this->getHttpMethodAndResourcesFromMethod($method, $resource);
266 21
        if (!$httpMethodAndResources) {
267 21
            return;
268
        }
269
270 37
        list($httpMethod, $resources, $isCollection, $isInflectable) = $httpMethodAndResources;
271 5
        $arguments = $this->getMethodArguments($method);
272 5
273
        // if we have only 1 resource & 1 argument passed, then it's object call, so
274 37
        // we can set collection singular name
275 10
        if (1 === count($resources) && 1 === count($arguments) - count($this->parents)) {
276 10
            $collection->setSingularName($resources[0]);
277
        }
278 37
279 37
        // if we have parents passed - merge them with own resource names
280
        if (count($this->parents)) {
281
            $resources = array_merge($this->parents, $resources);
282
        }
283
284 37
        if (empty($resources)) {
285 27
            $resources[] = null;
286 27
        }
287 27
288
        $routeName = $httpMethod.$this->generateRouteName($resources);
289
        $urlParts = $this->generateUrlParts($resources, $arguments, $httpMethod);
290 37
291 37
        // if passed method is not valid HTTP method then it's either
292 37
        // a hypertext driver, a custom object (PUT) or collection (GET)
293 37
        // method
294 37
        if (!in_array($httpMethod, $this->availableHTTPMethods)) {
295 37
            $urlParts[] = $httpMethod;
296 37
            $httpMethod = $this->getCustomHttpMethod($httpMethod, $resources, $arguments);
297
        }
298 37
299 37
        // generated parameters
300 18
        $routeName = strtolower($routeName);
301 18
        $path = implode('/', $urlParts);
302 18
        $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...
303 18
        $requirements = [];
304 18
        $options = [];
305 18
        $host = '';
306
        $versionCondition = $this->getVersionCondition();
307 18
        $versionRequirement = $this->getVersionRequirement();
308 18
309
        $annotations = $this->readRouteAnnotation($method);
310 18
        if (!empty($annotations)) {
311 18
            foreach ($annotations as $annotation) {
312 18
                $path = implode('/', $urlParts);
313
                $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...
314 18
                $requirements = [];
315 18
                $options = [];
316 18
                $methods = explode('|', $httpMethod);
317 18
318 18
                $annoRequirements = $annotation->getRequirements();
319 18
                $annoMethods = $annotation->getMethods();
320 18
321
                if (!empty($annoMethods)) {
322 18
                    $methods = $annoMethods;
323
                }
324
325 18
                $path = null !== $annotation->getPath() ? $this->routePrefix.$annotation->getPath() : $path;
326 18
                $requirements = array_merge($requirements, $annoRequirements);
327 18
                $options = array_merge($options, $annotation->getOptions());
328 18
                $defaults = array_merge($defaults, $annotation->getDefaults());
329 18
                $host = $annotation->getHost();
330 18
                $schemes = $annotation->getSchemes();
331 37
332
                if ($this->hasVersionPlaceholder($path)) {
333 37
                    $combinedCondition = $annotation->getCondition();
334
                    $requirements = array_merge($versionRequirement, $requirements);
335
                } else {
336 37
                    $combinedCondition = $this->combineConditions($versionCondition, $annotation->getCondition());
337 37
                }
338 37
339 37
                $this->includeFormatIfNeeded($path, $requirements);
340
341 37
                // add route to collection
342
                $route = new Route(
343
                    $path, $defaults, $requirements, $options, $host, $schemes, $methods, $combinedCondition
344
                );
345
                $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable, $annotation);
346 37
            }
347
        } else {
348 37
            if ($this->hasVersionPlaceholder($path)) {
349 36
                $versionCondition = null;
350
                $requirements = $versionRequirement;
351
            }
352 11
353
            $this->includeFormatIfNeeded($path, $requirements);
354
355
            $methods = explode('|', strtoupper($httpMethod));
356
357
            // add route to collection
358
            $route = new Route(
359
                $path, $defaults, $requirements, $options, $host, [], $methods, $versionCondition
360
            );
361 18
            $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable);
362
        }
363 18
    }
364 7
365
    /**
366
     * @return string|null
367 11
     */
368 11
    private function getVersionCondition()
369
    {
370
        if (empty($this->versions)) {
371 1
            return;
372
        }
373
374
        return sprintf("request.attributes.get('version') in ['%s']", implode("', '", $this->versions));
375
    }
376
377
    /**
378
     * @param string|null $conditionOne
379
     * @param string|null $conditionTwo
380 37
     *
381
     * @return string|null
382 37
     */
383 37
    private function combineConditions($conditionOne, $conditionTwo)
384
    {
385 37
        if (null === $conditionOne) {
386 12
            return $conditionTwo;
387 12
        }
388 37
389 37
        if (null === $conditionTwo) {
390
            return $conditionOne;
391
        }
392
393
        return sprintf('(%s) and (%s)', $conditionOne, $conditionTwo);
394
    }
395
396
    /**
397
     * @return array
398 37
     */
399
    private function getVersionRequirement()
400
    {
401 37
        if (empty($this->versions)) {
402 10
            return [];
403
        }
404
405 37
        return ['version' => implode('|', $this->versions)];
406 37
    }
407
408 37
    /**
409
     * Checks whether provided path contains {version} placeholder.
410 37
     *
411
     * @param string $path
412
     *
413 37
     * @return bool
414 4
     */
415
    private function hasVersionPlaceholder($path)
416
    {
417 37
        return false !== strpos($path, '{version}');
418
    }
419
420
    /**
421
     * Include the format in the path and requirements if its enabled.
422
     *
423
     * @param string $path
424
     * @param array  $requirements
425
     */
426
    private function includeFormatIfNeeded(&$path, &$requirements)
427
    {
428 37 View Code Duplication
        if (true === $this->includeFormat) {
429
            $path .= '.{_format}';
430
431 37
            if (!isset($requirements['_format']) && !empty($this->formats)) {
432 35
                $requirements['_format'] = implode('|', array_keys($this->formats));
433
            }
434
        }
435 37
    }
436 37
437 37
    /**
438 37
     * Checks whether provided method is readable.
439 37
     *
440 37
     * @param \ReflectionMethod $method
441
     *
442 37
     * @return bool
443 37
     */
444 37
    private function isMethodReadable(\ReflectionMethod $method)
445 16
    {
446 16
        // if method starts with _ - skip
447 37
        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...
448 14
            return false;
449 14
        }
450
451 37
        $hasNoRouteMethod = (bool) $this->readMethodAnnotation($method, 'NoRoute');
452 16
        $hasNoRouteClass = (bool) $this->readClassAnnotation($method->getDeclaringClass(), 'NoRoute');
453 16
454 16
        $hasNoRoute = $hasNoRouteMethod || $hasNoRouteClass;
455 16
        // since NoRoute extends Route we need to exclude all the method NoRoute annotations
456
        $hasRoute = (bool) $this->readMethodAnnotation($method, 'Route') && !$hasNoRouteMethod;
457 37
458
        // if method has NoRoute annotation and does not have Route annotation - skip
459 37
        if ($hasNoRoute && !$hasRoute) {
460
            return false;
461
        }
462
463
        return true;
464
    }
465
466
    /**
467
     * Returns HTTP method and resources list from method signature.
468
     *
469 37
     * @param \ReflectionMethod $method
470
     * @param string[]          $resource
471
     *
472 37
     * @return bool|array
473
     */
474
    private function getHttpMethodAndResourcesFromMethod(\ReflectionMethod $method, $resource)
475
    {
476 37
        // if method doesn't match regex - skip
477 37
        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...
478 37
            return false;
479 37
        }
480 37
481 37
        $httpMethod = strtolower($matches[1]);
482
        $resources = preg_split(
483 37
            '/([A-Z][^A-Z]*)/', $matches[2], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
484 37
        );
485 34
        $isCollection = false;
486
        $isInflectable = true;
487
488
        if (0 === strpos($httpMethod, self::COLLECTION_ROUTE_PREFIX)
489 34
            && in_array(substr($httpMethod, 1), $this->availableHTTPMethods)
490 34
        ) {
491 22
            $isCollection = true;
492 22
            $httpMethod = substr($httpMethod, 1);
493 22
        } elseif ('options' === $httpMethod) {
494 21
            $isCollection = true;
495
        }
496 3
497 1
        if ($isCollection && !empty($resource)) {
498
            $resourcePluralized = $this->generateResourceName(end($resource));
499 34
            $isInflectable = ($resourcePluralized != $resource[count($resource) - 1]);
500 37
            $resource[count($resource) - 1] = $resourcePluralized;
501
        }
502 37
503
        $resources = array_merge($resource, $resources);
504
505
        return [$httpMethod, $resources, $isCollection, $isInflectable];
506
    }
507
508
    /**
509
     * Returns readable arguments from method.
510
     *
511
     * @param \ReflectionMethod $method
512 37
     *
513
     * @return \ReflectionParameter[]
514 37
     */
515 1
    private function getMethodArguments(\ReflectionMethod $method)
516
    {
517
        // ignore all query params
518 36
        $params = $this->paramReader->getParamsFromMethod($method);
519
520
        // check if a parameter is coming from the request body
521
        $ignoreParameters = [];
522
        if (class_exists(ParamConverter::class)) {
523
            $ignoreParameters = array_map(function ($annotation) {
524
                return
525
                    $annotation instanceof ParamConverter &&
526
                    'fos_rest.request_body' === $annotation->getConverter()
527
                        ? $annotation->getName() : null;
528 37
            }, $this->annotationReader->getMethodAnnotations($method));
529
        }
530 37
531 37
        // ignore several type hinted arguments
532 37
        $ignoreClasses = [
533 37
            ConstraintViolationListInterface::class,
534 37
            MessageInterface::class,
535 37
            ParamConverter::class,
536
            ParamFetcherInterface::class,
537 37
            Request::class,
538
            SessionInterface::class,
539
            UserInterface::class,
540
        ];
541
542
        $arguments = [];
543
        foreach ($method->getParameters() as $argument) {
544
            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...
545
                continue;
546
            }
547
548
            $argumentClass = $argument->getClass();
549 37
            if ($argumentClass) {
550
                $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...
551 37
                foreach ($ignoreClasses as $class) {
552 37
                    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...
553
                        continue 2;
554
                    }
555 37
                }
556 3
            }
557 3
558
            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...
559
                continue;
560
            }
561 37
562 34
            $arguments[] = $argument;
563 24
        }
564 24
565 24
        return $arguments;
566 24
    }
567 10
568
    /**
569 37
     * Generates final resource name.
570 36
     *
571
     * @param string|bool $resource
572 35
     *
573 36
     * @return string
574 29
     */
575 29
    private function generateResourceName($resource)
576 35
    {
577
        if (false === $this->pluralize) {
578 36
            return $resource;
579 37
        }
580
581 37
        return $this->inflector->pluralize($resource);
0 ignored issues
show
Bug introduced by
It seems like $resource defined by parameter $resource on line 575 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...
582
    }
583
584
    /**
585
     * Generates route name from resources list.
586
     *
587
     * @param string[] $resources
588
     *
589
     * @return string
590
     */
591
    private function generateRouteName(array $resources)
592
    {
593 27
        $routeName = '';
594
        foreach ($resources as $resource) {
595 27
            if (null !== $resource) {
596
                $routeName .= '_'.basename($resource);
597
            }
598 12
        }
599
600
        return $routeName;
601 25
    }
602
603 15
    /**
604
     * Generates URL parts for route from resources list.
605
     *
606
     * @param string[]               $resources
607 24
     * @param \ReflectionParameter[] $arguments
608
     * @param string                 $httpMethod
609
     *
610
     * @return array
611
     */
612
    private function generateUrlParts(array $resources, array $arguments, $httpMethod)
613
    {
614
        $urlParts = [];
615
        foreach ($resources as $i => $resource) {
616
            // if we already added all parent routes paths to URL & we have
617 37
            // prefix - add it
618
            if (!empty($this->routePrefix) && $i === count($this->parents)) {
619 37
                $urlParts[] = $this->routePrefix;
620
            }
621 37
622 18
            // if we have argument for current resource, then it's object.
623 18
            // otherwise - it's collection
624
            if (isset($arguments[$i])) {
625 37
                if (null !== $resource) {
626
                    $urlParts[] =
627
                        strtolower($this->generateResourceName($resource))
628
                        .'/{'.$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...
629
                } else {
630
                    $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...
631
                }
632
            } elseif (null !== $resource) {
633
                if ((0 === count($arguments) && !in_array($httpMethod, $this->availableHTTPMethods))
634
                    || 'new' === $httpMethod
635
                    || 'post' === $httpMethod
636 37
                ) {
637
                    $urlParts[] = $this->generateResourceName(strtolower($resource));
638 37
                } else {
639
                    $urlParts[] = strtolower($resource);
640 37
                }
641
            }
642
        }
643 37
644
        return $urlParts;
645
    }
646
647
    /**
648
     * Returns custom HTTP method for provided list of resources, arguments, method.
649
     *
650
     * @param string                 $httpMethod current HTTP method
651
     * @param string[]               $resources  resources list
652
     * @param \ReflectionParameter[] $arguments  list of method arguments
653 37
     *
654
     * @return string
655 37
     */
656
    private function getCustomHttpMethod($httpMethod, array $resources, array $arguments)
657 37
    {
658 18
        if (in_array($httpMethod, $this->availableConventionalActions)) {
659
            // allow hypertext as the engine of application state
660 37
            // through conventional GET actions
661
            return 'get';
662
        }
663
664
        if (count($arguments) < count($resources)) {
665
            // resource collection
666
            return 'get';
667
        }
668
669
        // custom object
670 37
        return 'patch';
671
    }
672 37
673 37
    /**
674
     * Returns first route annotation for method.
675 37
     *
676 18
     * @param \ReflectionMethod $reflectionMethod
677 18
     *
678 18
     * @return RouteAnnotation[]
679 18
     */
680 18
    private function readRouteAnnotation(\ReflectionMethod $reflectionMethod)
681 18
    {
682
        $annotations = [];
683 37
684
        if ($newAnnotations = $this->readMethodAnnotations($reflectionMethod, 'Route')) {
685
            $annotations = array_merge($annotations, $newAnnotations);
686
        }
687
688
        return $annotations;
689
    }
690
691
    /**
692
     * Reads class annotations.
693
     *
694 37
     * @param \ReflectionClass $reflectionClass
695
     * @param string           $annotationName
696 37
     *
697 4
     * @return RouteAnnotation|null
698
     */
699 4
    private function readClassAnnotation(\ReflectionClass $reflectionClass, $annotationName)
700 3
    {
701 3
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
702 4
703
        if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, $annotationClass)) {
704 4
            return $annotation;
705
        }
706 37
    }
707
708 37
    /**
709 4
     * Reads method annotations.
710 4
     *
711 4
     * @param \ReflectionMethod $reflectionMethod
712 4
     * @param string            $annotationName
713 4
     *
714 35
     * @return RouteAnnotation|null
715
     */
716 37
    private function readMethodAnnotation(\ReflectionMethod $reflectionMethod, $annotationName)
717
    {
718
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
719
720
        if ($annotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $annotationClass)) {
721
            return $annotation;
722
        }
723
    }
724
725
    /**
726
     * Reads method annotations.
727
     *
728
     * @param \ReflectionMethod $reflectionMethod
729
     * @param string            $annotationName
730
     *
731
     * @return RouteAnnotation[]
732
     */
733
    private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, $annotationName)
734
    {
735
        $annotations = [];
736
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
737
738
        if ($annotations_new = $this->annotationReader->getMethodAnnotations($reflectionMethod)) {
739
            foreach ($annotations_new as $annotation) {
740
                if ($annotation instanceof $annotationClass) {
741
                    $annotations[] = $annotation;
742
                }
743
            }
744
        }
745
746
        return $annotations;
747
    }
748
749
    /**
750
     * @param RestRouteCollection $collection
751
     * @param string              $routeName
752
     * @param Route               $route
753
     * @param bool                $isCollection
754
     * @param bool                $isInflectable
755
     * @param RouteAnnotation     $annotation
756
     */
757
    private function addRoute(RestRouteCollection $collection, $routeName, $route, $isCollection, $isInflectable, RouteAnnotation $annotation = null)
758
    {
759
        if ($annotation && null !== $annotation->getName()) {
760
            $options = $annotation->getOptions();
761
762
            if (false === $this->hasMethodPrefix || (isset($options['method_prefix']) && false === $options['method_prefix'])) {
763
                $routeName = $annotation->getName();
764
            } else {
765
                $routeName .= $annotation->getName();
766
            }
767
        }
768
769
        $fullRouteName = $this->namePrefix.$routeName;
770
771
        if ($isCollection && !$isInflectable) {
772
            $collection->add($this->namePrefix.self::COLLECTION_ROUTE_PREFIX.$routeName, $route);
773
            if (!$collection->get($fullRouteName)) {
774
                $collection->add($fullRouteName, clone $route);
775
            }
776
        } else {
777
            $collection->add($fullRouteName, $route);
778
        }
779
    }
780
}
781