Completed
Push — master ( ad4bdc...54b0a9 )
by Christian
02:47 queued 01:03
created

RestActionReader::getParents()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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