Completed
Pull Request — master (#1506)
by Guilh
15:57 queued 11:18
created

RestActionReader::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 7
cts 7
cp 1
rs 9.4285
cc 1
eloc 6
nc 1
nop 5
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 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 46
    public function __construct(Reader $annotationReader, ParamReaderInterface $paramReader, InflectorInterface $inflector, $includeFormat, array $formats = [])
117
    {
118 46
        $this->annotationReader = $annotationReader;
119 46
        $this->paramReader = $paramReader;
120 46
        $this->inflector = $inflector;
121 46
        $this->includeFormat = $includeFormat;
122 46
        $this->formats = $formats;
123 46
    }
124
125
    /**
126
     * Sets routes prefix.
127
     *
128
     * @param string $prefix Routes prefix
129
     */
130 36
    public function setRoutePrefix($prefix = null)
131
    {
132 36
        $this->routePrefix = $prefix;
133 36
    }
134
135
    /**
136
     * Returns route prefix.
137
     *
138
     * @return string
139
     */
140 36
    public function getRoutePrefix()
141
    {
142 36
        return $this->routePrefix;
143
    }
144
145
    /**
146
     * Sets route names prefix.
147
     *
148
     * @param string $prefix Route names prefix
149
     */
150 36
    public function setNamePrefix($prefix = null)
151
    {
152 36
        $this->namePrefix = $prefix;
153 36
    }
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 36
    public function setVersions($versions = null)
171
    {
172 36
        $this->versions = (array) $versions;
173 36
    }
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 36
    public function setPluralize($pluralize)
191
    {
192 36
        $this->pluralize = $pluralize;
193 36
    }
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 36
    public function setParents(array $parents)
211
    {
212 36
        $this->parents = $parents;
213 36
    }
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 36
    public function read(RestRouteCollection $collection, \ReflectionMethod $method, $resource)
237
    {
238
        // check that every route parent has non-empty singular name
239 36
        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 36
        }
247
248
        // if method is not readable - skip
249 36
        if (!$this->isMethodReadable($method)) {
250 14
            return;
251
        }
252
253
        // if we can't get http-method and resources from method name - skip
254 36
        $httpMethodAndResources = $this->getHttpMethodAndResourcesFromMethod($method, $resource);
255 36
        if (!$httpMethodAndResources) {
256 35
            return;
257
        }
258
259 36
        list($httpMethod, $resources, $isCollection, $isInflectable) = $httpMethodAndResources;
260 36
        $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 36
        if (1 === count($resources) && 1 === count($arguments) - count($this->parents)) {
265 20
            $collection->setSingularName($resources[0]);
266 20
        }
267
268
        // if we have parents passed - merge them with own resource names
269 36
        if (count($this->parents)) {
270 5
            $resources = array_merge($this->parents, $resources);
271 5
        }
272
273 36
        if (empty($resources)) {
274 10
            $resources[] = null;
275 10
        }
276
277 36
        $routeName = $httpMethod.$this->generateRouteName($resources);
278 36
        $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 36
        if (!in_array($httpMethod, $this->availableHTTPMethods)) {
284 27
            $urlParts[] = $httpMethod;
285 27
            $httpMethod = $this->getCustomHttpMethod($httpMethod, $resources, $arguments);
286 27
        }
287
288
        // generated parameters
289 36
        $routeName = strtolower($routeName);
290 36
        $path = implode('/', $urlParts);
291 36
        $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 36
        $requirements = [];
293 36
        $options = [];
294 36
        $host = '';
295 36
        $versionCondition = $this->getVersionCondition();
296
297 36
        $annotations = $this->readRouteAnnotation($method);
298 36
        if (!empty($annotations)) {
299 18
            foreach ($annotations as $annotation) {
300 18
                $path = implode('/', $urlParts);
301 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...
302 18
                $requirements = [];
303 18
                $options = [];
304 18
                $methods = explode('|', $httpMethod);
305
306 18
                $annoRequirements = $annotation->getRequirements();
307 18
                $annoMethods = $annotation->getMethods();
308
309 18
                if (!empty($annoMethods)) {
310 18
                    $methods = $annoMethods;
311 18
                }
312
313 18
                $path = $annotation->getPath() !== null ? $this->routePrefix.$annotation->getPath() : $path;
314 18
                $requirements = array_merge($requirements, $annoRequirements);
315 18
                $options = array_merge($options, $annotation->getOptions());
316 18
                $defaults = array_merge($defaults, $annotation->getDefaults());
317 18
                $host = $annotation->getHost();
318 18
                $schemes = $annotation->getSchemes();
319 18
                $combinedCondition = $this->combineConditions($versionCondition, $annotation->getCondition());
320
321 18
                $this->includeFormatIfNeeded($path, $requirements);
322
323
                // add route to collection
324 18
                $route = new Route(
325 18
                    $path, $defaults, $requirements, $options, $host, $schemes, $methods, $combinedCondition
326 18
                );
327 18
                $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable, $annotation);
328 18
            }
329 18
        } else {
330 36
            $this->includeFormatIfNeeded($path, $requirements);
331
332 36
            $methods = explode('|', strtoupper($httpMethod));
333
334
            // add route to collection
335 36
            $route = new Route(
336 36
                $path, $defaults, $requirements, $options, $host, [], $methods, $versionCondition
337 36
            );
338 36
            $this->addRoute($collection, $routeName, $route, $isCollection, $isInflectable);
339
        }
340 36
    }
341
342
    /**
343
     * @return string|null
344
     */
345 36
    private function getVersionCondition()
346
    {
347 36
        if (empty($this->versions)) {
348 35
            return;
349
        }
350
351 11
        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 18
    private function combineConditions($conditionOne, $conditionTwo)
361
    {
362 18
        if (null === $conditionOne) {
363 7
            return $conditionTwo;
364
        }
365
366 11
        if (null === $conditionTwo) {
367 11
            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 36
    private function includeFormatIfNeeded(&$path, &$requirements)
380
    {
381 36 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 36
            $path .= '.{_format}';
383
384 36
            if (!isset($requirements['_format']) && !empty($this->formats)) {
385 12
                $requirements['_format'] = implode('|', array_keys($this->formats));
386 12
            }
387 36
        }
388 36
    }
389
390
    /**
391
     * Checks whether provided method is readable.
392
     *
393
     * @param \ReflectionMethod $method
394
     *
395
     * @return bool
396
     */
397 36
    private function isMethodReadable(\ReflectionMethod $method)
398
    {
399
        // if method starts with _ - skip
400 36
        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 36
        $hasNoRouteMethod = (bool) $this->readMethodAnnotation($method, 'NoRoute');
405 36
        $hasNoRouteClass = (bool) $this->readClassAnnotation($method->getDeclaringClass(), 'NoRoute');
406
407 36
        $hasNoRoute = $hasNoRouteMethod || $hasNoRouteClass;
408
        // since NoRoute extends Route we need to exclude all the method NoRoute annotations
409 36
        $hasRoute = (bool) $this->readMethodAnnotation($method, 'Route') && !$hasNoRouteMethod;
410
411
        // if method has NoRoute annotation and does not have Route annotation - skip
412 36
        if ($hasNoRoute && !$hasRoute) {
413 4
            return false;
414
        }
415
416 36
        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 36
    private function getHttpMethodAndResourcesFromMethod(\ReflectionMethod $method, $resource)
428
    {
429
        // if method doesn't match regex - skip
430 36
        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 35
            return false;
432
        }
433
434 36
        $httpMethod = strtolower($matches[1]);
435 36
        $resources = preg_split(
436 36
            '/([A-Z][^A-Z]*)/', $matches[2], -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
437 36
        );
438 36
        $isCollection = false;
439 36
        $isInflectable = true;
440
441 36
        if (0 === strpos($httpMethod, self::COLLECTION_ROUTE_PREFIX)
442 36
            && in_array(substr($httpMethod, 1), $this->availableHTTPMethods)
443 36
        ) {
444 15
            $isCollection = true;
445 15
            $httpMethod = substr($httpMethod, 1);
446 36
        } elseif ('options' === $httpMethod) {
447 14
            $isCollection = true;
448 14
        }
449
450 36
        if ($isCollection && !empty($resource)) {
451 15
            $resourcePluralized = $this->generateResourceName(end($resource));
452 15
            $isInflectable = ($resourcePluralized != $resource[count($resource) - 1]);
453 15
            $resource[count($resource) - 1] = $resourcePluralized;
454 15
        }
455
456 36
        $resources = array_merge($resource, $resources);
457
458 36
        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 36
    private function getMethodArguments(\ReflectionMethod $method)
469
    {
470
        // ignore all query params
471 36
        $params = $this->paramReader->getParamsFromMethod($method);
472
473
        // ignore several type hinted arguments
474
        $ignoreClasses = [
475 36
            \Symfony\Component\HttpFoundation\Request::class,
476 36
            \FOS\RestBundle\Request\ParamFetcherInterface::class,
477 36
            \Symfony\Component\Validator\ConstraintViolationListInterface::class,
478 36
            \Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter::class,
479 36
        ];
480
481 36
        $arguments = [];
482 36
        foreach ($method->getParameters() as $argument) {
483 33
            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 33
            $argumentClass = $argument->getClass();
488 33
            if ($argumentClass) {
489 21
                $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 21
                foreach ($ignoreClasses as $class) {
491 21
                    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 20
                        continue 2;
493
                    }
494 2
                }
495 1
            }
496
497 33
            $arguments[] = $argument;
498 36
        }
499
500 36
        return $arguments;
501
    }
502
503
    /**
504
     * Generates final resource name.
505
     *
506
     * @param string|bool $resource
507
     *
508
     * @return string
509
     */
510 36
    private function generateResourceName($resource)
511
    {
512 36
        if (false === $this->pluralize) {
513 1
            return $resource;
514
        }
515
516 35
        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 36
    private function generateRouteName(array $resources)
527
    {
528 36
        $routeName = '';
529 36
        foreach ($resources as $resource) {
530 36
            if (null !== $resource) {
531 36
                $routeName .= '_'.basename($resource);
532 36
            }
533 36
        }
534
535 36
        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 36
    private function generateUrlParts(array $resources, array $arguments, $httpMethod)
548
    {
549 36
        $urlParts = [];
550 36
        foreach ($resources as $i => $resource) {
551
            // if we already added all parent routes paths to URL & we have
552
            // prefix - add it
553 36
            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 36
            if (isset($arguments[$i])) {
560 33
                if (null !== $resource) {
561 23
                    $urlParts[] =
562 23
                        strtolower($this->generateResourceName($resource))
563 23
                        .'/{'.$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 23
                } else {
565 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...
566
                }
567 36
            } elseif (null !== $resource) {
568 35
                if ((0 === count($arguments) && !in_array($httpMethod, $this->availableHTTPMethods))
569
                    || 'new' === $httpMethod
570 34
                    || 'post' === $httpMethod
571 35
                ) {
572 28
                    $urlParts[] = $this->generateResourceName(strtolower($resource));
573 28
                } else {
574 34
                    $urlParts[] = strtolower($resource);
575
                }
576 35
            }
577 36
        }
578
579 36
        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 27
    private function getCustomHttpMethod($httpMethod, array $resources, array $arguments)
592
    {
593 27
        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 25
        if (count($arguments) < count($resources)) {
600
            // resource collection
601 15
            return 'get';
602
        }
603
604
        // custom object
605 24
        return 'patch';
606
    }
607
608
    /**
609
     * Returns first route annotation for method.
610
     *
611
     * @param \ReflectionMethod $reflectionMethod
612
     *
613
     * @return RouteAnnotation[]
614
     */
615 36
    private function readRouteAnnotation(\ReflectionMethod $reflectionMethod)
616
    {
617 36
        $annotations = [];
618
619 36
        if ($newAnnotations = $this->readMethodAnnotations($reflectionMethod, 'Route')) {
620 18
            $annotations = array_merge($annotations, $newAnnotations);
621 18
        }
622
623 36
        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 36
    private function readClassAnnotation(\ReflectionClass $reflectionClass, $annotationName)
635
    {
636 36
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
637
638 36
        if ($annotation = $this->annotationReader->getClassAnnotation($reflectionClass, $annotationClass)) {
639
            return $annotation;
640
        }
641 36
    }
642
643
    /**
644
     * Reads method annotations.
645
     *
646
     * @param \ReflectionMethod $reflectionMethod
647
     * @param string            $annotationName
648
     *
649
     * @return RouteAnnotation|null
650
     */
651 36
    private function readMethodAnnotation(\ReflectionMethod $reflectionMethod, $annotationName)
652
    {
653 36
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
654
655 36
        if ($annotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, $annotationClass)) {
656 18
            return $annotation;
657
        }
658 36
    }
659
660
    /**
661
     * Reads method annotations.
662
     *
663
     * @param \ReflectionMethod $reflectionMethod
664
     * @param string            $annotationName
665
     *
666
     * @return RouteAnnotation[]
667
     */
668 36
    private function readMethodAnnotations(\ReflectionMethod $reflectionMethod, $annotationName)
669
    {
670 36
        $annotations = [];
671 36
        $annotationClass = "FOS\\RestBundle\\Controller\\Annotations\\$annotationName";
672
673 36
        if ($annotations_new = $this->annotationReader->getMethodAnnotations($reflectionMethod)) {
674 18
            foreach ($annotations_new as $annotation) {
675 18
                if ($annotation instanceof $annotationClass) {
676 18
                    $annotations[] = $annotation;
677 18
                }
678 18
            }
679 18
        }
680
681 36
        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 36
    private function addRoute(RestRouteCollection $collection, $routeName, $route, $isCollection, $isInflectable, RouteAnnotation $annotation = null)
693
    {
694 36
        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 36
        $fullRouteName = $this->namePrefix.$routeName;
705
706 36
        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 34
            $collection->add($fullRouteName, $route);
713
        }
714 36
    }
715
}
716