|
| 1 | +<?php |
| 2 | + |
| 3 | +/* |
| 4 | + * This file is part of the Symfony package. |
| 5 | + * |
| 6 | + * (c) Fabien Potencier <fabien@symfony.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 Symfony\Component\DependencyInjection\Compiler; |
| 13 | + |
| 14 | +use Symfony\Component\DependencyInjection\Definition; |
| 15 | +use Symfony\Component\DependencyInjection\Reference; |
| 16 | + |
| 17 | +/** |
| 18 | + * Looks for definitions with autowiring enabled and parses their "@param" annotations for service ids and parameters. |
| 19 | + * |
| 20 | + * @author Nicolas Grekas <p@tchwork.com> |
| 21 | + */ |
| 22 | +class AutowireAnnotatedArgumentsPass extends AbstractRecursivePass |
| 23 | +{ |
| 24 | + /** |
| 25 | + * {@inheritdoc} |
| 26 | + */ |
| 27 | + protected function processValue($value, $isRoot = false) |
| 28 | + { |
| 29 | + $value = parent::processValue($value, $isRoot); |
| 30 | + |
| 31 | + if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) { |
| 32 | + return $value; |
| 33 | + } |
| 34 | + if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) { |
| 35 | + return $value; |
| 36 | + } |
| 37 | + |
| 38 | + try { |
| 39 | + if ($constructor = $this->getConstructor($value, false)) { |
| 40 | + $constructor = strtolower($constructor->name); |
| 41 | + } |
| 42 | + } catch (RuntimeException $e) { |
| 43 | + return $value; |
| 44 | + } |
| 45 | + if (!$annotatedParams = $this->getAnnotatedParams($reflectionClass, $constructor, $value)) { |
| 46 | + return $value; |
| 47 | + } |
| 48 | + $methodCalls = $value->getMethodCalls(); |
| 49 | + |
| 50 | + if ($constructor) { |
| 51 | + array_unshift($methodCalls, array($constructor, $value->getArguments())); |
| 52 | + } |
| 53 | + |
| 54 | + foreach ($methodCalls as $i => $call) { |
| 55 | + list($method, $arguments) = $call; |
| 56 | + if (!isset($annotatedParams[$m = strtolower($method)])) { |
| 57 | + continue; |
| 58 | + } |
| 59 | + |
| 60 | + foreach ($annotatedParams[$m] as $index => $param) { |
| 61 | + if (!array_key_exists($index, $arguments) || ('' === $arguments[$index] && $param instanceof Reference)) { |
| 62 | + $arguments[$index] = $param; |
| 63 | + } |
| 64 | + } |
| 65 | + ksort($arguments); |
| 66 | + |
| 67 | + if ($arguments !== $call[1]) { |
| 68 | + $methodCalls[$i][1] = $arguments; |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + if ($constructor) { |
| 73 | + list(, $arguments) = array_shift($methodCalls); |
| 74 | + |
| 75 | + if ($arguments !== $value->getArguments()) { |
| 76 | + $value->setArguments($arguments); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + if ($methodCalls !== $value->getMethodCalls()) { |
| 81 | + $value->setMethodCalls($methodCalls); |
| 82 | + } |
| 83 | + |
| 84 | + return $value; |
| 85 | + } |
| 86 | + |
| 87 | + private function getAnnotatedParams(\ReflectionClass $reflectionClass, ?string $constructor, Definition $definition): array |
| 88 | + { |
| 89 | + $annotatedParams = array(); |
| 90 | + |
| 91 | + if (null !== $constructor) { |
| 92 | + $annotatedParams[$constructor] = array(); |
| 93 | + } |
| 94 | + foreach ($definition->getMethodCalls() as list($method)) { |
| 95 | + $annotatedParams[strtolower($method)] = array(); |
| 96 | + } |
| 97 | + |
| 98 | + foreach ($reflectionClass->getMethods() as $reflectionMethod) { |
| 99 | + $r = $reflectionMethod; |
| 100 | + if (!isset($annotatedParams[$m = strtolower($r->name)])) { |
| 101 | + continue; |
| 102 | + } |
| 103 | + |
| 104 | + while (true) { |
| 105 | + if (false !== $doc = $r->getDocComment()) { |
| 106 | + if (false !== stripos($doc, '@param') && preg_match_all('#(?:^/\*\*|\n\s*+\*)\s*+@param\s[^\$\*]*+\$([^\s\*]++)\s++(@[^\s\*]++|%[^%\s\*]++%)(?=[\s\*])#i', $doc, $params, PREG_SET_ORDER)) { |
| 107 | + $paramIndex = array(); |
| 108 | + foreach ($r->getParameters() as $i => $p) { |
| 109 | + $paramIndex[$p->name] = $i; |
| 110 | + } |
| 111 | + foreach ($params as $p) { |
| 112 | + if (!isset($paramIndex[$p[1]])) { |
| 113 | + $this->container->log($this, sprintf('Skipping @param "$%s": no such argument on "%s::%s()".', $p[1], $r->class, $r->name)); |
| 114 | + continue; |
| 115 | + } |
| 116 | + if ('@' === $p[2][0] && $this->container->has($id = substr($p[2], 1))) { |
| 117 | + $p[2] = new Reference($id); |
| 118 | + } elseif ('%' === $p[2][0] && $this->container->hasParameter($id = substr($p[2], 1, -1))) { |
| 119 | + $p[2] = $this->container->getParameter($id); |
| 120 | + } else { |
| 121 | + $this->container->log($this, sprintf('Skipping @param "$%s" on "%s::%s(): %s "%s" not found.', $p[1], $r->class, $r->name, '@' === $p[2][0] ? 'service' : 'parameter', $id)); |
| 122 | + continue; |
| 123 | + } |
| 124 | + $annotatedParams[$m] += array($paramIndex[$p[1]] => $p[2]); |
| 125 | + } |
| 126 | + } |
| 127 | + if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) { |
| 128 | + break; |
| 129 | + } |
| 130 | + } |
| 131 | + try { |
| 132 | + $r = $r->getPrototype(); |
| 133 | + } catch (\ReflectionException $e) { |
| 134 | + break; // method has no prototype |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + return array_filter($annotatedParams); |
| 140 | + } |
| 141 | +} |
0 commit comments