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