Completed
Push — master ( bb017f...79816b )
by
unknown
11:51 queued 09:37
created

RestActionReader::setParents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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