comparison vendor/symfony/dependency-injection/Compiler/AutowirePass.php @ 14:1fec387a4317

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