Completed
Push — master ( df9414...a422c7 )
by Guilh
06:31
created

RestActionReader::getCustomHttpMethod()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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