[ Index ]

PHP Cross Reference of phpBB-3.3.2-deutsch

title

Body

[close]

/vendor/symfony/dependency-injection/Compiler/ -> AutowirePass.php (source)

   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  }


Generated: Wed Nov 11 20:28:18 2020 Cross-referenced by PHPXref 0.7.1