Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\DependencyInjection\Compiler;
|
Chris@0
|
13
|
Chris@14
|
14 use Symfony\Component\Config\Resource\ClassExistenceResource;
|
Chris@0
|
15 use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
|
Chris@0
|
16 use Symfony\Component\DependencyInjection\ContainerBuilder;
|
Chris@0
|
17 use Symfony\Component\DependencyInjection\Definition;
|
Chris@14
|
18 use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
|
Chris@0
|
19 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
Chris@14
|
20 use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
|
Chris@14
|
21 use Symfony\Component\DependencyInjection\TypedReference;
|
Chris@0
|
22
|
Chris@0
|
23 /**
|
Chris@14
|
24 * Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
|
Chris@0
|
25 *
|
Chris@0
|
26 * @author Kévin Dunglas <dunglas@gmail.com>
|
Chris@14
|
27 * @author Nicolas Grekas <p@tchwork.com>
|
Chris@0
|
28 */
|
Chris@14
|
29 class AutowirePass extends AbstractRecursivePass
|
Chris@0
|
30 {
|
Chris@17
|
31 private $definedTypes = [];
|
Chris@0
|
32 private $types;
|
Chris@14
|
33 private $ambiguousServiceTypes;
|
Chris@17
|
34 private $autowired = [];
|
Chris@14
|
35 private $lastFailure;
|
Chris@14
|
36 private $throwOnAutowiringException;
|
Chris@17
|
37 private $autowiringExceptions = [];
|
Chris@14
|
38 private $strictMode;
|
Chris@14
|
39
|
Chris@14
|
40 /**
|
Chris@14
|
41 * @param bool $throwOnAutowireException Errors can be retrieved via Definition::getErrors()
|
Chris@14
|
42 */
|
Chris@14
|
43 public function __construct($throwOnAutowireException = true)
|
Chris@14
|
44 {
|
Chris@14
|
45 $this->throwOnAutowiringException = $throwOnAutowireException;
|
Chris@14
|
46 }
|
Chris@14
|
47
|
Chris@14
|
48 /**
|
Chris@14
|
49 * @deprecated since version 3.4, to be removed in 4.0.
|
Chris@14
|
50 *
|
Chris@14
|
51 * @return AutowiringFailedException[]
|
Chris@14
|
52 */
|
Chris@14
|
53 public function getAutowiringExceptions()
|
Chris@14
|
54 {
|
Chris@14
|
55 @trigger_error('Calling AutowirePass::getAutowiringExceptions() is deprecated since Symfony 3.4 and will be removed in 4.0. Use Definition::getErrors() instead.', E_USER_DEPRECATED);
|
Chris@14
|
56
|
Chris@14
|
57 return $this->autowiringExceptions;
|
Chris@14
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 /**
|
Chris@0
|
61 * {@inheritdoc}
|
Chris@0
|
62 */
|
Chris@0
|
63 public function process(ContainerBuilder $container)
|
Chris@0
|
64 {
|
Chris@14
|
65 // clear out any possibly stored exceptions from before
|
Chris@17
|
66 $this->autowiringExceptions = [];
|
Chris@14
|
67 $this->strictMode = $container->hasParameter('container.autowiring.strict_mode') && $container->getParameter('container.autowiring.strict_mode');
|
Chris@0
|
68
|
Chris@0
|
69 try {
|
Chris@14
|
70 parent::process($container);
|
Chris@0
|
71 } finally {
|
Chris@17
|
72 $this->definedTypes = [];
|
Chris@0
|
73 $this->types = null;
|
Chris@14
|
74 $this->ambiguousServiceTypes = null;
|
Chris@17
|
75 $this->autowired = [];
|
Chris@0
|
76 }
|
Chris@0
|
77 }
|
Chris@0
|
78
|
Chris@0
|
79 /**
|
Chris@0
|
80 * Creates a resource to help know if this service has changed.
|
Chris@0
|
81 *
|
Chris@0
|
82 * @param \ReflectionClass $reflectionClass
|
Chris@0
|
83 *
|
Chris@0
|
84 * @return AutowireServiceResource
|
Chris@14
|
85 *
|
Chris@14
|
86 * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.
|
Chris@0
|
87 */
|
Chris@0
|
88 public static function createResourceForClass(\ReflectionClass $reflectionClass)
|
Chris@0
|
89 {
|
Chris@14
|
90 @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', E_USER_DEPRECATED);
|
Chris@14
|
91
|
Chris@17
|
92 $metadata = [];
|
Chris@0
|
93
|
Chris@14
|
94 foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
|
Chris@14
|
95 if (!$reflectionMethod->isStatic()) {
|
Chris@14
|
96 $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
|
Chris@14
|
97 }
|
Chris@0
|
98 }
|
Chris@0
|
99
|
Chris@0
|
100 return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
|
Chris@0
|
101 }
|
Chris@0
|
102
|
Chris@0
|
103 /**
|
Chris@14
|
104 * {@inheritdoc}
|
Chris@0
|
105 */
|
Chris@14
|
106 protected function processValue($value, $isRoot = false)
|
Chris@0
|
107 {
|
Chris@14
|
108 try {
|
Chris@14
|
109 return $this->doProcessValue($value, $isRoot);
|
Chris@14
|
110 } catch (AutowiringFailedException $e) {
|
Chris@14
|
111 if ($this->throwOnAutowiringException) {
|
Chris@14
|
112 throw $e;
|
Chris@14
|
113 }
|
Chris@14
|
114
|
Chris@14
|
115 $this->autowiringExceptions[] = $e;
|
Chris@14
|
116 $this->container->getDefinition($this->currentId)->addError($e->getMessage());
|
Chris@14
|
117
|
Chris@14
|
118 return parent::processValue($value, $isRoot);
|
Chris@14
|
119 }
|
Chris@14
|
120 }
|
Chris@14
|
121
|
Chris@14
|
122 private function doProcessValue($value, $isRoot = false)
|
Chris@14
|
123 {
|
Chris@14
|
124 if ($value instanceof TypedReference) {
|
Chris@14
|
125 if ($ref = $this->getAutowiredReference($value, $value->getRequiringClass() ? sprintf('for "%s" in "%s"', $value->getType(), $value->getRequiringClass()) : '')) {
|
Chris@14
|
126 return $ref;
|
Chris@14
|
127 }
|
Chris@14
|
128 $this->container->log($this, $this->createTypeNotFoundMessage($value, 'it'));
|
Chris@14
|
129 }
|
Chris@14
|
130 $value = parent::processValue($value, $isRoot);
|
Chris@14
|
131
|
Chris@14
|
132 if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
|
Chris@14
|
133 return $value;
|
Chris@14
|
134 }
|
Chris@14
|
135 if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
|
Chris@14
|
136 $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
|
Chris@14
|
137
|
Chris@14
|
138 return $value;
|
Chris@0
|
139 }
|
Chris@0
|
140
|
Chris@14
|
141 $methodCalls = $value->getMethodCalls();
|
Chris@14
|
142
|
Chris@14
|
143 try {
|
Chris@14
|
144 $constructor = $this->getConstructor($value, false);
|
Chris@14
|
145 } catch (RuntimeException $e) {
|
Chris@14
|
146 throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
|
Chris@0
|
147 }
|
Chris@0
|
148
|
Chris@14
|
149 if ($constructor) {
|
Chris@17
|
150 array_unshift($methodCalls, [$constructor, $value->getArguments()]);
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@14
|
153 $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls);
|
Chris@14
|
154
|
Chris@14
|
155 if ($constructor) {
|
Chris@14
|
156 list(, $arguments) = array_shift($methodCalls);
|
Chris@14
|
157
|
Chris@14
|
158 if ($arguments !== $value->getArguments()) {
|
Chris@14
|
159 $value->setArguments($arguments);
|
Chris@14
|
160 }
|
Chris@0
|
161 }
|
Chris@14
|
162
|
Chris@14
|
163 if ($methodCalls !== $value->getMethodCalls()) {
|
Chris@14
|
164 $value->setMethodCalls($methodCalls);
|
Chris@14
|
165 }
|
Chris@14
|
166
|
Chris@14
|
167 return $value;
|
Chris@14
|
168 }
|
Chris@14
|
169
|
Chris@14
|
170 /**
|
Chris@14
|
171 * @param \ReflectionClass $reflectionClass
|
Chris@14
|
172 * @param array $methodCalls
|
Chris@14
|
173 *
|
Chris@14
|
174 * @return array
|
Chris@14
|
175 */
|
Chris@14
|
176 private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls)
|
Chris@14
|
177 {
|
Chris@14
|
178 foreach ($methodCalls as $i => $call) {
|
Chris@14
|
179 list($method, $arguments) = $call;
|
Chris@14
|
180
|
Chris@14
|
181 if ($method instanceof \ReflectionFunctionAbstract) {
|
Chris@14
|
182 $reflectionMethod = $method;
|
Chris@14
|
183 } else {
|
Chris@18
|
184 $definition = new Definition($reflectionClass->name);
|
Chris@18
|
185 try {
|
Chris@18
|
186 $reflectionMethod = $this->getReflectionMethod($definition, $method);
|
Chris@18
|
187 } catch (RuntimeException $e) {
|
Chris@18
|
188 if ($definition->getFactory()) {
|
Chris@18
|
189 continue;
|
Chris@18
|
190 }
|
Chris@18
|
191 throw $e;
|
Chris@18
|
192 }
|
Chris@14
|
193 }
|
Chris@14
|
194
|
Chris@14
|
195 $arguments = $this->autowireMethod($reflectionMethod, $arguments);
|
Chris@14
|
196
|
Chris@14
|
197 if ($arguments !== $call[1]) {
|
Chris@14
|
198 $methodCalls[$i][1] = $arguments;
|
Chris@14
|
199 }
|
Chris@14
|
200 }
|
Chris@14
|
201
|
Chris@14
|
202 return $methodCalls;
|
Chris@14
|
203 }
|
Chris@14
|
204
|
Chris@14
|
205 /**
|
Chris@14
|
206 * Autowires the constructor or a method.
|
Chris@14
|
207 *
|
Chris@14
|
208 * @param \ReflectionFunctionAbstract $reflectionMethod
|
Chris@14
|
209 * @param array $arguments
|
Chris@14
|
210 *
|
Chris@14
|
211 * @return array The autowired arguments
|
Chris@14
|
212 *
|
Chris@14
|
213 * @throws AutowiringFailedException
|
Chris@14
|
214 */
|
Chris@14
|
215 private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments)
|
Chris@14
|
216 {
|
Chris@14
|
217 $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
|
Chris@14
|
218 $method = $reflectionMethod->name;
|
Chris@14
|
219 $parameters = $reflectionMethod->getParameters();
|
Chris@14
|
220 if (method_exists('ReflectionMethod', 'isVariadic') && $reflectionMethod->isVariadic()) {
|
Chris@0
|
221 array_pop($parameters);
|
Chris@0
|
222 }
|
Chris@0
|
223
|
Chris@0
|
224 foreach ($parameters as $index => $parameter) {
|
Chris@18
|
225 if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
|
Chris@0
|
226 continue;
|
Chris@0
|
227 }
|
Chris@0
|
228
|
Chris@14
|
229 $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
|
Chris@0
|
230
|
Chris@14
|
231 if (!$type) {
|
Chris@14
|
232 if (isset($arguments[$index])) {
|
Chris@0
|
233 continue;
|
Chris@0
|
234 }
|
Chris@0
|
235
|
Chris@14
|
236 // no default value? Then fail
|
Chris@14
|
237 if (!$parameter->isDefaultValueAvailable()) {
|
Chris@14
|
238 // For core classes, isDefaultValueAvailable() can
|
Chris@14
|
239 // be false when isOptional() returns true. If the
|
Chris@14
|
240 // argument *is* optional, allow it to be missing
|
Chris@14
|
241 if ($parameter->isOptional()) {
|
Chris@14
|
242 continue;
|
Chris@14
|
243 }
|
Chris@16
|
244 $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false);
|
Chris@16
|
245 $type = $type ? sprintf('is type-hinted "%s"', $type) : 'has no type-hint';
|
Chris@16
|
246
|
Chris@16
|
247 throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type));
|
Chris@0
|
248 }
|
Chris@0
|
249
|
Chris@14
|
250 // specifically pass the default value
|
Chris@14
|
251 $arguments[$index] = $parameter->getDefaultValue();
|
Chris@14
|
252
|
Chris@14
|
253 continue;
|
Chris@14
|
254 }
|
Chris@14
|
255
|
Chris@14
|
256 if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, !$parameter->isOptional() ? $class : ''), 'for '.sprintf('argument "$%s" of method "%s()"', $parameter->name, $class.'::'.$method))) {
|
Chris@14
|
257 $failureMessage = $this->createTypeNotFoundMessage($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
|
Chris@14
|
258
|
Chris@14
|
259 if ($parameter->isDefaultValueAvailable()) {
|
Chris@14
|
260 $value = $parameter->getDefaultValue();
|
Chris@14
|
261 } elseif (!$parameter->allowsNull()) {
|
Chris@14
|
262 throw new AutowiringFailedException($this->currentId, $failureMessage);
|
Chris@0
|
263 }
|
Chris@14
|
264 $this->container->log($this, $failureMessage);
|
Chris@0
|
265 }
|
Chris@0
|
266
|
Chris@0
|
267 $arguments[$index] = $value;
|
Chris@0
|
268 }
|
Chris@0
|
269
|
Chris@0
|
270 if ($parameters && !isset($arguments[++$index])) {
|
Chris@0
|
271 while (0 <= --$index) {
|
Chris@0
|
272 $parameter = $parameters[$index];
|
Chris@0
|
273 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
|
Chris@0
|
274 break;
|
Chris@0
|
275 }
|
Chris@0
|
276 unset($arguments[$index]);
|
Chris@0
|
277 }
|
Chris@0
|
278 }
|
Chris@0
|
279
|
Chris@0
|
280 // it's possible index 1 was set, then index 0, then 2, etc
|
Chris@0
|
281 // make sure that we re-order so they're injected as expected
|
Chris@0
|
282 ksort($arguments);
|
Chris@14
|
283
|
Chris@14
|
284 return $arguments;
|
Chris@14
|
285 }
|
Chris@14
|
286
|
Chris@14
|
287 /**
|
Chris@14
|
288 * @return TypedReference|null A reference to the service matching the given type, if any
|
Chris@14
|
289 */
|
Chris@14
|
290 private function getAutowiredReference(TypedReference $reference, $deprecationMessage)
|
Chris@14
|
291 {
|
Chris@14
|
292 $this->lastFailure = null;
|
Chris@14
|
293 $type = $reference->getType();
|
Chris@14
|
294
|
Chris@14
|
295 if ($type !== $this->container->normalizeId($reference) || ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract())) {
|
Chris@14
|
296 return $reference;
|
Chris@14
|
297 }
|
Chris@14
|
298
|
Chris@14
|
299 if (null === $this->types) {
|
Chris@14
|
300 $this->populateAvailableTypes($this->strictMode);
|
Chris@14
|
301 }
|
Chris@14
|
302
|
Chris@14
|
303 if (isset($this->definedTypes[$type])) {
|
Chris@14
|
304 return new TypedReference($this->types[$type], $type);
|
Chris@14
|
305 }
|
Chris@14
|
306
|
Chris@14
|
307 if (!$this->strictMode && isset($this->types[$type])) {
|
Chris@14
|
308 $message = 'Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won\'t be supported in version 4.0.';
|
Chris@14
|
309 if ($aliasSuggestion = $this->getAliasesSuggestionForType($type = $reference->getType(), $deprecationMessage)) {
|
Chris@14
|
310 $message .= ' '.$aliasSuggestion;
|
Chris@14
|
311 } else {
|
Chris@14
|
312 $message .= sprintf(' You should %s the "%s" service to "%s" instead.', isset($this->types[$this->types[$type]]) ? 'alias' : 'rename (or alias)', $this->types[$type], $type);
|
Chris@14
|
313 }
|
Chris@14
|
314
|
Chris@14
|
315 @trigger_error($message, E_USER_DEPRECATED);
|
Chris@14
|
316
|
Chris@14
|
317 return new TypedReference($this->types[$type], $type);
|
Chris@14
|
318 }
|
Chris@14
|
319
|
Chris@14
|
320 if (!$reference->canBeAutoregistered() || isset($this->types[$type]) || isset($this->ambiguousServiceTypes[$type])) {
|
Chris@14
|
321 return;
|
Chris@14
|
322 }
|
Chris@14
|
323
|
Chris@14
|
324 if (isset($this->autowired[$type])) {
|
Chris@14
|
325 return $this->autowired[$type] ? new TypedReference($this->autowired[$type], $type) : null;
|
Chris@14
|
326 }
|
Chris@14
|
327
|
Chris@14
|
328 if (!$this->strictMode) {
|
Chris@14
|
329 return $this->createAutowiredDefinition($type);
|
Chris@14
|
330 }
|
Chris@0
|
331 }
|
Chris@0
|
332
|
Chris@0
|
333 /**
|
Chris@0
|
334 * Populates the list of available types.
|
Chris@0
|
335 */
|
Chris@14
|
336 private function populateAvailableTypes($onlyAutowiringTypes = false)
|
Chris@0
|
337 {
|
Chris@17
|
338 $this->types = [];
|
Chris@14
|
339 if (!$onlyAutowiringTypes) {
|
Chris@17
|
340 $this->ambiguousServiceTypes = [];
|
Chris@14
|
341 }
|
Chris@0
|
342
|
Chris@0
|
343 foreach ($this->container->getDefinitions() as $id => $definition) {
|
Chris@14
|
344 $this->populateAvailableType($id, $definition, $onlyAutowiringTypes);
|
Chris@0
|
345 }
|
Chris@0
|
346 }
|
Chris@0
|
347
|
Chris@0
|
348 /**
|
Chris@0
|
349 * Populates the list of available types for a given definition.
|
Chris@0
|
350 *
|
Chris@0
|
351 * @param string $id
|
Chris@0
|
352 * @param Definition $definition
|
Chris@0
|
353 */
|
Chris@14
|
354 private function populateAvailableType($id, Definition $definition, $onlyAutowiringTypes)
|
Chris@0
|
355 {
|
Chris@0
|
356 // Never use abstract services
|
Chris@0
|
357 if ($definition->isAbstract()) {
|
Chris@0
|
358 return;
|
Chris@0
|
359 }
|
Chris@0
|
360
|
Chris@14
|
361 foreach ($definition->getAutowiringTypes(false) as $type) {
|
Chris@0
|
362 $this->definedTypes[$type] = true;
|
Chris@0
|
363 $this->types[$type] = $id;
|
Chris@0
|
364 unset($this->ambiguousServiceTypes[$type]);
|
Chris@0
|
365 }
|
Chris@0
|
366
|
Chris@14
|
367 if ($onlyAutowiringTypes) {
|
Chris@14
|
368 return;
|
Chris@14
|
369 }
|
Chris@14
|
370
|
Chris@14
|
371 if (preg_match('/^\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id) || $definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) {
|
Chris@0
|
372 return;
|
Chris@0
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
|
Chris@0
|
376 $this->set($reflectionInterface->name, $id);
|
Chris@0
|
377 }
|
Chris@0
|
378
|
Chris@0
|
379 do {
|
Chris@0
|
380 $this->set($reflectionClass->name, $id);
|
Chris@0
|
381 } while ($reflectionClass = $reflectionClass->getParentClass());
|
Chris@0
|
382 }
|
Chris@0
|
383
|
Chris@0
|
384 /**
|
Chris@0
|
385 * Associates a type and a service id if applicable.
|
Chris@0
|
386 *
|
Chris@0
|
387 * @param string $type
|
Chris@0
|
388 * @param string $id
|
Chris@0
|
389 */
|
Chris@0
|
390 private function set($type, $id)
|
Chris@0
|
391 {
|
Chris@0
|
392 if (isset($this->definedTypes[$type])) {
|
Chris@0
|
393 return;
|
Chris@0
|
394 }
|
Chris@0
|
395
|
Chris@0
|
396 // is this already a type/class that is known to match multiple services?
|
Chris@0
|
397 if (isset($this->ambiguousServiceTypes[$type])) {
|
Chris@0
|
398 $this->ambiguousServiceTypes[$type][] = $id;
|
Chris@0
|
399
|
Chris@0
|
400 return;
|
Chris@0
|
401 }
|
Chris@0
|
402
|
Chris@0
|
403 // check to make sure the type doesn't match multiple services
|
Chris@0
|
404 if (!isset($this->types[$type]) || $this->types[$type] === $id) {
|
Chris@0
|
405 $this->types[$type] = $id;
|
Chris@0
|
406
|
Chris@0
|
407 return;
|
Chris@0
|
408 }
|
Chris@0
|
409
|
Chris@0
|
410 // keep an array of all services matching this type
|
Chris@0
|
411 if (!isset($this->ambiguousServiceTypes[$type])) {
|
Chris@17
|
412 $this->ambiguousServiceTypes[$type] = [$this->types[$type]];
|
Chris@0
|
413 unset($this->types[$type]);
|
Chris@0
|
414 }
|
Chris@0
|
415 $this->ambiguousServiceTypes[$type][] = $id;
|
Chris@0
|
416 }
|
Chris@0
|
417
|
Chris@0
|
418 /**
|
Chris@0
|
419 * Registers a definition for the type if possible or throws an exception.
|
Chris@0
|
420 *
|
Chris@14
|
421 * @param string $type
|
Chris@0
|
422 *
|
Chris@14
|
423 * @return TypedReference|null A reference to the registered definition
|
Chris@0
|
424 */
|
Chris@14
|
425 private function createAutowiredDefinition($type)
|
Chris@0
|
426 {
|
Chris@14
|
427 if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) {
|
Chris@14
|
428 return;
|
Chris@0
|
429 }
|
Chris@0
|
430
|
Chris@14
|
431 $currentId = $this->currentId;
|
Chris@14
|
432 $this->currentId = $type;
|
Chris@14
|
433 $this->autowired[$type] = $argumentId = sprintf('autowired.%s', $type);
|
Chris@14
|
434 $argumentDefinition = new Definition($type);
|
Chris@14
|
435 $argumentDefinition->setPublic(false);
|
Chris@14
|
436 $argumentDefinition->setAutowired(true);
|
Chris@14
|
437
|
Chris@14
|
438 try {
|
Chris@14
|
439 $originalThrowSetting = $this->throwOnAutowiringException;
|
Chris@14
|
440 $this->throwOnAutowiringException = true;
|
Chris@14
|
441 $this->processValue($argumentDefinition, true);
|
Chris@14
|
442 $this->container->setDefinition($argumentId, $argumentDefinition);
|
Chris@14
|
443 } catch (AutowiringFailedException $e) {
|
Chris@14
|
444 $this->autowired[$type] = false;
|
Chris@14
|
445 $this->lastFailure = $e->getMessage();
|
Chris@14
|
446 $this->container->log($this, $this->lastFailure);
|
Chris@14
|
447
|
Chris@14
|
448 return;
|
Chris@14
|
449 } finally {
|
Chris@14
|
450 $this->throwOnAutowiringException = $originalThrowSetting;
|
Chris@14
|
451 $this->currentId = $currentId;
|
Chris@0
|
452 }
|
Chris@0
|
453
|
Chris@14
|
454 @trigger_error(sprintf('Relying on service auto-registration for type "%s" is deprecated since Symfony 3.4 and won\'t be supported in 4.0. Create a service named "%s" instead.', $type, $type), E_USER_DEPRECATED);
|
Chris@0
|
455
|
Chris@14
|
456 $this->container->log($this, sprintf('Type "%s" has been auto-registered for service "%s".', $type, $this->currentId));
|
Chris@0
|
457
|
Chris@14
|
458 return new TypedReference($argumentId, $type);
|
Chris@14
|
459 }
|
Chris@14
|
460
|
Chris@14
|
461 private function createTypeNotFoundMessage(TypedReference $reference, $label)
|
Chris@14
|
462 {
|
Chris@17
|
463 $trackResources = $this->container->isTrackingResources();
|
Chris@17
|
464 $this->container->setResourceTracking(false);
|
Chris@17
|
465 try {
|
Chris@17
|
466 if ($r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
|
Chris@17
|
467 $alternatives = $this->createTypeAlternatives($reference);
|
Chris@17
|
468 }
|
Chris@17
|
469 } finally {
|
Chris@17
|
470 $this->container->setResourceTracking($trackResources);
|
Chris@17
|
471 }
|
Chris@17
|
472
|
Chris@17
|
473 if (!$r) {
|
Chris@14
|
474 // either $type does not exist or a parent class does not exist
|
Chris@14
|
475 try {
|
Chris@14
|
476 $resource = new ClassExistenceResource($type, false);
|
Chris@14
|
477 // isFresh() will explode ONLY if a parent class/trait does not exist
|
Chris@14
|
478 $resource->isFresh(0);
|
Chris@14
|
479 $parentMsg = false;
|
Chris@14
|
480 } catch (\ReflectionException $e) {
|
Chris@14
|
481 $parentMsg = $e->getMessage();
|
Chris@14
|
482 }
|
Chris@14
|
483
|
Chris@14
|
484 $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
|
Chris@14
|
485 } else {
|
Chris@14
|
486 $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
|
Chris@14
|
487 $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives);
|
Chris@14
|
488
|
Chris@14
|
489 if ($r->isInterface() && !$alternatives) {
|
Chris@14
|
490 $message .= ' Did you create a class that implements this interface?';
|
Chris@14
|
491 }
|
Chris@0
|
492 }
|
Chris@0
|
493
|
Chris@14
|
494 $message = sprintf('Cannot autowire service "%s": %s %s', $this->currentId, $label, $message);
|
Chris@14
|
495
|
Chris@14
|
496 if (null !== $this->lastFailure) {
|
Chris@14
|
497 $message = $this->lastFailure."\n".$message;
|
Chris@14
|
498 $this->lastFailure = null;
|
Chris@14
|
499 }
|
Chris@14
|
500
|
Chris@14
|
501 return $message;
|
Chris@14
|
502 }
|
Chris@14
|
503
|
Chris@14
|
504 private function createTypeAlternatives(TypedReference $reference)
|
Chris@14
|
505 {
|
Chris@14
|
506 // try suggesting available aliases first
|
Chris@14
|
507 if ($message = $this->getAliasesSuggestionForType($type = $reference->getType())) {
|
Chris@14
|
508 return ' '.$message;
|
Chris@14
|
509 }
|
Chris@14
|
510 if (null === $this->ambiguousServiceTypes) {
|
Chris@14
|
511 $this->populateAvailableTypes();
|
Chris@14
|
512 }
|
Chris@14
|
513
|
Chris@14
|
514 if (isset($this->ambiguousServiceTypes[$type])) {
|
Chris@14
|
515 $message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type]));
|
Chris@14
|
516 } elseif (isset($this->types[$type])) {
|
Chris@14
|
517 $message = sprintf('the existing "%s" service', $this->types[$type]);
|
Chris@14
|
518 } elseif ($reference->getRequiringClass() && !$reference->canBeAutoregistered() && !$this->strictMode) {
|
Chris@14
|
519 return ' It cannot be auto-registered because it is from a different root namespace.';
|
Chris@14
|
520 } else {
|
Chris@14
|
521 return;
|
Chris@14
|
522 }
|
Chris@14
|
523
|
Chris@14
|
524 return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message);
|
Chris@0
|
525 }
|
Chris@0
|
526
|
Chris@0
|
527 /**
|
Chris@14
|
528 * @deprecated since version 3.3, to be removed in 4.0.
|
Chris@0
|
529 */
|
Chris@0
|
530 private static function getResourceMetadataForMethod(\ReflectionMethod $method)
|
Chris@0
|
531 {
|
Chris@17
|
532 $methodArgumentsMetadata = [];
|
Chris@0
|
533 foreach ($method->getParameters() as $parameter) {
|
Chris@0
|
534 try {
|
Chris@0
|
535 $class = $parameter->getClass();
|
Chris@0
|
536 } catch (\ReflectionException $e) {
|
Chris@0
|
537 // type-hint is against a non-existent class
|
Chris@0
|
538 $class = false;
|
Chris@0
|
539 }
|
Chris@0
|
540
|
Chris@0
|
541 $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic();
|
Chris@17
|
542 $methodArgumentsMetadata[] = [
|
Chris@0
|
543 'class' => $class,
|
Chris@0
|
544 'isOptional' => $parameter->isOptional(),
|
Chris@0
|
545 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null,
|
Chris@17
|
546 ];
|
Chris@0
|
547 }
|
Chris@0
|
548
|
Chris@0
|
549 return $methodArgumentsMetadata;
|
Chris@0
|
550 }
|
Chris@14
|
551
|
Chris@14
|
552 private function getAliasesSuggestionForType($type, $extraContext = null)
|
Chris@14
|
553 {
|
Chris@17
|
554 $aliases = [];
|
Chris@14
|
555 foreach (class_parents($type) + class_implements($type) as $parent) {
|
Chris@14
|
556 if ($this->container->has($parent) && !$this->container->findDefinition($parent)->isAbstract()) {
|
Chris@14
|
557 $aliases[] = $parent;
|
Chris@14
|
558 }
|
Chris@14
|
559 }
|
Chris@14
|
560
|
Chris@14
|
561 $extraContext = $extraContext ? ' '.$extraContext : '';
|
Chris@17
|
562 if (1 < $len = \count($aliases)) {
|
Chris@14
|
563 $message = sprintf('Try changing the type-hint%s to one of its parents: ', $extraContext);
|
Chris@14
|
564 for ($i = 0, --$len; $i < $len; ++$i) {
|
Chris@14
|
565 $message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
|
Chris@14
|
566 }
|
Chris@14
|
567 $message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
|
Chris@14
|
568
|
Chris@14
|
569 return $message;
|
Chris@14
|
570 }
|
Chris@14
|
571
|
Chris@14
|
572 if ($aliases) {
|
Chris@14
|
573 return sprintf('Try changing the type-hint%s to "%s" instead.', $extraContext, $aliases[0]);
|
Chris@14
|
574 }
|
Chris@14
|
575 }
|
Chris@0
|
576 }
|