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