annotate vendor/symfony/dependency-injection/Compiler/AutowirePass.php @ 2:92f882872392

Trusted hosts, + remove migration modules
author Chris Cannam
date Tue, 05 Dec 2017 09:26:43 +0000
parents 4c8ae668cc8c
children 1fec387a4317
rev   line source
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@0 14 use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
Chris@0 15 use Symfony\Component\DependencyInjection\ContainerBuilder;
Chris@0 16 use Symfony\Component\DependencyInjection\Definition;
Chris@0 17 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
Chris@0 18 use Symfony\Component\DependencyInjection\Reference;
Chris@0 19
Chris@0 20 /**
Chris@0 21 * Guesses constructor arguments of services definitions and try to instantiate services if necessary.
Chris@0 22 *
Chris@0 23 * @author Kévin Dunglas <dunglas@gmail.com>
Chris@0 24 */
Chris@0 25 class AutowirePass implements CompilerPassInterface
Chris@0 26 {
Chris@0 27 private $container;
Chris@0 28 private $reflectionClasses = array();
Chris@0 29 private $definedTypes = array();
Chris@0 30 private $types;
Chris@0 31 private $ambiguousServiceTypes = array();
Chris@0 32 private $autowired = array();
Chris@0 33
Chris@0 34 /**
Chris@0 35 * {@inheritdoc}
Chris@0 36 */
Chris@0 37 public function process(ContainerBuilder $container)
Chris@0 38 {
Chris@0 39 $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); };
Chris@0 40 spl_autoload_register($throwingAutoloader);
Chris@0 41
Chris@0 42 try {
Chris@0 43 $this->container = $container;
Chris@0 44 foreach ($container->getDefinitions() as $id => $definition) {
Chris@0 45 if ($definition->isAutowired()) {
Chris@0 46 $this->completeDefinition($id, $definition);
Chris@0 47 }
Chris@0 48 }
Chris@0 49 } finally {
Chris@0 50 spl_autoload_unregister($throwingAutoloader);
Chris@0 51
Chris@0 52 // Free memory and remove circular reference to container
Chris@0 53 $this->reflectionClasses = array();
Chris@0 54 $this->definedTypes = array();
Chris@0 55 $this->types = null;
Chris@0 56 $this->ambiguousServiceTypes = array();
Chris@0 57 $this->autowired = array();
Chris@0 58 }
Chris@0 59 }
Chris@0 60
Chris@0 61 /**
Chris@0 62 * Creates a resource to help know if this service has changed.
Chris@0 63 *
Chris@0 64 * @param \ReflectionClass $reflectionClass
Chris@0 65 *
Chris@0 66 * @return AutowireServiceResource
Chris@0 67 */
Chris@0 68 public static function createResourceForClass(\ReflectionClass $reflectionClass)
Chris@0 69 {
Chris@0 70 $metadata = array();
Chris@0 71
Chris@0 72 if ($constructor = $reflectionClass->getConstructor()) {
Chris@0 73 $metadata['__construct'] = self::getResourceMetadataForMethod($constructor);
Chris@0 74 }
Chris@0 75
Chris@0 76 foreach (self::getSetters($reflectionClass) as $reflectionMethod) {
Chris@0 77 $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
Chris@0 78 }
Chris@0 79
Chris@0 80 return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
Chris@0 81 }
Chris@0 82
Chris@0 83 /**
Chris@0 84 * Wires the given definition.
Chris@0 85 *
Chris@0 86 * @param string $id
Chris@0 87 * @param Definition $definition
Chris@0 88 *
Chris@0 89 * @throws RuntimeException
Chris@0 90 */
Chris@0 91 private function completeDefinition($id, Definition $definition)
Chris@0 92 {
Chris@0 93 if ($definition->getFactory()) {
Chris@0 94 throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $id));
Chris@0 95 }
Chris@0 96
Chris@0 97 if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
Chris@0 98 return;
Chris@0 99 }
Chris@0 100
Chris@0 101 if ($this->container->isTrackingResources()) {
Chris@0 102 $this->container->addResource(static::createResourceForClass($reflectionClass));
Chris@0 103 }
Chris@0 104
Chris@0 105 if (!$constructor = $reflectionClass->getConstructor()) {
Chris@0 106 return;
Chris@0 107 }
Chris@0 108 $parameters = $constructor->getParameters();
Chris@0 109 if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) {
Chris@0 110 array_pop($parameters);
Chris@0 111 }
Chris@0 112
Chris@0 113 $arguments = $definition->getArguments();
Chris@0 114 foreach ($parameters as $index => $parameter) {
Chris@0 115 if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
Chris@0 116 continue;
Chris@0 117 }
Chris@0 118
Chris@0 119 try {
Chris@0 120 if (!$typeHint = $parameter->getClass()) {
Chris@0 121 if (isset($arguments[$index])) {
Chris@0 122 continue;
Chris@0 123 }
Chris@0 124
Chris@0 125 // no default value? Then fail
Chris@0 126 if (!$parameter->isOptional()) {
Chris@0 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));
Chris@0 128 }
Chris@0 129
Chris@0 130 // specifically pass the default value
Chris@0 131 $arguments[$index] = $parameter->getDefaultValue();
Chris@0 132
Chris@0 133 continue;
Chris@0 134 }
Chris@0 135
Chris@0 136 if (isset($this->autowired[$typeHint->name])) {
Chris@0 137 $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null;
Chris@0 138 continue;
Chris@0 139 }
Chris@0 140
Chris@0 141 if (null === $this->types) {
Chris@0 142 $this->populateAvailableTypes();
Chris@0 143 }
Chris@0 144
Chris@0 145 if (isset($this->types[$typeHint->name])) {
Chris@0 146 $value = new Reference($this->types[$typeHint->name]);
Chris@0 147 } else {
Chris@0 148 try {
Chris@0 149 $value = $this->createAutowiredDefinition($typeHint, $id);
Chris@0 150 } catch (RuntimeException $e) {
Chris@0 151 if ($parameter->isDefaultValueAvailable()) {
Chris@0 152 $value = $parameter->getDefaultValue();
Chris@0 153 } elseif ($parameter->allowsNull()) {
Chris@0 154 $value = null;
Chris@0 155 } else {
Chris@0 156 throw $e;
Chris@0 157 }
Chris@0 158 $this->autowired[$typeHint->name] = false;
Chris@0 159 }
Chris@0 160 }
Chris@0 161 } catch (\ReflectionException $e) {
Chris@0 162 // Typehint against a non-existing class
Chris@0 163
Chris@0 164 if (!$parameter->isDefaultValueAvailable()) {
Chris@0 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);
Chris@0 166 }
Chris@0 167
Chris@0 168 $value = $parameter->getDefaultValue();
Chris@0 169 }
Chris@0 170
Chris@0 171 $arguments[$index] = $value;
Chris@0 172 }
Chris@0 173
Chris@0 174 if ($parameters && !isset($arguments[++$index])) {
Chris@0 175 while (0 <= --$index) {
Chris@0 176 $parameter = $parameters[$index];
Chris@0 177 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
Chris@0 178 break;
Chris@0 179 }
Chris@0 180 unset($arguments[$index]);
Chris@0 181 }
Chris@0 182 }
Chris@0 183
Chris@0 184 // it's possible index 1 was set, then index 0, then 2, etc
Chris@0 185 // make sure that we re-order so they're injected as expected
Chris@0 186 ksort($arguments);
Chris@0 187 $definition->setArguments($arguments);
Chris@0 188 }
Chris@0 189
Chris@0 190 /**
Chris@0 191 * Populates the list of available types.
Chris@0 192 */
Chris@0 193 private function populateAvailableTypes()
Chris@0 194 {
Chris@0 195 $this->types = array();
Chris@0 196
Chris@0 197 foreach ($this->container->getDefinitions() as $id => $definition) {
Chris@0 198 $this->populateAvailableType($id, $definition);
Chris@0 199 }
Chris@0 200 }
Chris@0 201
Chris@0 202 /**
Chris@0 203 * Populates the list of available types for a given definition.
Chris@0 204 *
Chris@0 205 * @param string $id
Chris@0 206 * @param Definition $definition
Chris@0 207 */
Chris@0 208 private function populateAvailableType($id, Definition $definition)
Chris@0 209 {
Chris@0 210 // Never use abstract services
Chris@0 211 if ($definition->isAbstract()) {
Chris@0 212 return;
Chris@0 213 }
Chris@0 214
Chris@0 215 foreach ($definition->getAutowiringTypes() as $type) {
Chris@0 216 $this->definedTypes[$type] = true;
Chris@0 217 $this->types[$type] = $id;
Chris@0 218 unset($this->ambiguousServiceTypes[$type]);
Chris@0 219 }
Chris@0 220
Chris@0 221 if (!$reflectionClass = $this->getReflectionClass($id, $definition)) {
Chris@0 222 return;
Chris@0 223 }
Chris@0 224
Chris@0 225 foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
Chris@0 226 $this->set($reflectionInterface->name, $id);
Chris@0 227 }
Chris@0 228
Chris@0 229 do {
Chris@0 230 $this->set($reflectionClass->name, $id);
Chris@0 231 } while ($reflectionClass = $reflectionClass->getParentClass());
Chris@0 232 }
Chris@0 233
Chris@0 234 /**
Chris@0 235 * Associates a type and a service id if applicable.
Chris@0 236 *
Chris@0 237 * @param string $type
Chris@0 238 * @param string $id
Chris@0 239 */
Chris@0 240 private function set($type, $id)
Chris@0 241 {
Chris@0 242 if (isset($this->definedTypes[$type])) {
Chris@0 243 return;
Chris@0 244 }
Chris@0 245
Chris@0 246 // is this already a type/class that is known to match multiple services?
Chris@0 247 if (isset($this->ambiguousServiceTypes[$type])) {
Chris@0 248 $this->ambiguousServiceTypes[$type][] = $id;
Chris@0 249
Chris@0 250 return;
Chris@0 251 }
Chris@0 252
Chris@0 253 // check to make sure the type doesn't match multiple services
Chris@0 254 if (!isset($this->types[$type]) || $this->types[$type] === $id) {
Chris@0 255 $this->types[$type] = $id;
Chris@0 256
Chris@0 257 return;
Chris@0 258 }
Chris@0 259
Chris@0 260 // keep an array of all services matching this type
Chris@0 261 if (!isset($this->ambiguousServiceTypes[$type])) {
Chris@0 262 $this->ambiguousServiceTypes[$type] = array($this->types[$type]);
Chris@0 263 unset($this->types[$type]);
Chris@0 264 }
Chris@0 265 $this->ambiguousServiceTypes[$type][] = $id;
Chris@0 266 }
Chris@0 267
Chris@0 268 /**
Chris@0 269 * Registers a definition for the type if possible or throws an exception.
Chris@0 270 *
Chris@0 271 * @param \ReflectionClass $typeHint
Chris@0 272 * @param string $id
Chris@0 273 *
Chris@0 274 * @return Reference A reference to the registered definition
Chris@0 275 *
Chris@0 276 * @throws RuntimeException
Chris@0 277 */
Chris@0 278 private function createAutowiredDefinition(\ReflectionClass $typeHint, $id)
Chris@0 279 {
Chris@0 280 if (isset($this->ambiguousServiceTypes[$typeHint->name])) {
Chris@0 281 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
Chris@0 282 $matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]);
Chris@0 283
Chris@0 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));
Chris@0 285 }
Chris@0 286
Chris@0 287 if (!$typeHint->isInstantiable()) {
Chris@0 288 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
Chris@0 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));
Chris@0 290 }
Chris@0 291
Chris@0 292 $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name);
Chris@0 293
Chris@0 294 $argumentDefinition = $this->container->register($argumentId, $typeHint->name);
Chris@0 295 $argumentDefinition->setPublic(false);
Chris@0 296
Chris@0 297 try {
Chris@0 298 $this->completeDefinition($argumentId, $argumentDefinition);
Chris@0 299 } catch (RuntimeException $e) {
Chris@0 300 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class';
Chris@0 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);
Chris@0 302 throw new RuntimeException($message, 0, $e);
Chris@0 303 }
Chris@0 304
Chris@0 305 return new Reference($argumentId);
Chris@0 306 }
Chris@0 307
Chris@0 308 /**
Chris@0 309 * Retrieves the reflection class associated with the given service.
Chris@0 310 *
Chris@0 311 * @param string $id
Chris@0 312 * @param Definition $definition
Chris@0 313 *
Chris@0 314 * @return \ReflectionClass|false
Chris@0 315 */
Chris@0 316 private function getReflectionClass($id, Definition $definition)
Chris@0 317 {
Chris@0 318 if (isset($this->reflectionClasses[$id])) {
Chris@0 319 return $this->reflectionClasses[$id];
Chris@0 320 }
Chris@0 321
Chris@0 322 // Cannot use reflection if the class isn't set
Chris@0 323 if (!$class = $definition->getClass()) {
Chris@0 324 return false;
Chris@0 325 }
Chris@0 326
Chris@0 327 $class = $this->container->getParameterBag()->resolveValue($class);
Chris@0 328
Chris@0 329 if ($deprecated = $definition->isDeprecated()) {
Chris@0 330 $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
Chris@0 331 return (E_USER_DEPRECATED === $level || !$prevErrorHandler) ? false : $prevErrorHandler($level, $message, $file, $line);
Chris@0 332 });
Chris@0 333 }
Chris@0 334
Chris@0 335 $e = null;
Chris@0 336
Chris@0 337 try {
Chris@0 338 $reflector = new \ReflectionClass($class);
Chris@0 339 } catch (\Exception $e) {
Chris@0 340 } catch (\Throwable $e) {
Chris@0 341 }
Chris@0 342
Chris@0 343 if ($deprecated) {
Chris@0 344 restore_error_handler();
Chris@0 345 }
Chris@0 346
Chris@0 347 if (null !== $e) {
Chris@0 348 if (!$e instanceof \ReflectionException) {
Chris@0 349 throw $e;
Chris@0 350 }
Chris@0 351 $reflector = false;
Chris@0 352 }
Chris@0 353
Chris@0 354 return $this->reflectionClasses[$id] = $reflector;
Chris@0 355 }
Chris@0 356
Chris@0 357 /**
Chris@0 358 * @param \ReflectionClass $reflectionClass
Chris@0 359 *
Chris@0 360 * @return \ReflectionMethod[]
Chris@0 361 */
Chris@0 362 private static function getSetters(\ReflectionClass $reflectionClass)
Chris@0 363 {
Chris@0 364 foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
Chris@0 365 if (!$reflectionMethod->isStatic() && 1 === $reflectionMethod->getNumberOfParameters() && 0 === strpos($reflectionMethod->name, 'set')) {
Chris@0 366 yield $reflectionMethod;
Chris@0 367 }
Chris@0 368 }
Chris@0 369 }
Chris@0 370
Chris@0 371 private static function getResourceMetadataForMethod(\ReflectionMethod $method)
Chris@0 372 {
Chris@0 373 $methodArgumentsMetadata = array();
Chris@0 374 foreach ($method->getParameters() as $parameter) {
Chris@0 375 try {
Chris@0 376 $class = $parameter->getClass();
Chris@0 377 } catch (\ReflectionException $e) {
Chris@0 378 // type-hint is against a non-existent class
Chris@0 379 $class = false;
Chris@0 380 }
Chris@0 381
Chris@0 382 $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic();
Chris@0 383 $methodArgumentsMetadata[] = array(
Chris@0 384 'class' => $class,
Chris@0 385 'isOptional' => $parameter->isOptional(),
Chris@0 386 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null,
Chris@0 387 );
Chris@0 388 }
Chris@0 389
Chris@0 390 return $methodArgumentsMetadata;
Chris@0 391 }
Chris@0 392 }