Completed
Push — master ( 1af7ca...fe85dd )
by Lukas Kahwe
05:49
created

RestActionReader   D

Complexity

Total Complexity 93

Size/Duplication

Total Lines 629
Duplicated Lines 1.11 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 93.68%

Importance

Changes 9
Bugs 2 Features 2
Metric Value
wmc 93
c 9
b 2
f 2
lcom 1
cbo 6
dl 7
loc 629
ccs 252
cts 269
cp 0.9368
rs 4.7548

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 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
D read() 0 105 15
B getCondition() 0 20 5
A includeFormatIfNeeded() 7 10 4
B isMethodReadable() 0 21 6
C getHttpMethodAndResourcesFromMethod() 0 33 7
C getMethodArguments() 0 37 7
A generateResourceName() 0 8 2
A generateRouteName() 0 11 3
C generateUrlParts() 0 34 11
A getCustomHttpMethod() 0 16 3
A readRouteAnnotation() 0 12 3
A readClassAnnotation() 0 8 2
A readMethodAnnotation() 0 8 2
A readMethodAnnotations() 0 15 4
C addRoute() 0 23 8

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\ParamReader;
18
use FOS\RestBundle\Routing\RestRouteCollection;
19
use Symfony\Component\Routing\Route;
20
use FOS\RestBundle\Request\ParamReaderInterface;
21
22
/**
23
 * REST controller actions reader.
24
 *
25
 * @author Konstantin Kudryashov <[email protected]>
26
 */
27
class RestActionReader
28
{
29
    const COLLECTION_ROUTE_PREFIX = 'c';
30
31
    private $annotationReader;
32
    private $paramReader;
33
    private $inflector;
34
    private $formats;
35
    private $includeFormat;
36
    private $routePrefix;
37
    private $namePrefix;
38
    private $versions;
39
    private $pluralize;
40
    private $parents = [];
41
    private $availableHTTPMethods = ['get', 'post', 'put', 'patch', 'delete', 'link', 'unlink', 'head', 'options'];
42
    private $availableConventionalActions = ['new', 'edit', 'remove'];
43
44
    /**
45
     * Initializes controller reader.
46
     *
47
     * @param Reader               $annotationReader
48
     * @param ParamReaderInterface $paramReader
49
     * @param InflectorInterface   $inflector
50
     * @param bool                 $includeFormat
51
     * @param array                $formats
52
     */
53 40
    public function __construct(Reader $annotationReader, ParamReaderInterface $paramReader, InflectorInterface $inflector, $includeFormat, array $formats = [])
54
    {
55 40
        $this->annotationReader = $annotationReader;
56 40
        $this->paramReader = $paramReader;
57 40
        $this->inflector = $inflector;
58 40
        $this->includeFormat = $includeFormat;
59 40
        $this->formats = $formats;
60 40
    }
61
62
    /**
63
     * Sets routes prefix.
64
     *
65
     * @param string $prefix Routes prefix
66
     */
67 25
    public function setRoutePrefix($prefix = null)
68
    {
69 25
        $this->routePrefix = $prefix;
70 25
    }
71
72
    /**
73
     * Returns route prefix.
74
     *
75
     * @return string
76
     */
77 25
    public function getRoutePrefix()
78
    {
79 25
        return $this->routePrefix;
80
    }
81
82
    /**
83
     * Sets route names prefix.
84
     *
85
     * @param string $prefix Route names prefix
86
     */
87 25
    public function setNamePrefix($prefix = null)
88
    {
89 25
        $this->namePrefix = $prefix;
90 25
    }
91
92
    /**
93
     * Returns name prefix.
94
     *
95
     * @return string
96
     */
97
    public function getNamePrefix()
98
    {
99
        return $this->namePrefix;
100
    }
101
102
    /**
103
     * Sets route names versions.
104
     *
105
     * @param array|string|null $versions Route names versions
106
     */
107 25
    public function setVersions($versions = null)
108
    {
109 25
        $this->versions = (array) $versions;
110 25
    }
111
112
    /**
113
     * Returns versions.
114
     *
115
     * @return array|null
116
     */
117
    public function getVersions()
118
    {
119
        return $this->versions;
120
    }
121
122
    /**
123
     * Sets pluralize.
124
     *
125
     * @param bool|null $pluralize Specify if resource name must be pluralized
126
     */
127 25
    public function setPluralize($pluralize)
128
    {
129 25
        $this->pluralize = $pluralize;
130 25
    }
131
132
    /**
133
     * Returns pluralize.
134
     *
135
     * @return bool|null
136
     */
137
    public function getPluralize()
138
    {
139
        return $this->pluralize;
140
    }
141
142
    /**
143
     * Set parent routes.
144
     *
145
     * @param array $parents Array of parent resources names
146
     */
147 25
    public function setParents(array $parents)
148
    {
149 25
        $this->parents = $parents;
150 25
    }
151
152
    /**
153
     * Returns parents.
154
     *
155
     * @return array
156
     */
157
    public function getParents()
158
    {
159
        return $this->parents;
160
    }
161
162
    /**
163
     * Reads action route.
164
     *
165
     * @param RestRouteCollection $collection
166
     * @param \ReflectionMethod   $method
167
     * @param string[]            $resource
168
     *
169
     * @throws \InvalidArgumentException
170
     *
171
     * @return Route
172
     */
173 25
    public function read(RestRouteCollection $collection, \ReflectionMethod $method, $resource)
174
    {
175
        // check that every route parent has non-empty singular name
176 25
        foreach ($this->parents as $parent) {
177 5
            if (empty($parent) || '/' === substr($parent, -1)) {
178
                throw new \InvalidArgumentException(
179
                    "Every parent controller must have `get{SINGULAR}Action(\$id)` method\n".
180
                    'where {SINGULAR} is a singular form of associated object'
181
                );
182
            }
183 25
        }
184
185
        // if method is not readable - skip
186 25
        if (!$this->isMethodReadable($method)) {
187 14
            return;
188
        }
189
190
        // if we can't get http-method and resources from method name - skip
191 25
        $httpMethodAndResources = $this->getHttpMethodAndResourcesFromMethod($method, $resource);
192 25
        if (!$httpMethodAndResources) {
193 24
            return;
194
        }
195
196 25
        list($httpMethod, $resources, $isCollection, $isInflectable) = $httpMethodAndResources;
197 25
        $arguments = $this->getMethodArguments($method);
198
199
        // if we have only 1 resource & 1 argument passed, then it's object call, so
200
        // we can set collection singular name
201 25
        if (1 === count($resources) && 1 === count($arguments) - count($this->parents)) {
202 19
            $collection->setSingularName($resources[0]);
203 19
        }
204
205
        // if we have parents passed - merge them with own resource names
206 25
        if (count($this->parents)) {
207 5
            $resources = array_merge($this->parents, $resources);
208 5
        }
209
210 25
        if (empty($resources)) {
211
            $resources[] = null;
212
        }
213
214 25
        $routeName = $httpMethod.$this->generateRouteName($resources);
215 25
        $urlParts = $this->generateUrlParts($resources, $arguments, $httpMethod);
216
217
        // if passed method is not valid HTTP method then it's either
218
        // a hypertext driver, a custom object (PUT) or collection (GET)
219
        // method
220 25
        if (!in_array($httpMethod, $this->availableHTTPMethods)) {
221 17
            $urlParts[] = $httpMethod;
222 17
            $httpMethod = $this->getCustomHttpMethod($httpMethod, $resources, $arguments);
223 17
        }
224
225
        // generated parameters
226 25
        $routeName = strtolower($routeName);
227 25
        $path = implode('/', $urlParts);
228 25
        $defaults = ['_controller' => $method->getName()];
229 25
        $requirements = [];
230 25
        $options = [];
231 25
        $host = '';
232 25
        $condition = null;
233
234 25
        $annotations = $this->readRouteAnnotation($method);
235 25
        if (!empty($annotations)) {
236 8
            foreach ($annotations as $annotation) {
237 8
                $path = implode('/', $urlParts);
238 8
                $defaults = ['_controller' => $method->getName()];
239 8
                $requirements = [];
240 8
                $options = [];
241 8
                $methods = explode('|', $httpMethod);
242
243 8
                $annoRequirements = $annotation->getRequirements();
244 8
                $annoMethods = $annotation->getMethods();
245
246 8
                if (!empty($annoMethods)) {
247 8
                    $methods = $annoMethods;
248 8
                }
249
250 8
                $path = $annotation->getPath() !== null ? $this->routePrefix.$annotation->getPath() : $path;
251 8
                $requirements = array_merge($requirements, $annoRequirements);
252 8
                $options = array_merge($options, $annotation->getOptions());
253 8
                $defaults = array_merge($defaults, $annotation->getDefaults());
254 8
                $host = $annotation->getHost();
255 8
                $schemes = $annotation->getSchemes();
256 8
                $condition = $this->getCondition($method, $annotation);
257
258 8
                $this->includeFormatIfNeeded($path, $requirements);
259
260
                // add route to collection
261 8
                $route = new Route(
262 8
                    $path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition
263 8
                );
264 8
                $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable, $annotation);
265 8
            }
266 8
        } else {
267 24
            $this->includeFormatIfNeeded($path, $requirements);
268
269 24
            $methods = explode('|', strtoupper($httpMethod));
270
271
            // add route to collection
272 24
            $route = new Route(
273 24
                $path, $defaults, $requirements, $options, $host, [], $methods, $condition
274 24
            );
275 24
            $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable);
276
        }
277 25
    }
278
279
    /**
280
     * Determine the Route condition by combining Route annotations with Version annotation.
281
     *
282
     * @param \ReflectionMethod $method
283
     * @param RouteAnnotation   $annotation
284
     *
285
     * @return string
286
     */
287 8
    private function getCondition(\ReflectionMethod $method, RouteAnnotation $annotation)
0 ignored issues
show
Unused Code introduced by
The parameter $method is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
288
    {
289 8
        $condition = $annotation->getCondition();
290
291 8
        if (!empty($this->versions)) {
292 1
            $versionCondition = "request.attributes.get('version') == (";
293 1
            $first = true;
294 1
            foreach ($this->versions as $version) {
295 1
                if (!$first) {
296 1
                    $versionCondition .= ' or ';
297 1
                }
298 1
                $versionCondition .= '\''.$version.'\'';
299 1
                $first = false;
300 1
            }
301 1
            $versionCondition .= ')';
302 1
            $condition = $condition ? '('.$condition.') and '.$versionCondition : $versionCondition;
303 1
        }
304
305 8
        return $condition;
306
    }
307
308
    /**
309
     * Include the format in the path and requirements if its enabled.
310
     *
311
     * @param string $path
312
     * @param array  $requirements
313
     */
314 25
    private function includeFormatIfNeeded(&$path, &$requirements)
315
    {
316 25 View Code Duplication
        if ($this->includeFormat === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
317 25
            $path .= '.{_format}';
318
319 25
            if (!isset($requirements['_format']) && !empty($this->formats)) {
320 1
                $requirements['_format'] = implode('|', array_keys($this->formats));
321 1
            }
322 25
        }
323 25
    }
324
325
    /**
326
     * Checks whether provided method is readable.
327
     *
328
     * @param \ReflectionMethod $method
329
     *
330
     * @return bool
331
     */
332 25
    private function isMethodReadable(\ReflectionMethod $method)
333
    {
334
        // if method starts with _ - skip
335 25
        if ('_' === substr($method->getName(), 0, 1)) {
336 10
            return false;
337
        }
338
339 25
        $hasNoRouteMethod = (bool) $this->readMethodAnnotation($method, 'NoRoute');
340 25
        $hasNoRouteClass = (bool) $this->readClassAnnotation($method->getDeclaringClass(), 'NoRoute');
341
342 25
        $hasNoRoute = $hasNoRoute = $hasNoRouteMethod || $hasNoRouteClass;
343
        // since NoRoute extends Route we need to exclude all the method NoRoute annotations
344 25
        $hasRoute = (bool) $this->readMethodAnnotation($method, 'Route') && !$hasNoRouteMethod;
345
346
        // if method has NoRoute annotation and does not have Route annotation - skip
347 25
        if ($hasNoRoute && !$hasRoute) {
348 4
            return false;
349
        }
350
351 25
        return true;
352
    }
353
354
    /**
355
     * Returns HTTP method and resources list from method signature.
356
     *
357
     * @param \ReflectionMethod $method
358
     * @param string[]          $resource
359
     *
360
     * @return bool|array
361
     */
362 25
    private function getHttpMethodAndResourcesFromMethod(\ReflectionMethod $method, $resource)
363
    {
364
        // if method doesn't match regex - skip
365 25
        if (!preg_match('/([a-z][_a-z0-9]+)(.*)Action/', $method->getName(), $matches)) {
366 24
            return false;
367
        }
368
369 25
        $httpMethod = strtolower($matches[1]);
370 25
        $resources = preg_split(
371 25
            '/([A-Z][^A-Z]*)/', $matches[2], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
372 25
        );
373 25
        $isCollection = false;
374 25
        $isInflectable = true;
375
376 25
        if (0 === strpos($httpMethod, self::COLLECTION_ROUTE_PREFIX)
377 25
            && in_array(substr($httpMethod, 1), $this->availableHTTPMethods)
378 25
        ) {
379 6
            $isCollection = true;
380 6
            $httpMethod = substr($httpMethod, 1);
381 25
        } elseif ('options' === $httpMethod) {
382 14
            $isCollection = true;
383 14
        }
384
385 25
        if ($isCollection && !empty($resource)) {
386 6
            $resourcePluralized = $this->generateResourceName(end($resource));
387 6
            $isInflectable = ($resourcePluralized != $resource[count($resource) - 1]);
388 6
            $resource[count($resource) - 1] = $resourcePluralized;
389 6
        }
390
391 25
        $resources = array_merge($resource, $resources);
392
393 25
        return [$httpMethod, $resources, $isCollection, $isInflectable];
394
    }
395
396
    /**
397
     * Returns readable arguments from method.
398
     *
399
     * @param \ReflectionMethod $method
400
     *
401
     * @return \ReflectionParameter[]
402
     */
403 25
    private function getMethodArguments(\ReflectionMethod $method)
404
    {
405
        // ignore all query params
406 25
        $params = $this->paramReader->getParamsFromMethod($method);
407
408
        // ignore type hinted arguments that are or extend from:
409
        // * Symfony\Component\HttpFoundation\Request
410
        // * FOS\RestBundle\Request\QueryFetcher
411
        // * Symfony\Component\Validator\ConstraintViolationList
412
        $ignoreClasses = [
413 25
            'Symfony\Component\HttpFoundation\Request',
414 25
            'FOS\RestBundle\Request\ParamFetcherInterface',
415 25
            'Symfony\Component\Validator\ConstraintViolationListInterface',
416 25
            'Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter',
417 25
            'Psr\Http\Message\MessageInterface',
418 25
        ];
419
420 25
        $arguments = [];
421 25
        foreach ($method->getParameters() as $argument) {
422 22
            if (isset($params[$argument->getName()])) {
423
                continue;
424
            }
425
426 22
            $argumentClass = $argument->getClass();
427 22
            if ($argumentClass) {
428 11
                foreach ($ignoreClasses as $class) {
429 11
                    if ($argumentClass->getName() === $class || $argumentClass->isSubclassOf($class)) {
430 11
                        continue 2;
431
                    }
432 1
                }
433
            }
434
435 22
            $arguments[] = $argument;
436 25
        }
437
438 25
        return $arguments;
439
    }
440
441
    /**
442
     * Generates final resource name.
443
     *
444
     * @param string|bool $resource
445
     *
446
     * @return string
447
     */
448 25
    private function generateResourceName($resource)
449
    {
450 25
        if (false === $this->pluralize) {
451 1
            return $resource;
452
        }
453
454 24
        return $this->inflector->pluralize($resource);
0 ignored issues
show
Bug introduced by
It seems like $resource defined by parameter $resource on line 448 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...
455
    }
456
457
    /**
458
     * Generates route name from resources list.
459
     *
460
     * @param string[] $resources
461
     *
462
     * @return string
463
     */
464 25
    private function generateRouteName(array $resources)
465
    {
466 25
        $routeName = '';
467 25
        foreach ($resources as $resource) {
468 25
            if (null !== $resource) {
469 25
                $routeName .= '_'.basename($resource);
470 25
            }
471 25
        }
472
473 25
        return $routeName;
474
    }
475
476
    /**
477
     * Generates URL parts for route from resources list.
478
     *
479
     * @param string[]               $resources
480
     * @param \ReflectionParameter[] $arguments
481
     * @param string                 $httpMethod
482
     *
483
     * @return array
484
     */
485 25
    private function generateUrlParts(array $resources, array $arguments, $httpMethod)
486
    {
487 25
        $urlParts = [];
488 25
        foreach ($resources as $i => $resource) {
489
            // if we already added all parent routes paths to URL & we have
490
            // prefix - add it
491 25
            if (!empty($this->routePrefix) && $i === count($this->parents)) {
492 3
                $urlParts[] = $this->routePrefix;
493 3
            }
494
495
            // if we have argument for current resource, then it's object.
496
            // otherwise - it's collection
497 25
            if (isset($arguments[$i])) {
498 22
                if (null !== $resource) {
499 22
                    $urlParts[] =
500 22
                        strtolower($this->generateResourceName($resource))
501 22
                        .'/{'.$arguments[$i]->getName().'}';
502 22
                } else {
503
                    $urlParts[] = '{'.$arguments[$i]->getName().'}';
504
                }
505 25
            } elseif (null !== $resource) {
506 25
                if ((0 === count($arguments) && !in_array($httpMethod, $this->availableHTTPMethods))
507
                    || 'new' === $httpMethod
508 24
                    || 'post' === $httpMethod
509 25
                ) {
510 18
                    $urlParts[] = $this->generateResourceName(strtolower($resource));
511 18
                } else {
512 24
                    $urlParts[] = strtolower($resource);
513
                }
514 25
            }
515 25
        }
516
517 25
        return $urlParts;
518
    }
519
520
    /**
521
     * Returns custom HTTP method for provided list of resources, arguments, method.
522
     *
523
     * @param string                 $httpMethod current HTTP method
524
     * @param string[]               $resources  resources list
525
     * @param \ReflectionParameter[] $arguments  list of method arguments
526
     *
527
     * @return string
528
     */
529 17
    private function getCustomHttpMethod($httpMethod, array $resources, array $arguments)
530
    {
531 17
        if (in_array($httpMethod, $this->availableConventionalActions)) {
532
            // allow hypertext as the engine of application state
533
            // through conventional GET actions
534 12
            return 'get';
535
        }
536
537 15
        if (count($arguments) < count($resources)) {
538
            // resource collection
539 15
            return 'get';
540
        }
541
542
        //custom object
543 14
        return 'patch';
544
    }
545
546
    /**
547
     * Returns first route annotation for method.
548
     *
549
     * @param \ReflectionMethod $reflectionMethod
550
     *
551
     * @return RouteAnnotation[]
552
     */
553 25
    private function readRouteAnnotation(\ReflectionMethod $reflectionMethod)
554
    {
555 25
        $annotations = [];
556
557 25
        foreach (['Route', 'Get', 'Post', 'Put', 'Patch', 'Delete', 'Link', 'Unlink', 'Head', 'Options'] as $annotationName) {
558 25
            if ($annotations_new = $this->readMethodAnnotations($reflectionMethod, $annotationName)) {
559 8
                $annotations = array_merge($annotations, $annotations_new);
560 8
            }
561 25
        }
562
563 25
        return $annotations;
564
    }
565
566
    /**
567
     * Reads class annotations.
568
     *
569
     * @param \ReflectionClass $reflectionClass
570
     * @param string           $annotationName
571
     *
572
     * @return RouteAnnotation|null
573
     */
574 25
    private function readClassAnnotation(\ReflectionClass $reflectionClass, $annotationName)
575
    {
576 25
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
577
578 25
        if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, $annotationClass)) {
579
            return $annotation;
580
        }
581 25
    }
582
583
    /**
584
     * Reads method annotations.
585
     *
586
     * @param \ReflectionMethod $reflectionMethod
587
     * @param string            $annotationName
588
     *
589
     * @return RouteAnnotation|null
590
     */
591 25
    private function readMethodAnnotation(\ReflectionMethod $reflectionMethod, $annotationName)
592
    {
593 25
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
594
595 25
        if ($annotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $annotationClass)) {
596 8
            return $annotation;
597
        }
598 25
    }
599
600
    /**
601
     * Reads method annotations.
602
     *
603
     * @param \ReflectionMethod $reflectionMethod
604
     * @param string            $annotationName
605
     *
606
     * @return RouteAnnotation[]
607
     */
608 25
    private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, $annotationName)
609
    {
610 25
        $annotations = [];
611 25
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
612
613 25
        if ($annotations_new = $this->annotationReader->getMethodAnnotations($reflectionMethod)) {
614 8
            foreach ($annotations_new as $annotation) {
615 8
                if ($annotation instanceof $annotationClass) {
616 8
                    $annotations[] = $annotation;
617 8
                }
618 8
            }
619 8
        }
620
621 25
        return $annotations;
622
    }
623
624
    /**
625
     * @param RestRouteCollection $collection
626
     * @param string              $routeName
627
     * @param Route               $route
628
     * @param bool                $isCollection
629
     * @param bool                $isInflectable
630
     * @param RouteAnnotation     $annotation
631
     */
632 25
    private function addRoute(RestRouteCollection $collection, $routeName, $route, $isCollection, $isInflectable, RouteAnnotation $annotation = null)
633
    {
634 25
        if ($annotation && null !== $annotation->getName()) {
635 4
            $options = $annotation->getOptions();
636
637 4
            if (isset($options['method_prefix']) && false === $options['method_prefix']) {
638 3
                $routeName = $annotation->getName();
639 3
            } else {
640 4
                $routeName = $routeName.$annotation->getName();
641
            }
642 4
        }
643
644 25
        $fullRouteName = $this->namePrefix.$routeName;
645
646 25
        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 4
            }
651 4
        } else {
652 23
            $collection->add($fullRouteName, $route);
653
        }
654 25
    }
655
}
656