Update website

This commit is contained in:
Guilhem Lavaux 2024-11-19 08:02:04 +01:00
parent 4413528994
commit 1d90fbf296
6865 changed files with 1091082 additions and 0 deletions

View file

@ -0,0 +1,242 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
abstract class AbstractRecursivePass implements CompilerPassInterface
{
/**
* @var ContainerBuilder
*/
protected $container;
protected $currentId;
private $processExpressions = false;
private $expressionLanguage;
private $inExpression = false;
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
try {
$this->processValue($container->getDefinitions(), true);
} finally {
$this->container = null;
}
}
protected function enableExpressionProcessing()
{
$this->processExpressions = true;
}
protected function inExpression(bool $reset = true): bool
{
$inExpression = $this->inExpression;
if ($reset) {
$this->inExpression = false;
}
return $inExpression;
}
/**
* Processes a value found in a definition tree.
*
* @param mixed $value
*
* @return mixed
*/
protected function processValue($value, bool $isRoot = false)
{
if (\is_array($value)) {
foreach ($value as $k => $v) {
if ($isRoot) {
$this->currentId = $k;
}
if ($v !== $processedValue = $this->processValue($v, $isRoot)) {
$value[$k] = $processedValue;
}
}
} elseif ($value instanceof ArgumentInterface) {
$value->setValues($this->processValue($value->getValues()));
} elseif ($value instanceof Expression && $this->processExpressions) {
$this->getExpressionLanguage()->compile((string) $value, ['this' => 'container']);
} elseif ($value instanceof Definition) {
$value->setArguments($this->processValue($value->getArguments()));
$value->setProperties($this->processValue($value->getProperties()));
$value->setMethodCalls($this->processValue($value->getMethodCalls()));
$changes = $value->getChanges();
if (isset($changes['factory'])) {
$value->setFactory($this->processValue($value->getFactory()));
}
if (isset($changes['configurator'])) {
$value->setConfigurator($this->processValue($value->getConfigurator()));
}
}
return $value;
}
/**
* @return \ReflectionFunctionAbstract|null
*
* @throws RuntimeException
*/
protected function getConstructor(Definition $definition, bool $required)
{
if ($definition->isSynthetic()) {
return null;
}
if (\is_string($factory = $definition->getFactory())) {
if (!\function_exists($factory)) {
throw new RuntimeException(sprintf('Invalid service "%s": function "%s" does not exist.', $this->currentId, $factory));
}
$r = new \ReflectionFunction($factory);
if (false !== $r->getFileName() && file_exists($r->getFileName())) {
$this->container->fileExists($r->getFileName());
}
return $r;
}
if ($factory) {
[$class, $method] = $factory;
if ('__construct' === $method) {
throw new RuntimeException(sprintf('Invalid service "%s": "__construct()" cannot be used as a factory method.', $this->currentId));
}
if ($class instanceof Reference) {
$factoryDefinition = $this->container->findDefinition((string) $class);
while ((null === $class = $factoryDefinition->getClass()) && $factoryDefinition instanceof ChildDefinition) {
$factoryDefinition = $this->container->findDefinition($factoryDefinition->getParent());
}
} elseif ($class instanceof Definition) {
$class = $class->getClass();
} elseif (null === $class) {
$class = $definition->getClass();
}
return $this->getReflectionMethod(new Definition($class), $method);
}
while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) {
$definition = $this->container->findDefinition($definition->getParent());
}
try {
if (!$r = $this->container->getReflectionClass($class)) {
if (null === $class) {
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
}
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
} catch (\ReflectionException $e) {
throw new RuntimeException(sprintf('Invalid service "%s": ', $this->currentId).lcfirst($e->getMessage()));
}
if (!$r = $r->getConstructor()) {
if ($required) {
throw new RuntimeException(sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, sprintf($class !== $this->currentId ? ' "%s"' : '', $class)));
}
} elseif (!$r->isPublic()) {
throw new RuntimeException(sprintf('Invalid service "%s": ', $this->currentId).sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class).' must be public.');
}
return $r;
}
/**
* @throws RuntimeException
*
* @return \ReflectionFunctionAbstract
*/
protected function getReflectionMethod(Definition $definition, string $method)
{
if ('__construct' === $method) {
return $this->getConstructor($definition, true);
}
while ((null === $class = $definition->getClass()) && $definition instanceof ChildDefinition) {
$definition = $this->container->findDefinition($definition->getParent());
}
if (null === $class) {
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $this->currentId));
}
if (!$r = $this->container->getReflectionClass($class)) {
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $this->currentId, $class));
}
if (!$r->hasMethod($method)) {
if ($r->hasMethod('__call') && ($r = $r->getMethod('__call')) && $r->isPublic()) {
return new \ReflectionMethod(static function (...$arguments) {}, '__invoke');
}
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
$r = $r->getMethod($method);
if (!$r->isPublic()) {
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
return $r;
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
if (!class_exists(ExpressionLanguage::class)) {
throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
}
$providers = $this->container->getExpressionLanguageProviders();
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function (string $arg): string {
if ('""' === substr_replace($arg, '', 1, -1)) {
$id = stripcslashes(substr($arg, 1, -1));
$this->inExpression = true;
$arg = $this->processValue(new Reference($id));
$this->inExpression = false;
if (!$arg instanceof Reference) {
throw new RuntimeException(sprintf('"%s::processValue()" must return a Reference when processing an expression, "%s" returned for service("%s").', static::class, get_debug_type($arg), $id));
}
$arg = sprintf('"%s"', $arg);
}
return sprintf('$this->get(%s)', $arg);
});
}
return $this->expressionLanguage;
}
}

View file

@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass
{
private $tagName;
private $aliases = [];
public function __construct(string $tagName = 'container.private')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) {
return new Reference($this->aliases[$id], $value->getInvalidBehavior());
}
return parent::processValue($value, $isRoot);
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
if (null === $package = $tags[0]['package'] ?? null) {
throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
}
if (null === $version = $tags[0]['version'] ?? null) {
throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
}
$definition = $container->getDefinition($id);
if (!$definition->isPublic() || $definition->isPrivate()) {
continue;
}
$container
->setAlias($id, $aliasId = '.'.$this->tagName.'.'.$id)
->setPublic(true)
->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.');
$container->setDefinition($aliasId, $definition);
$this->aliases[$id] = $aliasId;
}
parent::process($container);
}
}

View file

@ -0,0 +1,191 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Run this pass before passes that need to know more about the relation of
* your services.
*
* This class will populate the ServiceReferenceGraph with information. You can
* retrieve the graph in other passes from the compiler.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class AnalyzeServiceReferencesPass extends AbstractRecursivePass
{
private $graph;
private $currentDefinition;
private $onlyConstructorArguments;
private $hasProxyDumper;
private $lazy;
private $byConstructor;
private $byFactory;
private $definitions;
private $aliases;
/**
* @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls
*/
public function __construct(bool $onlyConstructorArguments = false, bool $hasProxyDumper = true)
{
$this->onlyConstructorArguments = $onlyConstructorArguments;
$this->hasProxyDumper = $hasProxyDumper;
$this->enableExpressionProcessing();
}
/**
* Processes a ContainerBuilder object to populate the service reference graph.
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->graph = $container->getCompiler()->getServiceReferenceGraph();
$this->graph->clear();
$this->lazy = false;
$this->byConstructor = false;
$this->byFactory = false;
$this->definitions = $container->getDefinitions();
$this->aliases = $container->getAliases();
foreach ($this->aliases as $id => $alias) {
$targetId = $this->getDefinitionId((string) $alias);
$this->graph->connect($id, $alias, $targetId, null !== $targetId ? $this->container->getDefinition($targetId) : null, null);
}
try {
parent::process($container);
} finally {
$this->aliases = $this->definitions = [];
}
}
protected function processValue($value, bool $isRoot = false)
{
$lazy = $this->lazy;
$inExpression = $this->inExpression();
if ($value instanceof ArgumentInterface) {
$this->lazy = !$this->byFactory || !$value instanceof IteratorArgument;
parent::processValue($value->getValues());
$this->lazy = $lazy;
return $value;
}
if ($value instanceof Reference) {
$targetId = $this->getDefinitionId((string) $value);
$targetDefinition = null !== $targetId ? $this->container->getDefinition($targetId) : null;
$this->graph->connect(
$this->currentId,
$this->currentDefinition,
$targetId,
$targetDefinition,
$value,
$this->lazy || ($this->hasProxyDumper && $targetDefinition && $targetDefinition->isLazy()),
ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior(),
$this->byConstructor
);
if ($inExpression) {
$this->graph->connect(
'.internal.reference_in_expression',
null,
$targetId,
$targetDefinition,
$value,
$this->lazy || ($targetDefinition && $targetDefinition->isLazy()),
true
);
}
return $value;
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if ($isRoot) {
if ($value->isSynthetic() || $value->isAbstract()) {
return $value;
}
$this->currentDefinition = $value;
} elseif ($this->currentDefinition === $value) {
return $value;
}
$this->lazy = false;
$byConstructor = $this->byConstructor;
$this->byConstructor = $isRoot || $byConstructor;
$byFactory = $this->byFactory;
$this->byFactory = true;
$this->processValue($value->getFactory());
$this->byFactory = $byFactory;
$this->processValue($value->getArguments());
$properties = $value->getProperties();
$setters = $value->getMethodCalls();
// Any references before a "wither" are part of the constructor-instantiation graph
$lastWitherIndex = null;
foreach ($setters as $k => $call) {
if ($call[2] ?? false) {
$lastWitherIndex = $k;
}
}
if (null !== $lastWitherIndex) {
$this->processValue($properties);
$setters = $properties = [];
foreach ($value->getMethodCalls() as $k => $call) {
if (null === $lastWitherIndex) {
$setters[] = $call;
continue;
}
if ($lastWitherIndex === $k) {
$lastWitherIndex = null;
}
$this->processValue($call);
}
}
$this->byConstructor = $byConstructor;
if (!$this->onlyConstructorArguments) {
$this->processValue($properties);
$this->processValue($setters);
$this->processValue($value->getConfigurator());
}
$this->lazy = $lazy;
return $value;
}
private function getDefinitionId(string $id): ?string
{
while (isset($this->aliases[$id])) {
$id = (string) $this->aliases[$id];
}
return isset($this->definitions[$id]) ? $id : null;
}
}

View file

@ -0,0 +1,168 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @author Alexander M. Turek <me@derrabus.de>
*/
final class AttributeAutoconfigurationPass extends AbstractRecursivePass
{
private $classAttributeConfigurators = [];
private $methodAttributeConfigurators = [];
private $propertyAttributeConfigurators = [];
private $parameterAttributeConfigurators = [];
public function process(ContainerBuilder $container): void
{
if (80000 > \PHP_VERSION_ID || !$container->getAutoconfiguredAttributes()) {
return;
}
foreach ($container->getAutoconfiguredAttributes() as $attributeName => $callable) {
$callableReflector = new \ReflectionFunction(\Closure::fromCallable($callable));
if ($callableReflector->getNumberOfParameters() <= 2) {
$this->classAttributeConfigurators[$attributeName] = $callable;
continue;
}
$reflectorParameter = $callableReflector->getParameters()[2];
$parameterType = $reflectorParameter->getType();
$types = [];
if ($parameterType instanceof \ReflectionUnionType) {
foreach ($parameterType->getTypes() as $type) {
$types[] = $type->getName();
}
} elseif ($parameterType instanceof \ReflectionNamedType) {
$types[] = $parameterType->getName();
} else {
throw new LogicException(sprintf('Argument "$%s" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in "%s" on line "%d".', $reflectorParameter->getName(), $callableReflector->getFileName(), $callableReflector->getStartLine()));
}
try {
$attributeReflector = new \ReflectionClass($attributeName);
} catch (\ReflectionException $e) {
continue;
}
$targets = $attributeReflector->getAttributes(\Attribute::class)[0] ?? 0;
$targets = $targets ? $targets->getArguments()[0] ?? -1 : 0;
foreach (['class', 'method', 'property', 'parameter'] as $symbol) {
if (['Reflector'] !== $types) {
if (!\in_array('Reflection'.ucfirst($symbol), $types, true)) {
continue;
}
if (!($targets & \constant('Attribute::TARGET_'.strtoupper($symbol)))) {
throw new LogicException(sprintf('Invalid type "Reflection%s" on argument "$%s": attribute "%s" cannot target a '.$symbol.' in "%s" on line "%d".', ucfirst($symbol), $reflectorParameter->getName(), $attributeName, $callableReflector->getFileName(), $callableReflector->getStartLine()));
}
}
$this->{$symbol.'AttributeConfigurators'}[$attributeName] = $callable;
}
}
parent::process($container);
}
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition
|| !$value->isAutoconfigured()
|| $value->isAbstract()
|| $value->hasTag('container.ignore_attributes')
|| !($classReflector = $this->container->getReflectionClass($value->getClass(), false))
) {
return parent::processValue($value, $isRoot);
}
$instanceof = $value->getInstanceofConditionals();
$conditionals = $instanceof[$classReflector->getName()] ?? new ChildDefinition('');
if ($this->classAttributeConfigurators) {
foreach ($classReflector->getAttributes() as $attribute) {
if ($configurator = $this->classAttributeConfigurators[$attribute->getName()] ?? null) {
$configurator($conditionals, $attribute->newInstance(), $classReflector);
}
}
}
if ($this->parameterAttributeConfigurators) {
try {
$constructorReflector = $this->getConstructor($value, false);
} catch (RuntimeException $e) {
$constructorReflector = null;
}
if ($constructorReflector) {
foreach ($constructorReflector->getParameters() as $parameterReflector) {
foreach ($parameterReflector->getAttributes() as $attribute) {
if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) {
$configurator($conditionals, $attribute->newInstance(), $parameterReflector);
}
}
}
}
}
if ($this->methodAttributeConfigurators || $this->parameterAttributeConfigurators) {
foreach ($classReflector->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflector) {
if ($methodReflector->isStatic() || $methodReflector->isConstructor() || $methodReflector->isDestructor()) {
continue;
}
if ($this->methodAttributeConfigurators) {
foreach ($methodReflector->getAttributes() as $attribute) {
if ($configurator = $this->methodAttributeConfigurators[$attribute->getName()] ?? null) {
$configurator($conditionals, $attribute->newInstance(), $methodReflector);
}
}
}
if ($this->parameterAttributeConfigurators) {
foreach ($methodReflector->getParameters() as $parameterReflector) {
foreach ($parameterReflector->getAttributes() as $attribute) {
if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) {
$configurator($conditionals, $attribute->newInstance(), $parameterReflector);
}
}
}
}
}
}
if ($this->propertyAttributeConfigurators) {
foreach ($classReflector->getProperties(\ReflectionProperty::IS_PUBLIC) as $propertyReflector) {
if ($propertyReflector->isStatic()) {
continue;
}
foreach ($propertyReflector->getAttributes() as $attribute) {
if ($configurator = $this->propertyAttributeConfigurators[$attribute->getName()] ?? null) {
$configurator($conditionals, $attribute->newInstance(), $propertyReflector);
}
}
}
}
if (!isset($instanceof[$classReflector->getName()]) && new ChildDefinition('') != $conditionals) {
$instanceof[$classReflector->getName()] = $conditionals;
$value->setInstanceofConditionals($instanceof);
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* Sets a service to be an alias of another one, given a format pattern.
*/
class AutoAliasServicePass implements CompilerPassInterface
{
private $privateAliases = [];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->findTaggedServiceIds('auto_alias') as $serviceId => $tags) {
foreach ($tags as $tag) {
if (!isset($tag['format'])) {
throw new InvalidArgumentException(sprintf('Missing tag information "format" on auto_alias service "%s".', $serviceId));
}
$aliasId = $container->getParameterBag()->resolveValue($tag['format']);
if ($container->hasDefinition($aliasId) || $container->hasAlias($aliasId)) {
$alias = new Alias($aliasId, $container->getDefinition($serviceId)->isPublic());
$container->setAlias($serviceId, $alias);
if (!$alias->isPublic()) {
$alias->setPublic(true);
$this->privateAliases[] = $alias;
}
}
}
}
}
/**
* @internal to be removed in Symfony 6.0
*/
public function getPrivateAliases(): array
{
$privateAliases = $this->privateAliases;
$this->privateAliases = [];
return $privateAliases;
}
}

View file

@ -0,0 +1,629 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class AutowirePass extends AbstractRecursivePass
{
private $types;
private $ambiguousServiceTypes;
private $autowiringAliases;
private $lastFailure;
private $throwOnAutowiringException;
private $decoratedClass;
private $decoratedId;
private $methodCalls;
private $defaultArgument;
private $getPreviousValue;
private $decoratedMethodIndex;
private $decoratedMethodArgumentIndex;
private $typesClone;
private $combinedAliases;
public function __construct(bool $throwOnAutowireException = true)
{
$this->throwOnAutowiringException = $throwOnAutowireException;
$this->defaultArgument = new class() {
public $value;
public $names;
};
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->populateCombinedAliases($container);
try {
$this->typesClone = clone $this;
parent::process($container);
} finally {
$this->decoratedClass = null;
$this->decoratedId = null;
$this->methodCalls = null;
$this->defaultArgument->names = null;
$this->getPreviousValue = null;
$this->decoratedMethodIndex = null;
$this->decoratedMethodArgumentIndex = null;
$this->typesClone = null;
$this->combinedAliases = [];
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
try {
return $this->doProcessValue($value, $isRoot);
} catch (AutowiringFailedException $e) {
if ($this->throwOnAutowiringException) {
throw $e;
}
$this->container->getDefinition($this->currentId)->addError($e->getMessageCallback() ?? $e->getMessage());
return parent::processValue($value, $isRoot);
}
}
/**
* @return mixed
*/
private function doProcessValue($value, bool $isRoot = false)
{
if ($value instanceof TypedReference) {
if ($ref = $this->getAutowiredReference($value, true)) {
return $ref;
}
if (ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
$message = $this->createTypeNotFoundMessageCallback($value, 'it');
// since the error message varies by referenced id and $this->currentId, so should the id of the dummy errored definition
$this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, (string) $value), $value->getType())
->addError($message);
return new TypedReference($id, $value->getType(), $value->getInvalidBehavior(), $value->getName());
}
}
$value = parent::processValue($value, $isRoot);
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
$this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
return $value;
}
$this->methodCalls = $value->getMethodCalls();
try {
$constructor = $this->getConstructor($value, false);
} catch (RuntimeException $e) {
throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
}
if ($constructor) {
array_unshift($this->methodCalls, [$constructor, $value->getArguments()]);
}
$checkAttributes = 80000 <= \PHP_VERSION_ID && !$value->hasTag('container.ignore_attributes');
$this->methodCalls = $this->autowireCalls($reflectionClass, $isRoot, $checkAttributes);
if ($constructor) {
[, $arguments] = array_shift($this->methodCalls);
if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
}
if ($this->methodCalls !== $value->getMethodCalls()) {
$value->setMethodCalls($this->methodCalls);
}
return $value;
}
private function autowireCalls(\ReflectionClass $reflectionClass, bool $isRoot, bool $checkAttributes): array
{
$this->decoratedId = null;
$this->decoratedClass = null;
$this->getPreviousValue = null;
if ($isRoot && ($definition = $this->container->getDefinition($this->currentId)) && null !== ($this->decoratedId = $definition->innerServiceId) && $this->container->has($this->decoratedId)) {
$this->decoratedClass = $this->container->findDefinition($this->decoratedId)->getClass();
}
$patchedIndexes = [];
foreach ($this->methodCalls as $i => $call) {
[$method, $arguments] = $call;
if ($method instanceof \ReflectionFunctionAbstract) {
$reflectionMethod = $method;
} else {
$definition = new Definition($reflectionClass->name);
try {
$reflectionMethod = $this->getReflectionMethod($definition, $method);
} catch (RuntimeException $e) {
if ($definition->getFactory()) {
continue;
}
throw $e;
}
}
$arguments = $this->autowireMethod($reflectionMethod, $arguments, $checkAttributes, $i);
if ($arguments !== $call[1]) {
$this->methodCalls[$i][1] = $arguments;
$patchedIndexes[] = $i;
}
}
// use named arguments to skip complex default values
foreach ($patchedIndexes as $i) {
$namedArguments = null;
$arguments = $this->methodCalls[$i][1];
foreach ($arguments as $j => $value) {
if ($namedArguments && !$value instanceof $this->defaultArgument) {
unset($arguments[$j]);
$arguments[$namedArguments[$j]] = $value;
}
if ($namedArguments || !$value instanceof $this->defaultArgument) {
continue;
}
if (\PHP_VERSION_ID >= 80100 && (\is_array($value->value) ? $value->value : \is_object($value->value))) {
unset($arguments[$j]);
$namedArguments = $value->names;
} else {
$arguments[$j] = $value->value;
}
}
$this->methodCalls[$i][1] = $arguments;
}
return $this->methodCalls;
}
/**
* Autowires the constructor or a method.
*
* @throws AutowiringFailedException
*/
private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments, bool $checkAttributes, int $methodIndex): array
{
$class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
$method = $reflectionMethod->name;
$parameters = $reflectionMethod->getParameters();
if ($reflectionMethod->isVariadic()) {
array_pop($parameters);
}
$this->defaultArgument->names = new \ArrayObject();
foreach ($parameters as $index => $parameter) {
$this->defaultArgument->names[$index] = $parameter->name;
if (\array_key_exists($parameter->name, $arguments)) {
$arguments[$index] = $arguments[$parameter->name];
unset($arguments[$parameter->name]);
}
if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
continue;
}
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
if ($checkAttributes) {
foreach ($parameter->getAttributes() as $attribute) {
if (TaggedIterator::class === $attribute->getName()) {
$attribute = $attribute->newInstance();
$arguments[$index] = new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, false, $attribute->defaultPriorityMethod);
break;
}
if (TaggedLocator::class === $attribute->getName()) {
$attribute = $attribute->newInstance();
$arguments[$index] = new ServiceLocatorArgument(new TaggedIteratorArgument($attribute->tag, $attribute->indexAttribute, $attribute->defaultIndexMethod, true, $attribute->defaultPriorityMethod));
break;
}
}
if ('' !== ($arguments[$index] ?? '')) {
continue;
}
}
if (!$type) {
if (isset($arguments[$index])) {
continue;
}
// no default value? Then fail
if (!$parameter->isDefaultValueAvailable()) {
// For core classes, isDefaultValueAvailable() can
// be false when isOptional() returns true. If the
// argument *is* optional, allow it to be missing
if ($parameter->isOptional()) {
--$index;
break;
}
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false);
$type = $type ? sprintf('is type-hinted "%s"', ltrim($type, '\\')) : 'has no type-hint';
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));
}
// specifically pass the default value
$arguments[$index] = clone $this->defaultArgument;
$arguments[$index]->value = $parameter->getDefaultValue();
continue;
}
$getValue = function () use ($type, $parameter, $class, $method) {
if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, Target::parseName($parameter)), true)) {
$failureMessage = $this->createTypeNotFoundMessageCallback($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
if ($parameter->isDefaultValueAvailable()) {
$value = clone $this->defaultArgument;
$value->value = $parameter->getDefaultValue();
} elseif (!$parameter->allowsNull()) {
throw new AutowiringFailedException($this->currentId, $failureMessage);
}
}
return $value;
};
if ($this->decoratedClass && $isDecorated = is_a($this->decoratedClass, $type, true)) {
if ($this->getPreviousValue) {
// The inner service is injected only if there is only 1 argument matching the type of the decorated class
// across all arguments of all autowired methods.
// If a second matching argument is found, the default behavior is restored.
$getPreviousValue = $this->getPreviousValue;
$this->methodCalls[$this->decoratedMethodIndex][1][$this->decoratedMethodArgumentIndex] = $getPreviousValue();
$this->decoratedClass = null; // Prevent further checks
} else {
$arguments[$index] = new TypedReference($this->decoratedId, $this->decoratedClass);
$this->getPreviousValue = $getValue;
$this->decoratedMethodIndex = $methodIndex;
$this->decoratedMethodArgumentIndex = $index;
continue;
}
}
$arguments[$index] = $getValue();
}
if ($parameters && !isset($arguments[++$index])) {
while (0 <= --$index) {
if (!$arguments[$index] instanceof $this->defaultArgument) {
break;
}
unset($arguments[$index]);
}
}
// it's possible index 1 was set, then index 0, then 2, etc
// make sure that we re-order so they're injected as expected
ksort($arguments, \SORT_NATURAL);
return $arguments;
}
/**
* Returns a reference to the service matching the given type, if any.
*/
private function getAutowiredReference(TypedReference $reference, bool $filterType): ?TypedReference
{
$this->lastFailure = null;
$type = $reference->getType();
if ($type !== (string) $reference) {
return $reference;
}
if ($filterType && false !== $m = strpbrk($type, '&|')) {
$types = array_diff(explode($m[0], $type), ['int', 'string', 'array', 'bool', 'float', 'iterable', 'object', 'callable', 'null']);
sort($types);
$type = implode($m[0], $types);
}
if (null !== $name = $reference->getName()) {
if ($this->container->has($alias = $type.' $'.$name) && !$this->container->findDefinition($alias)->isAbstract()) {
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
}
if (null !== ($alias = $this->combinedAliases[$alias] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
}
if ($this->container->has($name) && !$this->container->findDefinition($name)->isAbstract()) {
foreach ($this->container->getAliases() + $this->combinedAliases as $id => $alias) {
if ($name === (string) $alias && str_starts_with($id, $type.' $')) {
return new TypedReference($name, $type, $reference->getInvalidBehavior());
}
}
}
}
if ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract()) {
return new TypedReference($type, $type, $reference->getInvalidBehavior());
}
if (null !== ($alias = $this->combinedAliases[$type] ?? null) && !$this->container->findDefinition($alias)->isAbstract()) {
return new TypedReference($alias, $type, $reference->getInvalidBehavior());
}
return null;
}
/**
* Populates the list of available types.
*/
private function populateAvailableTypes(ContainerBuilder $container)
{
$this->types = [];
$this->ambiguousServiceTypes = [];
$this->autowiringAliases = [];
foreach ($container->getDefinitions() as $id => $definition) {
$this->populateAvailableType($container, $id, $definition);
}
foreach ($container->getAliases() as $id => $alias) {
$this->populateAutowiringAlias($id);
}
}
/**
* Populates the list of available types for a given definition.
*/
private function populateAvailableType(ContainerBuilder $container, string $id, Definition $definition)
{
// Never use abstract services
if ($definition->isAbstract()) {
return;
}
if ('' === $id || '.' === $id[0] || $definition->isDeprecated() || !$reflectionClass = $container->getReflectionClass($definition->getClass(), false)) {
return;
}
foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
$this->set($reflectionInterface->name, $id);
}
do {
$this->set($reflectionClass->name, $id);
} while ($reflectionClass = $reflectionClass->getParentClass());
$this->populateAutowiringAlias($id);
}
/**
* Associates a type and a service id if applicable.
*/
private function set(string $type, string $id)
{
// is this already a type/class that is known to match multiple services?
if (isset($this->ambiguousServiceTypes[$type])) {
$this->ambiguousServiceTypes[$type][] = $id;
return;
}
// check to make sure the type doesn't match multiple services
if (!isset($this->types[$type]) || $this->types[$type] === $id) {
$this->types[$type] = $id;
return;
}
// keep an array of all services matching this type
if (!isset($this->ambiguousServiceTypes[$type])) {
$this->ambiguousServiceTypes[$type] = [$this->types[$type]];
unset($this->types[$type]);
}
$this->ambiguousServiceTypes[$type][] = $id;
}
private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label): \Closure
{
if (null === $this->typesClone->container) {
$this->typesClone->container = new ContainerBuilder($this->container->getParameterBag());
$this->typesClone->container->setAliases($this->container->getAliases());
$this->typesClone->container->setDefinitions($this->container->getDefinitions());
$this->typesClone->container->setResourceTracking(false);
}
$currentId = $this->currentId;
return (function () use ($reference, $label, $currentId) {
return $this->createTypeNotFoundMessage($reference, $label, $currentId);
})->bindTo($this->typesClone);
}
private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId): string
{
if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
// either $type does not exist or a parent class does not exist
try {
$resource = new ClassExistenceResource($type, false);
// isFresh() will explode ONLY if a parent class/trait does not exist
$resource->isFresh(0);
$parentMsg = false;
} catch (\ReflectionException $e) {
$parentMsg = $e->getMessage();
}
$message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
} else {
$alternatives = $this->createTypeAlternatives($this->container, $reference);
$message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
$message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives);
if ($r->isInterface() && !$alternatives) {
$message .= ' Did you create a class that implements this interface?';
}
}
$message = sprintf('Cannot autowire service "%s": %s %s', $currentId, $label, $message);
if (null !== $this->lastFailure) {
$message = $this->lastFailure."\n".$message;
$this->lastFailure = null;
}
return $message;
}
private function createTypeAlternatives(ContainerBuilder $container, TypedReference $reference): string
{
// try suggesting available aliases first
if ($message = $this->getAliasesSuggestionForType($container, $type = $reference->getType())) {
return ' '.$message;
}
if (null === $this->ambiguousServiceTypes) {
$this->populateAvailableTypes($container);
}
$servicesAndAliases = $container->getServiceIds();
if (null !== ($autowiringAliases = $this->autowiringAliases[$type] ?? null) && !isset($autowiringAliases[''])) {
return sprintf(' Available autowiring aliases for this %s are: "$%s".', class_exists($type, false) ? 'class' : 'interface', implode('", "$', $autowiringAliases));
}
if (!$container->has($type) && false !== $key = array_search(strtolower($type), array_map('strtolower', $servicesAndAliases))) {
return sprintf(' Did you mean "%s"?', $servicesAndAliases[$key]);
} elseif (isset($this->ambiguousServiceTypes[$type])) {
$message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type]));
} elseif (isset($this->types[$type])) {
$message = sprintf('the existing "%s" service', $this->types[$type]);
} else {
return '';
}
return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message);
}
private function getAliasesSuggestionForType(ContainerBuilder $container, string $type): ?string
{
$aliases = [];
foreach (class_parents($type) + class_implements($type) as $parent) {
if ($container->has($parent) && !$container->findDefinition($parent)->isAbstract()) {
$aliases[] = $parent;
}
}
if (1 < $len = \count($aliases)) {
$message = 'Try changing the type-hint to one of its parents: ';
for ($i = 0, --$len; $i < $len; ++$i) {
$message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
}
$message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
return $message;
}
if ($aliases) {
return sprintf('Try changing the type-hint to "%s" instead.', $aliases[0]);
}
return null;
}
private function populateAutowiringAlias(string $id): void
{
if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) {
return;
}
$type = $m[2];
$name = $m[3] ?? '';
if (class_exists($type, false) || interface_exists($type, false)) {
$this->autowiringAliases[$type][$name] = $name;
}
}
private function populateCombinedAliases(ContainerBuilder $container): void
{
$this->combinedAliases = [];
$reverseAliases = [];
foreach ($container->getAliases() as $id => $alias) {
if (!preg_match('/(?(DEFINE)(?<V>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))^((?&V)(?:\\\\(?&V))*+)(?: \$((?&V)))?$/', $id, $m)) {
continue;
}
$type = $m[2];
$name = $m[3] ?? '';
$reverseAliases[(string) $alias][$name][] = $type;
}
foreach ($reverseAliases as $alias => $names) {
foreach ($names as $name => $types) {
if (2 > $count = \count($types)) {
continue;
}
sort($types);
$i = 1 << $count;
// compute the powerset of the list of types
while ($i--) {
$set = [];
for ($j = 0; $j < $count; ++$j) {
if ($i & (1 << $j)) {
$set[] = $types[$j];
}
}
if (2 <= \count($set)) {
$this->combinedAliases[implode('&', $set).('' === $name ? '' : ' $'.$name)] = $alias;
$this->combinedAliases[implode('|', $set).('' === $name ? '' : ' $'.$name)] = $alias;
}
}
}
}
}
}

View file

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Contracts\Service\Attribute\Required;
/**
* Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class AutowireRequiredMethodsPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
$value = parent::processValue($value, $isRoot);
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
return $value;
}
$alreadyCalledMethods = [];
$withers = [];
foreach ($value->getMethodCalls() as [$method]) {
$alreadyCalledMethods[strtolower($method)] = true;
}
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
$r = $reflectionMethod;
if ($r->isConstructor() || isset($alreadyCalledMethods[strtolower($r->name)])) {
continue;
}
while (true) {
if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) {
if ($this->isWither($r, $r->getDocComment() ?: '')) {
$withers[] = [$r->name, [], true];
} else {
$value->addMethodCall($r->name, []);
}
break;
}
if (false !== $doc = $r->getDocComment()) {
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
if ($this->isWither($reflectionMethod, $doc)) {
$withers[] = [$reflectionMethod->name, [], true];
} else {
$value->addMethodCall($reflectionMethod->name, []);
}
break;
}
if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {
break;
}
}
try {
$r = $r->getPrototype();
} catch (\ReflectionException $e) {
break; // method has no prototype
}
}
}
if ($withers) {
// Prepend withers to prevent creating circular loops
$setters = $value->getMethodCalls();
$value->setMethodCalls($withers);
foreach ($setters as $call) {
$value->addMethodCall($call[0], $call[1], $call[2] ?? false);
}
}
return $value;
}
private function isWither(\ReflectionMethod $reflectionMethod, string $doc): bool
{
$match = preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@return\s++(static|\$this)[\s\*]#i', $doc, $matches);
if ($match && 'static' === $matches[1]) {
return true;
}
if ($match && '$this' === $matches[1]) {
return false;
}
$reflectionType = $reflectionMethod->hasReturnType() ? $reflectionMethod->getReturnType() : null;
return $reflectionType instanceof \ReflectionNamedType && 'static' === $reflectionType->getName();
}
}

View file

@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Contracts\Service\Attribute\Required;
/**
* Looks for definitions with autowiring enabled and registers their corresponding "@required" properties.
*
* @author Sebastien Morel (Plopix) <morel.seb@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class AutowireRequiredPropertiesPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (\PHP_VERSION_ID < 70400) {
return $value;
}
$value = parent::processValue($value, $isRoot);
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return $value;
}
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
return $value;
}
$properties = $value->getProperties();
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
if (!($type = $reflectionProperty->getType()) instanceof \ReflectionNamedType) {
continue;
}
if ((\PHP_VERSION_ID < 80000 || !$reflectionProperty->getAttributes(Required::class))
&& ((false === $doc = $reflectionProperty->getDocComment()) || false === stripos($doc, '@required') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc))
) {
continue;
}
if (\array_key_exists($name = $reflectionProperty->getName(), $properties)) {
continue;
}
$type = $type->getName();
$value->setProperty($name, new TypedReference($type, $type, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name));
}
return $value;
}
}

View file

@ -0,0 +1,119 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Checks if arguments of methods are properly configured.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class CheckArgumentsValidityPass extends AbstractRecursivePass
{
private $throwExceptions;
public function __construct(bool $throwExceptions = true)
{
$this->throwExceptions = $throwExceptions;
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
$i = 0;
$hasNamedArgs = false;
foreach ($value->getArguments() as $k => $v) {
if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) {
$hasNamedArgs = true;
continue;
}
if ($k !== $i++) {
if (!\is_int($k)) {
$msg = sprintf('Invalid constructor argument for service "%s": integer expected but found string "%s". Check your service definition.', $this->currentId, $k);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
$msg = sprintf('Invalid constructor argument %d for service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $this->currentId, $i);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
}
if ($hasNamedArgs) {
$msg = sprintf('Invalid constructor argument for service "%s": cannot use positional argument after named argument. Check your service definition.', $this->currentId);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
}
foreach ($value->getMethodCalls() as $methodCall) {
$i = 0;
$hasNamedArgs = false;
foreach ($methodCall[1] as $k => $v) {
if (\PHP_VERSION_ID >= 80000 && preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $k)) {
$hasNamedArgs = true;
continue;
}
if ($k !== $i++) {
if (!\is_int($k)) {
$msg = sprintf('Invalid argument for method call "%s" of service "%s": integer expected but found string "%s". Check your service definition.', $methodCall[0], $this->currentId, $k);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
$msg = sprintf('Invalid argument %d for method call "%s" of service "%s": argument %d must be defined before. Check your service definition.', 1 + $k, $methodCall[0], $this->currentId, $i);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
}
if ($hasNamedArgs) {
$msg = sprintf('Invalid argument for method call "%s" of service "%s": cannot use positional argument after named argument. Check your service definition.', $methodCall[0], $this->currentId);
$value->addError($msg);
if ($this->throwExceptions) {
throw new RuntimeException($msg);
}
break;
}
}
}
return null;
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
/**
* Checks your services for circular references.
*
* References from method calls are ignored since we might be able to resolve
* these references depending on the order in which services are called.
*
* Circular reference from method calls will only be detected at run-time.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckCircularReferencesPass implements CompilerPassInterface
{
private $currentPath;
private $checkedNodes;
/**
* Checks the ContainerBuilder object for circular references.
*/
public function process(ContainerBuilder $container)
{
$graph = $container->getCompiler()->getServiceReferenceGraph();
$this->checkedNodes = [];
foreach ($graph->getNodes() as $id => $node) {
$this->currentPath = [$id];
$this->checkOutEdges($node->getOutEdges());
}
}
/**
* Checks for circular references.
*
* @param ServiceReferenceGraphEdge[] $edges An array of Edges
*
* @throws ServiceCircularReferenceException when a circular reference is found
*/
private function checkOutEdges(array $edges)
{
foreach ($edges as $edge) {
$node = $edge->getDestNode();
$id = $node->getId();
if (empty($this->checkedNodes[$id])) {
// Don't check circular references for lazy edges
if (!$node->getValue() || (!$edge->isLazy() && !$edge->isWeak())) {
$searchKey = array_search($id, $this->currentPath);
$this->currentPath[] = $id;
if (false !== $searchKey) {
throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey));
}
$this->checkOutEdges($node->getOutEdges());
}
$this->checkedNodes[$id] = true;
array_pop($this->currentPath);
}
}
}
}

View file

@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Loader\FileLoader;
/**
* This pass validates each definition individually only taking the information
* into account which is contained in the definition itself.
*
* Later passes can rely on the following, and specifically do not need to
* perform these checks themselves:
*
* - non synthetic, non abstract services always have a class set
* - synthetic services are always public
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckDefinitionValidityPass implements CompilerPassInterface
{
/**
* Processes the ContainerBuilder to validate the Definition.
*
* @throws RuntimeException When the Definition is invalid
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
// synthetic service is public
if ($definition->isSynthetic() && !$definition->isPublic()) {
throw new RuntimeException(sprintf('A synthetic service ("%s") must be public.', $id));
}
// non-synthetic, non-abstract service has class
if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass() && !$definition->hasTag('container.service_locator') && (!$definition->getFactory() || !preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id))) {
if ($definition->getFactory()) {
throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id));
}
if (class_exists($id) || interface_exists($id, false)) {
if (str_starts_with($id, '\\') && 1 < substr_count($id, '\\')) {
throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface. Please specify the class attribute explicitly or remove the leading backslash by renaming the service to "%s" to get rid of this error.', $id, substr($id, 1)));
}
throw new RuntimeException(sprintf('The definition for "%s" has no class attribute, and appears to reference a class or interface in the global namespace. Leaving out the "class" attribute is only allowed for namespaced classes. Please specify the class attribute explicitly to get rid of this error.', $id));
}
throw new RuntimeException(sprintf('The definition for "%s" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.', $id));
}
// tag attribute values must be scalars
foreach ($definition->getTags() as $name => $tags) {
foreach ($tags as $attributes) {
foreach ($attributes as $attribute => $value) {
if (!\is_scalar($value) && null !== $value) {
throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute));
}
}
}
}
if ($definition->isPublic() && !$definition->isPrivate()) {
$resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs);
if (null !== $usedEnvs) {
throw new EnvParameterException([$resolvedId], null, 'A service name ("%s") cannot contain dynamic values.');
}
}
}
foreach ($container->getAliases() as $id => $alias) {
if ($alias->isPublic() && !$alias->isPrivate()) {
$resolvedId = $container->resolveEnvPlaceholders($id, null, $usedEnvs);
if (null !== $usedEnvs) {
throw new EnvParameterException([$resolvedId], null, 'An alias name ("%s") cannot contain dynamic values.');
}
}
}
}
}

View file

@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Checks that all references are pointing to a valid service.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckExceptionOnInvalidReferenceBehaviorPass extends AbstractRecursivePass
{
private $serviceLocatorContextIds = [];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->serviceLocatorContextIds = [];
foreach ($container->findTaggedServiceIds('container.service_locator_context') as $id => $tags) {
$this->serviceLocatorContextIds[$id] = $tags[0]['id'];
$container->getDefinition($id)->clearTag('container.service_locator_context');
}
try {
return parent::process($container);
} finally {
$this->serviceLocatorContextIds = [];
}
}
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
}
if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $value->getInvalidBehavior() || $this->container->has($id = (string) $value)) {
return $value;
}
$currentId = $this->currentId;
$graph = $this->container->getCompiler()->getServiceReferenceGraph();
if (isset($this->serviceLocatorContextIds[$currentId])) {
$currentId = $this->serviceLocatorContextIds[$currentId];
$locator = $this->container->getDefinition($this->currentId)->getFactory()[0];
foreach ($locator->getArgument(0) as $k => $v) {
if ($v->getValues()[0] === $value) {
if ($k !== $id) {
$currentId = $k.'" in the container provided to "'.$currentId;
}
throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
}
}
}
if ('.' === $currentId[0] && $graph->hasNode($currentId)) {
foreach ($graph->getNode($currentId)->getInEdges() as $edge) {
if (!$edge->getValue() instanceof Reference || ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $edge->getValue()->getInvalidBehavior()) {
continue;
}
$sourceId = $edge->getSourceNode()->getId();
if ('.' !== $sourceId[0]) {
$currentId = $sourceId;
break;
}
}
}
throw new ServiceNotFoundException($id, $currentId, null, $this->getAlternatives($id));
}
private function getAlternatives(string $id): array
{
$alternatives = [];
foreach ($this->container->getServiceIds() as $knownId) {
if ('' === $knownId || '.' === $knownId[0]) {
continue;
}
$lev = levenshtein($id, $knownId);
if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) {
$alternatives[] = $knownId;
}
}
return $alternatives;
}
}

View file

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Checks the validity of references.
*
* The following checks are performed by this pass:
* - target definitions are not abstract
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class CheckReferenceValidityPass extends AbstractRecursivePass
{
protected function processValue($value, bool $isRoot = false)
{
if ($isRoot && $value instanceof Definition && ($value->isSynthetic() || $value->isAbstract())) {
return $value;
}
if ($value instanceof Reference && $this->container->hasDefinition((string) $value)) {
$targetDefinition = $this->container->getDefinition((string) $value);
if ($targetDefinition->isAbstract()) {
throw new RuntimeException(sprintf('The definition "%s" has a reference to an abstract definition "%s". Abstract definitions cannot be the target of references.', $this->currentId, $value));
}
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,329 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\InvalidParameterTypeException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ExpressionLanguage;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* Checks whether injected parameters are compatible with type declarations.
*
* This pass should be run after all optimization passes.
*
* It can be added either:
* * before removing passes to check all services even if they are not currently used,
* * after removing passes to check only services are used in the app.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Julien Maulny <jmaulny@darkmira.fr>
*/
final class CheckTypeDeclarationsPass extends AbstractRecursivePass
{
private const SCALAR_TYPES = [
'int' => true,
'float' => true,
'bool' => true,
'string' => true,
];
private const BUILTIN_TYPES = [
'array' => true,
'bool' => true,
'callable' => true,
'float' => true,
'int' => true,
'iterable' => true,
'object' => true,
'string' => true,
];
private $autoload;
private $skippedIds;
private $expressionLanguage;
/**
* @param bool $autoload Whether services who's class in not loaded should be checked or not.
* Defaults to false to save loading code during compilation.
* @param array $skippedIds An array indexed by the service ids to skip
*/
public function __construct(bool $autoload = false, array $skippedIds = [])
{
$this->autoload = $autoload;
$this->skippedIds = $skippedIds;
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (isset($this->skippedIds[$this->currentId])) {
return $value;
}
if (!$value instanceof Definition || $value->hasErrors() || $value->isDeprecated()) {
return parent::processValue($value, $isRoot);
}
if (!$this->autoload) {
if (!$class = $value->getClass()) {
return parent::processValue($value, $isRoot);
}
if (!class_exists($class, false) && !interface_exists($class, false)) {
return parent::processValue($value, $isRoot);
}
}
if (ServiceLocator::class === $value->getClass()) {
return parent::processValue($value, $isRoot);
}
if ($constructor = $this->getConstructor($value, false)) {
$this->checkTypeDeclarations($value, $constructor, $value->getArguments());
}
foreach ($value->getMethodCalls() as $methodCall) {
try {
$reflectionMethod = $this->getReflectionMethod($value, $methodCall[0]);
} catch (RuntimeException $e) {
if ($value->getFactory()) {
continue;
}
throw $e;
}
$this->checkTypeDeclarations($value, $reflectionMethod, $methodCall[1]);
}
return parent::processValue($value, $isRoot);
}
/**
* @throws InvalidArgumentException When not enough parameters are defined for the method
*/
private function checkTypeDeclarations(Definition $checkedDefinition, \ReflectionFunctionAbstract $reflectionFunction, array $values): void
{
$numberOfRequiredParameters = $reflectionFunction->getNumberOfRequiredParameters();
if (\count($values) < $numberOfRequiredParameters) {
throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": "%s::%s()" requires %d arguments, %d passed.', $this->currentId, $reflectionFunction->class, $reflectionFunction->name, $numberOfRequiredParameters, \count($values)));
}
$reflectionParameters = $reflectionFunction->getParameters();
$checksCount = min($reflectionFunction->getNumberOfParameters(), \count($values));
$envPlaceholderUniquePrefix = $this->container->getParameterBag() instanceof EnvPlaceholderParameterBag ? $this->container->getParameterBag()->getEnvPlaceholderUniquePrefix() : null;
for ($i = 0; $i < $checksCount; ++$i) {
if (!$reflectionParameters[$i]->hasType() || $reflectionParameters[$i]->isVariadic()) {
continue;
}
$this->checkType($checkedDefinition, $values[$i], $reflectionParameters[$i], $envPlaceholderUniquePrefix);
}
if ($reflectionFunction->isVariadic() && ($lastParameter = end($reflectionParameters))->hasType()) {
$variadicParameters = \array_slice($values, $lastParameter->getPosition());
foreach ($variadicParameters as $variadicParameter) {
$this->checkType($checkedDefinition, $variadicParameter, $lastParameter, $envPlaceholderUniquePrefix);
}
}
}
/**
* @throws InvalidParameterTypeException When a parameter is not compatible with the declared type
*/
private function checkType(Definition $checkedDefinition, $value, \ReflectionParameter $parameter, ?string $envPlaceholderUniquePrefix, \ReflectionType $reflectionType = null): void
{
$reflectionType = $reflectionType ?? $parameter->getType();
if ($reflectionType instanceof \ReflectionUnionType) {
foreach ($reflectionType->getTypes() as $t) {
try {
$this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t);
return;
} catch (InvalidParameterTypeException $e) {
}
}
throw new InvalidParameterTypeException($this->currentId, $e->getCode(), $parameter);
}
if ($reflectionType instanceof \ReflectionIntersectionType) {
foreach ($reflectionType->getTypes() as $t) {
$this->checkType($checkedDefinition, $value, $parameter, $envPlaceholderUniquePrefix, $t);
}
return;
}
if (!$reflectionType instanceof \ReflectionNamedType) {
return;
}
$type = $reflectionType->getName();
if ($value instanceof Reference) {
if (!$this->container->has($value = (string) $value)) {
return;
}
if ('service_container' === $value && is_a($type, Container::class, true)) {
return;
}
$value = $this->container->findDefinition($value);
}
if ('self' === $type) {
$type = $parameter->getDeclaringClass()->getName();
}
if ('static' === $type) {
$type = $checkedDefinition->getClass();
}
$class = null;
if ($value instanceof Definition) {
if ($value->getFactory()) {
return;
}
$class = $value->getClass();
if ($class && isset(self::BUILTIN_TYPES[strtolower($class)])) {
$class = strtolower($class);
} elseif (!$class || (!$this->autoload && !class_exists($class, false) && !interface_exists($class, false))) {
return;
}
} elseif ($value instanceof Parameter) {
$value = $this->container->getParameter($value);
} elseif ($value instanceof Expression) {
try {
$value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this->container]);
} catch (\Exception $e) {
// If a service from the expression cannot be fetched from the container, we skip the validation.
return;
}
} elseif (\is_string($value)) {
if ('%' === ($value[0] ?? '') && preg_match('/^%([^%]+)%$/', $value, $match)) {
$value = $this->container->getParameter(substr($value, 1, -1));
}
if ($envPlaceholderUniquePrefix && \is_string($value) && str_contains($value, 'env_')) {
// If the value is an env placeholder that is either mixed with a string or with another env placeholder, then its resolved value will always be a string, so we don't need to resolve it.
// We don't need to change the value because it is already a string.
if ('' === preg_replace('/'.$envPlaceholderUniquePrefix.'_\w+_[a-f0-9]{32}/U', '', $value, -1, $c) && 1 === $c) {
try {
$value = $this->container->resolveEnvPlaceholders($value, true);
} catch (\Exception $e) {
// If an env placeholder cannot be resolved, we skip the validation.
return;
}
}
}
}
if (null === $value && $parameter->allowsNull()) {
return;
}
if (null === $class) {
if ($value instanceof IteratorArgument) {
$class = RewindableGenerator::class;
} elseif ($value instanceof ServiceClosureArgument) {
$class = \Closure::class;
} elseif ($value instanceof ServiceLocatorArgument) {
$class = ServiceLocator::class;
} elseif (\is_object($value)) {
$class = \get_class($value);
} else {
$class = \gettype($value);
$class = ['integer' => 'int', 'double' => 'float', 'boolean' => 'bool'][$class] ?? $class;
}
}
if (isset(self::SCALAR_TYPES[$type]) && isset(self::SCALAR_TYPES[$class])) {
return;
}
if ('string' === $type && method_exists($class, '__toString')) {
return;
}
if ('callable' === $type && (\Closure::class === $class || method_exists($class, '__invoke'))) {
return;
}
if ('callable' === $type && \is_array($value) && isset($value[0]) && ($value[0] instanceof Reference || $value[0] instanceof Definition || \is_string($value[0]))) {
return;
}
if ('iterable' === $type && (\is_array($value) || 'array' === $class || is_subclass_of($class, \Traversable::class))) {
return;
}
if ($type === $class) {
return;
}
if ('object' === $type && !isset(self::BUILTIN_TYPES[$class])) {
return;
}
if ('mixed' === $type) {
return;
}
if (is_a($class, $type, true)) {
return;
}
if ('false' === $type) {
if (false === $value) {
return;
}
} elseif ($reflectionType->isBuiltin()) {
$checkFunction = sprintf('is_%s', $type);
if ($checkFunction($value)) {
return;
}
}
throw new InvalidParameterTypeException($this->currentId, \is_object($value) ? $class : get_debug_type($value), $parameter);
}
private function getExpressionLanguage(): ExpressionLanguage
{
if (null === $this->expressionLanguage) {
$this->expressionLanguage = new ExpressionLanguage(null, $this->container->getExpressionLanguageProviders());
}
return $this->expressionLanguage;
}
}

View file

@ -0,0 +1,107 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
/**
* This class is used to remove circular dependencies between individual passes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class Compiler
{
private $passConfig;
private $log = [];
private $serviceReferenceGraph;
public function __construct()
{
$this->passConfig = new PassConfig();
$this->serviceReferenceGraph = new ServiceReferenceGraph();
}
/**
* @return PassConfig
*/
public function getPassConfig()
{
return $this->passConfig;
}
/**
* @return ServiceReferenceGraph
*/
public function getServiceReferenceGraph()
{
return $this->serviceReferenceGraph;
}
public function addPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0)
{
$this->passConfig->addPass($pass, $type, $priority);
}
/**
* @final
*/
public function log(CompilerPassInterface $pass, string $message)
{
if (str_contains($message, "\n")) {
$message = str_replace("\n", "\n".\get_class($pass).': ', trim($message));
}
$this->log[] = \get_class($pass).': '.$message;
}
/**
* @return array
*/
public function getLog()
{
return $this->log;
}
/**
* Run the Compiler and process all Passes.
*/
public function compile(ContainerBuilder $container)
{
try {
foreach ($this->passConfig->getPasses() as $pass) {
$pass->process($container);
}
} catch (\Exception $e) {
$usedEnvs = [];
$prev = $e;
do {
$msg = $prev->getMessage();
if ($msg !== $resolvedMsg = $container->resolveEnvPlaceholders($msg, null, $usedEnvs)) {
$r = new \ReflectionProperty($prev, 'message');
$r->setAccessible(true);
$r->setValue($prev, $resolvedMsg);
}
} while ($prev = $prev->getPrevious());
if ($usedEnvs) {
$e = new EnvParameterException($usedEnvs, $e);
}
throw $e;
} finally {
$this->getServiceReferenceGraph()->clear();
}
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Interface that must be implemented by compilation passes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
*/
public function process(ContainerBuilder $container);
}

View file

@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Overwrites a service but keeps the overridden one.
*
* @author Christophe Coevoet <stof@notk.org>
* @author Fabien Potencier <fabien@symfony.com>
* @author Diego Saint Esteben <diego@saintesteben.me>
*/
class DecoratorServicePass extends AbstractRecursivePass
{
private $innerId = '.inner';
public function __construct(?string $innerId = '.inner')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->innerId = $innerId;
}
public function process(ContainerBuilder $container)
{
$definitions = new \SplPriorityQueue();
$order = \PHP_INT_MAX;
foreach ($container->getDefinitions() as $id => $definition) {
if (!$decorated = $definition->getDecoratedService()) {
continue;
}
$definitions->insert([$id, $definition], [$decorated[2], --$order]);
}
$decoratingDefinitions = [];
$tagsToKeep = $container->hasParameter('container.behavior_describing_tags')
? $container->getParameter('container.behavior_describing_tags')
: ['container.do_not_inline', 'container.service_locator', 'container.service_subscriber', 'container.service_subscriber.locator'];
foreach ($definitions as [$id, $definition]) {
$decoratedService = $definition->getDecoratedService();
[$inner, $renamedId] = $decoratedService;
$invalidBehavior = $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
$definition->setDecoratedService(null);
if (!$renamedId) {
$renamedId = $id.'.inner';
}
$this->currentId = $renamedId;
$this->processValue($definition);
$definition->innerServiceId = $renamedId;
$definition->decorationOnInvalid = $invalidBehavior;
// we create a new alias/service for the service we are replacing
// to be able to reference it in the new one
if ($container->hasAlias($inner)) {
$alias = $container->getAlias($inner);
$public = $alias->isPublic();
$container->setAlias($renamedId, new Alias((string) $alias, false));
$decoratedDefinition = $container->findDefinition($alias);
} elseif ($container->hasDefinition($inner)) {
$decoratedDefinition = $container->getDefinition($inner);
$public = $decoratedDefinition->isPublic();
$decoratedDefinition->setPublic(false);
$container->setDefinition($renamedId, $decoratedDefinition);
$decoratingDefinitions[$inner] = $decoratedDefinition;
} elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
$container->removeDefinition($id);
continue;
} elseif (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
$public = $definition->isPublic();
$decoratedDefinition = null;
} else {
throw new ServiceNotFoundException($inner, $id);
}
if ($decoratedDefinition && $decoratedDefinition->isSynthetic()) {
throw new InvalidArgumentException(sprintf('A synthetic service cannot be decorated: service "%s" cannot decorate "%s".', $id, $inner));
}
if (isset($decoratingDefinitions[$inner])) {
$decoratingDefinition = $decoratingDefinitions[$inner];
$decoratingTags = $decoratingDefinition->getTags();
$resetTags = [];
// Behavior-describing tags must not be transferred out to decorators
foreach ($tagsToKeep as $containerTag) {
if (isset($decoratingTags[$containerTag])) {
$resetTags[$containerTag] = $decoratingTags[$containerTag];
unset($decoratingTags[$containerTag]);
}
}
$definition->setTags(array_merge($decoratingTags, $definition->getTags()));
$decoratingDefinition->setTags($resetTags);
$decoratingDefinitions[$inner] = $definition;
}
$container->setAlias($inner, $id)->setPublic($public);
}
}
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && $this->innerId === (string) $value) {
return new Reference($this->currentId, $value->getInvalidBehavior());
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Throws an exception for any Definitions that have errors and still exist.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class DefinitionErrorExceptionPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition || !$value->hasErrors()) {
return parent::processValue($value, $isRoot);
}
if ($isRoot && !$value->isPublic()) {
$graph = $this->container->getCompiler()->getServiceReferenceGraph();
$runtimeException = false;
foreach ($graph->getNode($this->currentId)->getInEdges() as $edge) {
if (!$edge->getValue() instanceof Reference || ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE !== $edge->getValue()->getInvalidBehavior()) {
$runtimeException = false;
break;
}
$runtimeException = true;
}
if ($runtimeException) {
return parent::processValue($value, $isRoot);
}
}
// only show the first error so the user can focus on it
$errors = $value->getErrors();
$message = reset($errors);
throw new RuntimeException($message);
}
}

View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* A pass to automatically process extensions if they implement
* CompilerPassInterface.
*
* @author Wouter J <wouter@wouterj.nl>
*/
class ExtensionCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getExtensions() as $extension) {
if (!$extension instanceof CompilerPassInterface) {
continue;
}
$extension->process($container);
}
}
}

View file

@ -0,0 +1,223 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Inline service definitions where this is possible.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class InlineServiceDefinitionsPass extends AbstractRecursivePass
{
private $analyzingPass;
private $cloningIds = [];
private $connectedIds = [];
private $notInlinedIds = [];
private $inlinedIds = [];
private $notInlinableIds = [];
private $graph;
public function __construct(AnalyzeServiceReferencesPass $analyzingPass = null)
{
$this->analyzingPass = $analyzingPass;
}
public function process(ContainerBuilder $container)
{
$this->container = $container;
if ($this->analyzingPass) {
$analyzedContainer = new ContainerBuilder();
$analyzedContainer->setAliases($container->getAliases());
$analyzedContainer->setDefinitions($container->getDefinitions());
foreach ($container->getExpressionLanguageProviders() as $provider) {
$analyzedContainer->addExpressionLanguageProvider($provider);
}
} else {
$analyzedContainer = $container;
}
try {
$remainingInlinedIds = [];
$this->connectedIds = $this->notInlinedIds = $container->getDefinitions();
do {
if ($this->analyzingPass) {
$analyzedContainer->setDefinitions(array_intersect_key($analyzedContainer->getDefinitions(), $this->connectedIds));
$this->analyzingPass->process($analyzedContainer);
}
$this->graph = $analyzedContainer->getCompiler()->getServiceReferenceGraph();
$notInlinedIds = $this->notInlinedIds;
$this->connectedIds = $this->notInlinedIds = $this->inlinedIds = [];
foreach ($analyzedContainer->getDefinitions() as $id => $definition) {
if (!$this->graph->hasNode($id)) {
continue;
}
foreach ($this->graph->getNode($id)->getOutEdges() as $edge) {
if (isset($notInlinedIds[$edge->getSourceNode()->getId()])) {
$this->currentId = $id;
$this->processValue($definition, true);
break;
}
}
}
foreach ($this->inlinedIds as $id => $isPublicOrNotShared) {
if ($isPublicOrNotShared) {
$remainingInlinedIds[$id] = $id;
} else {
$container->removeDefinition($id);
$analyzedContainer->removeDefinition($id);
}
}
} while ($this->inlinedIds && $this->analyzingPass);
foreach ($remainingInlinedIds as $id) {
if (isset($this->notInlinableIds[$id])) {
continue;
}
$definition = $container->getDefinition($id);
if (!$definition->isShared() && !$definition->isPublic()) {
$container->removeDefinition($id);
}
}
} finally {
$this->container = null;
$this->connectedIds = $this->notInlinedIds = $this->inlinedIds = [];
$this->notInlinableIds = [];
$this->graph = null;
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof ArgumentInterface) {
// References found in ArgumentInterface::getValues() are not inlineable
return $value;
}
if ($value instanceof Definition && $this->cloningIds) {
if ($value->isShared()) {
return $value;
}
$value = clone $value;
}
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
} elseif (!$this->container->hasDefinition($id = (string) $value)) {
return $value;
}
$definition = $this->container->getDefinition($id);
if (!$this->isInlineableDefinition($id, $definition)) {
$this->notInlinableIds[$id] = true;
return $value;
}
$this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId));
$this->inlinedIds[$id] = $definition->isPublic() || !$definition->isShared();
$this->notInlinedIds[$this->currentId] = true;
if ($definition->isShared()) {
return $definition;
}
if (isset($this->cloningIds[$id])) {
$ids = array_keys($this->cloningIds);
$ids[] = $id;
throw new ServiceCircularReferenceException($id, \array_slice($ids, array_search($id, $ids)));
}
$this->cloningIds[$id] = true;
try {
return $this->processValue($definition);
} finally {
unset($this->cloningIds[$id]);
}
}
/**
* Checks if the definition is inlineable.
*/
private function isInlineableDefinition(string $id, Definition $definition): bool
{
if ($definition->hasErrors() || $definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic() || $definition->hasTag('container.do_not_inline')) {
return false;
}
if (!$definition->isShared()) {
if (!$this->graph->hasNode($id)) {
return true;
}
foreach ($this->graph->getNode($id)->getInEdges() as $edge) {
$srcId = $edge->getSourceNode()->getId();
$this->connectedIds[$srcId] = true;
if ($edge->isWeak() || $edge->isLazy()) {
return !$this->connectedIds[$id] = true;
}
}
return true;
}
if ($definition->isPublic()) {
return false;
}
if (!$this->graph->hasNode($id)) {
return true;
}
if ($this->currentId == $id) {
return false;
}
$this->connectedIds[$id] = true;
$srcIds = [];
$srcCount = 0;
foreach ($this->graph->getNode($id)->getInEdges() as $edge) {
$srcId = $edge->getSourceNode()->getId();
$this->connectedIds[$srcId] = true;
if ($edge->isWeak() || $edge->isLazy()) {
return false;
}
$srcIds[$srcId] = true;
++$srcCount;
}
if (1 !== \count($srcIds)) {
$this->notInlinedIds[$id] = true;
return false;
}
if ($srcCount > 1 && \is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) {
return false;
}
return $this->container->getDefinition($srcId)->isShared();
}
}

View file

@ -0,0 +1,216 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/**
* Merges extension configs into the container builder.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MergeExtensionConfigurationPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$parameters = $container->getParameterBag()->all();
$definitions = $container->getDefinitions();
$aliases = $container->getAliases();
$exprLangProviders = $container->getExpressionLanguageProviders();
$configAvailable = class_exists(BaseNode::class);
foreach ($container->getExtensions() as $extension) {
if ($extension instanceof PrependExtensionInterface) {
$extension->prepend($container);
}
}
foreach ($container->getExtensions() as $name => $extension) {
if (!$config = $container->getExtensionConfig($name)) {
// this extension was not called
continue;
}
$resolvingBag = $container->getParameterBag();
if ($resolvingBag instanceof EnvPlaceholderParameterBag && $extension instanceof Extension) {
// create a dedicated bag so that we can track env vars per-extension
$resolvingBag = new MergeExtensionConfigurationParameterBag($resolvingBag);
if ($configAvailable) {
BaseNode::setPlaceholderUniquePrefix($resolvingBag->getEnvPlaceholderUniquePrefix());
}
}
$config = $resolvingBag->resolveValue($config);
try {
$tmpContainer = new MergeExtensionConfigurationContainerBuilder($extension, $resolvingBag);
$tmpContainer->setResourceTracking($container->isTrackingResources());
$tmpContainer->addObjectResource($extension);
if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) {
$tmpContainer->addObjectResource($configuration);
}
foreach ($exprLangProviders as $provider) {
$tmpContainer->addExpressionLanguageProvider($provider);
}
$extension->load($config, $tmpContainer);
} catch (\Exception $e) {
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
$container->getParameterBag()->mergeEnvPlaceholders($resolvingBag);
}
throw $e;
}
if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) {
// don't keep track of env vars that are *overridden* when configs are merged
$resolvingBag->freezeAfterProcessing($extension, $tmpContainer);
}
$container->merge($tmpContainer);
$container->getParameterBag()->add($parameters);
}
$container->addDefinitions($definitions);
$container->addAliases($aliases);
}
}
/**
* @internal
*/
class MergeExtensionConfigurationParameterBag extends EnvPlaceholderParameterBag
{
private $processedEnvPlaceholders;
public function __construct(parent $parameterBag)
{
parent::__construct($parameterBag->all());
$this->mergeEnvPlaceholders($parameterBag);
}
public function freezeAfterProcessing(Extension $extension, ContainerBuilder $container)
{
if (!$config = $extension->getProcessedConfigs()) {
// Extension::processConfiguration() wasn't called, we cannot know how configs were merged
return;
}
$this->processedEnvPlaceholders = [];
// serialize config and container to catch env vars nested in object graphs
$config = serialize($config).serialize($container->getDefinitions()).serialize($container->getAliases()).serialize($container->getParameterBag()->all());
foreach (parent::getEnvPlaceholders() as $env => $placeholders) {
foreach ($placeholders as $placeholder) {
if (false !== stripos($config, $placeholder)) {
$this->processedEnvPlaceholders[$env] = $placeholders;
break;
}
}
}
}
/**
* {@inheritdoc}
*/
public function getEnvPlaceholders(): array
{
return $this->processedEnvPlaceholders ?? parent::getEnvPlaceholders();
}
public function getUnusedEnvPlaceholders(): array
{
return null === $this->processedEnvPlaceholders ? [] : array_diff_key(parent::getEnvPlaceholders(), $this->processedEnvPlaceholders);
}
}
/**
* A container builder preventing using methods that wouldn't have any effect from extensions.
*
* @internal
*/
class MergeExtensionConfigurationContainerBuilder extends ContainerBuilder
{
private $extensionClass;
public function __construct(ExtensionInterface $extension, ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
$this->extensionClass = \get_class($extension);
}
/**
* {@inheritdoc}
*/
public function addCompilerPass(CompilerPassInterface $pass, string $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0): self
{
throw new LogicException(sprintf('You cannot add compiler pass "%s" from extension "%s". Compiler passes must be registered before the container is compiled.', get_debug_type($pass), $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function registerExtension(ExtensionInterface $extension)
{
throw new LogicException(sprintf('You cannot register extension "%s" from "%s". Extensions must be registered before the container is compiled.', get_debug_type($extension), $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function compile(bool $resolveEnvPlaceholders = false)
{
throw new LogicException(sprintf('Cannot compile the container in extension "%s".', $this->extensionClass));
}
/**
* {@inheritdoc}
*/
public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null)
{
if (true !== $format || !\is_string($value)) {
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);
}
$bag = $this->getParameterBag();
$value = $bag->resolveValue($value);
if (!$bag instanceof EnvPlaceholderParameterBag) {
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);
}
foreach ($bag->getEnvPlaceholders() as $env => $placeholders) {
if (!str_contains($env, ':')) {
continue;
}
foreach ($placeholders as $placeholder) {
if (false !== stripos($value, $placeholder)) {
throw new RuntimeException(sprintf('Using a cast in "env(%s)" is incompatible with resolution at compile time in "%s". The logic in the extension should be moved to a compiler pass, or an env parameter with no cast should be used instead.', $env, $this->extensionClass));
}
}
}
return parent::resolveEnvPlaceholders($value, $format, $usedEnvs);
}
}

View file

@ -0,0 +1,269 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* Compiler Pass Configuration.
*
* This class has a default configuration embedded.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PassConfig
{
public const TYPE_AFTER_REMOVING = 'afterRemoving';
public const TYPE_BEFORE_OPTIMIZATION = 'beforeOptimization';
public const TYPE_BEFORE_REMOVING = 'beforeRemoving';
public const TYPE_OPTIMIZE = 'optimization';
public const TYPE_REMOVE = 'removing';
private $mergePass;
private $afterRemovingPasses = [];
private $beforeOptimizationPasses = [];
private $beforeRemovingPasses = [];
private $optimizationPasses;
private $removingPasses;
public function __construct()
{
$this->mergePass = new MergeExtensionConfigurationPass();
$this->beforeOptimizationPasses = [
100 => [
new ResolveClassPass(),
new RegisterAutoconfigureAttributesPass(),
new AttributeAutoconfigurationPass(),
new ResolveInstanceofConditionalsPass(),
new RegisterEnvVarProcessorsPass(),
],
-1000 => [new ExtensionCompilerPass()],
];
$this->optimizationPasses = [[
$autoAliasServicePass = new AutoAliasServicePass(),
new ValidateEnvPlaceholdersPass(),
new ResolveDecoratorStackPass(),
new ResolveChildDefinitionsPass(),
new RegisterServiceSubscribersPass(),
new ResolveParameterPlaceHoldersPass(false, false),
new ResolveFactoryClassPass(),
new ResolveNamedArgumentsPass(),
new AutowireRequiredMethodsPass(),
new AutowireRequiredPropertiesPass(),
new ResolveBindingsPass(),
new ServiceLocatorTagPass(),
new DecoratorServicePass(),
new CheckDefinitionValidityPass(),
new AutowirePass(false),
new ServiceLocatorTagPass(),
new ResolveTaggedIteratorArgumentPass(),
new ResolveServiceSubscribersPass(),
new ResolveReferencesToAliasesPass(),
new ResolveInvalidReferencesPass(),
new AnalyzeServiceReferencesPass(true),
new CheckCircularReferencesPass(),
new CheckReferenceValidityPass(),
new CheckArgumentsValidityPass(false),
]];
$this->removingPasses = [[
new RemovePrivateAliasesPass(),
(new ReplaceAliasByActualDefinitionPass())->setAutoAliasServicePass($autoAliasServicePass),
new RemoveAbstractDefinitionsPass(),
new RemoveUnusedDefinitionsPass(),
new AnalyzeServiceReferencesPass(),
new CheckExceptionOnInvalidReferenceBehaviorPass(),
new InlineServiceDefinitionsPass(new AnalyzeServiceReferencesPass()),
new AnalyzeServiceReferencesPass(),
new DefinitionErrorExceptionPass(),
]];
$this->afterRemovingPasses = [[
new ResolveHotPathPass(),
new ResolveNoPreloadPass(),
new AliasDeprecatedPublicServicesPass(),
]];
}
/**
* Returns all passes in order to be processed.
*
* @return CompilerPassInterface[]
*/
public function getPasses()
{
return array_merge(
[$this->mergePass],
$this->getBeforeOptimizationPasses(),
$this->getOptimizationPasses(),
$this->getBeforeRemovingPasses(),
$this->getRemovingPasses(),
$this->getAfterRemovingPasses()
);
}
/**
* Adds a pass.
*
* @throws InvalidArgumentException when a pass type doesn't exist
*/
public function addPass(CompilerPassInterface $pass, string $type = self::TYPE_BEFORE_OPTIMIZATION, int $priority = 0)
{
$property = $type.'Passes';
if (!isset($this->$property)) {
throw new InvalidArgumentException(sprintf('Invalid type "%s".', $type));
}
$passes = &$this->$property;
if (!isset($passes[$priority])) {
$passes[$priority] = [];
}
$passes[$priority][] = $pass;
}
/**
* Gets all passes for the AfterRemoving pass.
*
* @return CompilerPassInterface[]
*/
public function getAfterRemovingPasses()
{
return $this->sortPasses($this->afterRemovingPasses);
}
/**
* Gets all passes for the BeforeOptimization pass.
*
* @return CompilerPassInterface[]
*/
public function getBeforeOptimizationPasses()
{
return $this->sortPasses($this->beforeOptimizationPasses);
}
/**
* Gets all passes for the BeforeRemoving pass.
*
* @return CompilerPassInterface[]
*/
public function getBeforeRemovingPasses()
{
return $this->sortPasses($this->beforeRemovingPasses);
}
/**
* Gets all passes for the Optimization pass.
*
* @return CompilerPassInterface[]
*/
public function getOptimizationPasses()
{
return $this->sortPasses($this->optimizationPasses);
}
/**
* Gets all passes for the Removing pass.
*
* @return CompilerPassInterface[]
*/
public function getRemovingPasses()
{
return $this->sortPasses($this->removingPasses);
}
/**
* Gets the Merge pass.
*
* @return CompilerPassInterface
*/
public function getMergePass()
{
return $this->mergePass;
}
public function setMergePass(CompilerPassInterface $pass)
{
$this->mergePass = $pass;
}
/**
* Sets the AfterRemoving passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setAfterRemovingPasses(array $passes)
{
$this->afterRemovingPasses = [$passes];
}
/**
* Sets the BeforeOptimization passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setBeforeOptimizationPasses(array $passes)
{
$this->beforeOptimizationPasses = [$passes];
}
/**
* Sets the BeforeRemoving passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setBeforeRemovingPasses(array $passes)
{
$this->beforeRemovingPasses = [$passes];
}
/**
* Sets the Optimization passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setOptimizationPasses(array $passes)
{
$this->optimizationPasses = [$passes];
}
/**
* Sets the Removing passes.
*
* @param CompilerPassInterface[] $passes
*/
public function setRemovingPasses(array $passes)
{
$this->removingPasses = [$passes];
}
/**
* Sort passes by priority.
*
* @param array $passes CompilerPassInterface instances with their priority as key
*
* @return CompilerPassInterface[]
*/
private function sortPasses(array $passes): array
{
if (0 === \count($passes)) {
return [];
}
krsort($passes);
// Flatten the array
return array_merge(...$passes);
}
}

View file

@ -0,0 +1,171 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Trait that allows a generic method to find and sort service by priority option in the tag.
*
* @author Iltar van der Berg <kjarli@gmail.com>
*/
trait PriorityTaggedServiceTrait
{
/**
* Finds all services with the given tag name and order them by their priority.
*
* The order of additions must be respected for services having the same priority,
* and knowing that the \SplPriorityQueue class does not respect the FIFO method,
* we should not use that class.
*
* @see https://bugs.php.net/53710
* @see https://bugs.php.net/60926
*
* @param string|TaggedIteratorArgument $tagName
*
* @return Reference[]
*/
private function findAndSortTaggedServices($tagName, ContainerBuilder $container): array
{
$indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null;
if ($tagName instanceof TaggedIteratorArgument) {
$indexAttribute = $tagName->getIndexAttribute();
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
$needsIndexes = $tagName->needsIndexes();
$defaultPriorityMethod = $tagName->getDefaultPriorityMethod() ?? 'getDefaultPriority';
$tagName = $tagName->getTag();
}
$i = 0;
$services = [];
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
$defaultPriority = null;
$defaultIndex = null;
$definition = $container->getDefinition($serviceId);
$class = $definition->getClass();
$class = $container->getParameterBag()->resolveValue($class) ?: null;
$checkTaggedItem = !$definition->hasTag(80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName);
foreach ($attributes as $attribute) {
$index = $priority = null;
if (isset($attribute['priority'])) {
$priority = $attribute['priority'];
} elseif (null === $defaultPriority && $defaultPriorityMethod && $class) {
$defaultPriority = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem);
}
$priority = $priority ?? $defaultPriority ?? $defaultPriority = 0;
if (null === $indexAttribute && !$defaultIndexMethod && !$needsIndexes) {
$services[] = [$priority, ++$i, null, $serviceId, null];
continue 2;
}
if (null !== $indexAttribute && isset($attribute[$indexAttribute])) {
$index = $attribute[$indexAttribute];
} elseif (null === $defaultIndex && $defaultPriorityMethod && $class) {
$defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem);
}
$index = $index ?? $defaultIndex ?? $defaultIndex = $serviceId;
$services[] = [$priority, ++$i, $index, $serviceId, $class];
}
}
uasort($services, static function ($a, $b) { return $b[0] <=> $a[0] ?: $a[1] <=> $b[1]; });
$refs = [];
foreach ($services as [, , $index, $serviceId, $class]) {
if (!$class) {
$reference = new Reference($serviceId);
} elseif ($index === $serviceId) {
$reference = new TypedReference($serviceId, $class);
} else {
$reference = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $index);
}
if (null === $index) {
$refs[] = $reference;
} else {
$refs[$index] = $reference;
}
}
return $refs;
}
}
/**
* @internal
*/
class PriorityTaggedServiceUtil
{
/**
* @return string|int|null
*/
public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem)
{
if (!($r = $container->getReflectionClass($class)) || (!$checkTaggedItem && !$r->hasMethod($defaultMethod))) {
return null;
}
if ($checkTaggedItem && !$r->hasMethod($defaultMethod)) {
foreach ($r->getAttributes(AsTaggedItem::class) as $attribute) {
return 'priority' === $indexAttribute ? $attribute->newInstance()->priority : $attribute->newInstance()->index;
}
return null;
}
if (null !== $indexAttribute) {
$service = $class !== $serviceId ? sprintf('service "%s"', $serviceId) : 'on the corresponding service';
$message = [sprintf('Either method "%s::%s()" should ', $class, $defaultMethod), sprintf(' or tag "%s" on %s is missing attribute "%s".', $tagName, $service, $indexAttribute)];
} else {
$message = [sprintf('Method "%s::%s()" should ', $class, $defaultMethod), '.'];
}
if (!($rm = $r->getMethod($defaultMethod))->isStatic()) {
throw new InvalidArgumentException(implode('be static', $message));
}
if (!$rm->isPublic()) {
throw new InvalidArgumentException(implode('be public', $message));
}
$default = $rm->invoke(null);
if ('priority' === $indexAttribute) {
if (!\is_int($default)) {
throw new InvalidArgumentException(implode(sprintf('return int (got "%s")', get_debug_type($default)), $message));
}
return $default;
}
if (\is_int($default)) {
$default = (string) $default;
}
if (!\is_string($default)) {
throw new InvalidArgumentException(implode(sprintf('return string|int (got "%s")', get_debug_type($default)), $message));
}
return $default;
}
}

View file

@ -0,0 +1,92 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
/**
* Reads #[Autoconfigure] attributes on definitions that are autoconfigured
* and don't have the "container.ignore_attributes" tag.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class RegisterAutoconfigureAttributesPass implements CompilerPassInterface
{
private static $registerForAutoconfiguration;
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (80000 > \PHP_VERSION_ID) {
return;
}
foreach ($container->getDefinitions() as $id => $definition) {
if ($this->accept($definition) && $class = $container->getReflectionClass($definition->getClass(), false)) {
$this->processClass($container, $class);
}
}
}
public function accept(Definition $definition): bool
{
return 80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() && !$definition->hasTag('container.ignore_attributes');
}
public function processClass(ContainerBuilder $container, \ReflectionClass $class)
{
foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
self::registerForAutoconfiguration($container, $class, $attribute);
}
}
private static function registerForAutoconfiguration(ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute)
{
if (self::$registerForAutoconfiguration) {
return (self::$registerForAutoconfiguration)($container, $class, $attribute);
}
$parseDefinitions = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinitions');
$parseDefinitions->setAccessible(true);
$yamlLoader = $parseDefinitions->getDeclaringClass()->newInstanceWithoutConstructor();
self::$registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinitions, $yamlLoader) {
$attribute = (array) $attribute->newInstance();
foreach ($attribute['tags'] ?? [] as $i => $tag) {
if (\is_array($tag) && [0] === array_keys($tag)) {
$attribute['tags'][$i] = [$class->name => $tag[0]];
}
}
$parseDefinitions->invoke(
$yamlLoader,
[
'services' => [
'_instanceof' => [
$class->name => [$container->registerForAutoconfiguration($class->name)] + $attribute,
],
],
],
$class->getFileName(),
false
);
};
return (self::$registerForAutoconfiguration)($container, $class, $attribute);
}
}

View file

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\EnvVarProcessor;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Reference;
/**
* Creates the container.env_var_processors_locator service.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterEnvVarProcessorsPass implements CompilerPassInterface
{
private const ALLOWED_TYPES = ['array', 'bool', 'float', 'int', 'string'];
public function process(ContainerBuilder $container)
{
$bag = $container->getParameterBag();
$types = [];
$processors = [];
foreach ($container->findTaggedServiceIds('container.env_var_processor') as $id => $tags) {
if (!$r = $container->getReflectionClass($class = $container->getDefinition($id)->getClass())) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
} elseif (!$r->isSubclassOf(EnvVarProcessorInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
}
foreach ($class::getProvidedTypes() as $prefix => $type) {
$processors[$prefix] = new Reference($id);
$types[$prefix] = self::validateProvidedTypes($type, $class);
}
}
if ($bag instanceof EnvPlaceholderParameterBag) {
foreach (EnvVarProcessor::getProvidedTypes() as $prefix => $type) {
if (!isset($types[$prefix])) {
$types[$prefix] = self::validateProvidedTypes($type, EnvVarProcessor::class);
}
}
$bag->setProvidedTypes($types);
}
if ($processors) {
$container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors))
->setPublic(true)
;
}
}
private static function validateProvidedTypes(string $types, string $class): array
{
$types = explode('|', $types);
foreach ($types as $type) {
if (!\in_array($type, self::ALLOWED_TYPES)) {
throw new InvalidArgumentException(sprintf('Invalid type "%s" returned by "%s::getProvidedTypes()", expected one of "%s".', $type, $class, implode('", "', self::ALLOWED_TYPES)));
}
}
return $types;
}
}

View file

@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterReverseContainerPass implements CompilerPassInterface
{
private $beforeRemoving;
private $serviceId;
private $tagName;
public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible')
{
if (1 < \func_num_args()) {
trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->beforeRemoving = $beforeRemoving;
$this->serviceId = $serviceId;
$this->tagName = $tagName;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->serviceId)) {
return;
}
$refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
$services = [];
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
$services[$id] = new Reference($id, $refType);
}
if ($this->beforeRemoving) {
// prevent inlining of the reverse container
$services[$this->serviceId] = new Reference($this->serviceId, $refType);
}
$locator = $container->getDefinition($this->serviceId)->getArgument(1);
if ($locator instanceof Reference) {
$locator = $container->getDefinition((string) $locator);
}
if ($locator instanceof Definition) {
foreach ($services as $id => $ref) {
$services[$id] = new ServiceClosureArgument($ref);
}
$locator->replaceArgument(0, $services);
} else {
$locator->setValues($services);
}
}
}

View file

@ -0,0 +1,131 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
* Compiler pass to register tagged services that require a service locator.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterServiceSubscribersPass extends AbstractRecursivePass
{
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) {
return parent::processValue($value, $isRoot);
}
$serviceMap = [];
$autowire = $value->isAutowired();
foreach ($value->getTag('container.service_subscriber') as $attributes) {
if (!$attributes) {
$autowire = true;
continue;
}
ksort($attributes);
if ([] !== array_diff(array_keys($attributes), ['id', 'key'])) {
throw new InvalidArgumentException(sprintf('The "container.service_subscriber" tag accepts only the "key" and "id" attributes, "%s" given for service "%s".', implode('", "', array_keys($attributes)), $this->currentId));
}
if (!\array_key_exists('id', $attributes)) {
throw new InvalidArgumentException(sprintf('Missing "id" attribute on "container.service_subscriber" tag with key="%s" for service "%s".', $attributes['key'], $this->currentId));
}
if (!\array_key_exists('key', $attributes)) {
$attributes['key'] = $attributes['id'];
}
if (isset($serviceMap[$attributes['key']])) {
continue;
}
$serviceMap[$attributes['key']] = new Reference($attributes['id']);
}
$class = $value->getClass();
if (!$r = $this->container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId));
}
if (!$r->isSubclassOf(ServiceSubscriberInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class));
}
$class = $r->name;
$replaceDeprecatedSession = $this->container->has('.session.deprecated') && $r->isSubclassOf(AbstractController::class);
$subscriberMap = [];
foreach ($class::getSubscribedServices() as $key => $type) {
if (!\is_string($type) || !preg_match('/(?(DEFINE)(?<cn>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?<fqcn>(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) {
throw new InvalidArgumentException(sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type)));
}
if ($optionalBehavior = '?' === $type[0]) {
$type = substr($type, 1);
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
}
if (\is_int($name = $key)) {
$key = $type;
$name = null;
}
if (!isset($serviceMap[$key])) {
if (!$autowire) {
throw new InvalidArgumentException(sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $this->currentId, $key, $class));
}
if ($replaceDeprecatedSession && SessionInterface::class === $type) {
// This prevents triggering the deprecation when building the container
// Should be removed in Symfony 6.0
$type = '.session.deprecated';
}
$serviceMap[$key] = new Reference($type);
}
if ($name) {
if (false !== $i = strpos($name, '::get')) {
$name = lcfirst(substr($name, 5 + $i));
} elseif (str_contains($name, '::')) {
$name = null;
}
}
if (null !== $name && !$this->container->has($name) && !$this->container->has($type.' $'.$name)) {
$camelCaseName = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name))));
$name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name;
}
$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $name);
unset($serviceMap[$key]);
}
if ($serviceMap = array_keys($serviceMap)) {
$message = sprintf(1 < \count($serviceMap) ? 'keys "%s" do' : 'key "%s" does', str_replace('%', '%%', implode('", "', $serviceMap)));
throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId));
}
$locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId);
$value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]);
$value->setBindings([
PsrContainerInterface::class => new BoundArgument($locatorRef, false),
ServiceProviderInterface::class => new BoundArgument($locatorRef, false),
] + $value->getBindings());
return parent::processValue($value);
}
}

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Removes abstract Definitions.
*/
class RemoveAbstractDefinitionsPass implements CompilerPassInterface
{
/**
* Removes abstract definitions from the ContainerBuilder.
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isAbstract()) {
$container->removeDefinition($id);
$container->log($this, sprintf('Removed service "%s"; reason: abstract.', $id));
}
}
}
}

View file

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Remove private aliases from the container. They were only used to establish
* dependencies between services, and these dependencies have been resolved in
* one of the previous passes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RemovePrivateAliasesPass implements CompilerPassInterface
{
/**
* Removes private aliases from the ContainerBuilder.
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getAliases() as $id => $alias) {
if ($alias->isPublic()) {
continue;
}
$container->removeAlias($id);
$container->log($this, sprintf('Removed service "%s"; reason: private alias.', $id));
}
}
}

View file

@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Removes unused service definitions from the container.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class RemoveUnusedDefinitionsPass extends AbstractRecursivePass
{
private $connectedIds = [];
/**
* Processes the ContainerBuilder to remove unused definitions.
*/
public function process(ContainerBuilder $container)
{
try {
$this->enableExpressionProcessing();
$this->container = $container;
$connectedIds = [];
$aliases = $container->getAliases();
foreach ($aliases as $id => $alias) {
if ($alias->isPublic()) {
$this->connectedIds[] = (string) $aliases[$id];
}
}
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isPublic()) {
$connectedIds[$id] = true;
$this->processValue($definition);
}
}
while ($this->connectedIds) {
$ids = $this->connectedIds;
$this->connectedIds = [];
foreach ($ids as $id) {
if (!isset($connectedIds[$id]) && $container->hasDefinition($id)) {
$connectedIds[$id] = true;
$this->processValue($container->getDefinition($id));
}
}
}
foreach ($container->getDefinitions() as $id => $definition) {
if (!isset($connectedIds[$id])) {
$container->removeDefinition($id);
$container->resolveEnvPlaceholders(!$definition->hasErrors() ? serialize($definition) : $definition);
$container->log($this, sprintf('Removed service "%s"; reason: unused.', $id));
}
}
} finally {
$this->container = null;
$this->connectedIds = [];
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
}
if (ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior()) {
$this->connectedIds[] = (string) $value;
}
return $value;
}
}

View file

@ -0,0 +1,120 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Replaces aliases with actual service definitions, effectively removing these
* aliases.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ReplaceAliasByActualDefinitionPass extends AbstractRecursivePass
{
private $replacements;
private $autoAliasServicePass;
/**
* @internal to be removed in Symfony 6.0
*
* @return $this
*/
public function setAutoAliasServicePass(AutoAliasServicePass $autoAliasServicePass): self
{
$this->autoAliasServicePass = $autoAliasServicePass;
return $this;
}
/**
* Process the Container to replace aliases with service definitions.
*
* @throws InvalidArgumentException if the service definition does not exist
*/
public function process(ContainerBuilder $container)
{
// First collect all alias targets that need to be replaced
$seenAliasTargets = [];
$replacements = [];
$privateAliases = $this->autoAliasServicePass ? $this->autoAliasServicePass->getPrivateAliases() : [];
foreach ($privateAliases as $target) {
$target->setDeprecated('symfony/dependency-injection', '5.4', 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.');
}
foreach ($container->getAliases() as $definitionId => $target) {
$targetId = (string) $target;
// Special case: leave this target alone
if ('service_container' === $targetId) {
continue;
}
// Check if target needs to be replaced
if (isset($replacements[$targetId])) {
$container->setAlias($definitionId, $replacements[$targetId])->setPublic($target->isPublic());
if ($target->isDeprecated()) {
$container->getAlias($definitionId)->setDeprecated(...array_values($target->getDeprecation('%alias_id%')));
}
}
// No need to process the same target twice
if (isset($seenAliasTargets[$targetId])) {
continue;
}
// Process new target
$seenAliasTargets[$targetId] = true;
try {
$definition = $container->getDefinition($targetId);
} catch (ServiceNotFoundException $e) {
if ('' !== $e->getId() && '@' === $e->getId()[0]) {
throw new ServiceNotFoundException($e->getId(), $e->getSourceId(), null, [substr($e->getId(), 1)]);
}
throw $e;
}
if ($definition->isPublic()) {
continue;
}
// Remove private definition and schedule for replacement
$definition->setPublic($target->isPublic());
$container->setDefinition($definitionId, $definition);
$container->removeDefinition($targetId);
$replacements[$targetId] = $definitionId;
if ($target->isPublic() && $target->isDeprecated()) {
$definition->addTag('container.private', $target->getDeprecation('%service_id%'));
}
}
$this->replacements = $replacements;
parent::process($container);
$this->replacements = [];
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && isset($this->replacements[$referenceId = (string) $value])) {
// Perform the replacement
$newId = $this->replacements[$referenceId];
$value = new Reference($newId, $value->getInvalidBehavior());
$this->container->log($this, sprintf('Changed reference of service "%s" previously pointing to "%s" to "%s".', $this->currentId, $referenceId, $newId));
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,260 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\Attribute\Target;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* @author Guilhem Niot <guilhem.niot@gmail.com>
*/
class ResolveBindingsPass extends AbstractRecursivePass
{
private $usedBindings = [];
private $unusedBindings = [];
private $errorMessages = [];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->usedBindings = $container->getRemovedBindingIds();
try {
parent::process($container);
foreach ($this->unusedBindings as [$key, $serviceId, $bindingType, $file]) {
$argumentType = $argumentName = $message = null;
if (str_contains($key, ' ')) {
[$argumentType, $argumentName] = explode(' ', $key, 2);
} elseif ('$' === $key[0]) {
$argumentName = $key;
} else {
$argumentType = $key;
}
if ($argumentType) {
$message .= sprintf('of type "%s" ', $argumentType);
}
if ($argumentName) {
$message .= sprintf('named "%s" ', $argumentName);
}
if (BoundArgument::DEFAULTS_BINDING === $bindingType) {
$message .= 'under "_defaults"';
} elseif (BoundArgument::INSTANCEOF_BINDING === $bindingType) {
$message .= 'under "_instanceof"';
} else {
$message .= sprintf('for service "%s"', $serviceId);
}
if ($file) {
$message .= sprintf(' in file "%s"', $file);
}
$message = sprintf('A binding is configured for an argument %s, but no corresponding argument has been found. It may be unused and should be removed, or it may have a typo.', $message);
if ($this->errorMessages) {
$message .= sprintf("\nCould be related to%s:", 1 < \count($this->errorMessages) ? ' one of' : '');
}
foreach ($this->errorMessages as $m) {
$message .= "\n - ".$m;
}
throw new InvalidArgumentException($message);
}
} finally {
$this->usedBindings = [];
$this->unusedBindings = [];
$this->errorMessages = [];
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof TypedReference && $value->getType() === (string) $value) {
// Already checked
$bindings = $this->container->getDefinition($this->currentId)->getBindings();
$name = $value->getName();
if (isset($name, $bindings[$name = $value.' $'.$name])) {
return $this->getBindingValue($bindings[$name]);
}
if (isset($bindings[$value->getType()])) {
return $this->getBindingValue($bindings[$value->getType()]);
}
return parent::processValue($value, $isRoot);
}
if (!$value instanceof Definition || !$bindings = $value->getBindings()) {
return parent::processValue($value, $isRoot);
}
$bindingNames = [];
foreach ($bindings as $key => $binding) {
[$bindingValue, $bindingId, $used, $bindingType, $file] = $binding->getValues();
if ($used) {
$this->usedBindings[$bindingId] = true;
unset($this->unusedBindings[$bindingId]);
} elseif (!isset($this->usedBindings[$bindingId])) {
$this->unusedBindings[$bindingId] = [$key, $this->currentId, $bindingType, $file];
}
if (preg_match('/^(?:(?:array|bool|float|int|string|iterable|([^ $]++)) )\$/', $key, $m)) {
$bindingNames[substr($key, \strlen($m[0]))] = $binding;
}
if (!isset($m[1])) {
continue;
}
if (is_subclass_of($m[1], \UnitEnum::class)) {
$bindingNames[substr($key, \strlen($m[0]))] = $binding;
continue;
}
if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition && !$bindingValue instanceof TaggedIteratorArgument && !$bindingValue instanceof ServiceLocatorArgument) {
throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected "%s", "%s", "%s", "%s" or null, "%s" given.', $key, $this->currentId, Reference::class, Definition::class, TaggedIteratorArgument::class, ServiceLocatorArgument::class, get_debug_type($bindingValue)));
}
}
if ($value->isAbstract()) {
return parent::processValue($value, $isRoot);
}
$calls = $value->getMethodCalls();
try {
if ($constructor = $this->getConstructor($value, false)) {
$calls[] = [$constructor, $value->getArguments()];
}
} catch (RuntimeException $e) {
$this->errorMessages[] = $e->getMessage();
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
return parent::processValue($value, $isRoot);
}
foreach ($calls as $i => $call) {
[$method, $arguments] = $call;
if ($method instanceof \ReflectionFunctionAbstract) {
$reflectionMethod = $method;
} else {
try {
$reflectionMethod = $this->getReflectionMethod($value, $method);
} catch (RuntimeException $e) {
if ($value->getFactory()) {
continue;
}
throw $e;
}
}
$names = [];
foreach ($reflectionMethod->getParameters() as $key => $parameter) {
$names[$key] = $parameter->name;
if (\array_key_exists($key, $arguments) && '' !== $arguments[$key]) {
continue;
}
if (\array_key_exists($parameter->name, $arguments) && '' !== $arguments[$parameter->name]) {
continue;
}
$typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter);
$name = Target::parseName($parameter);
if ($typeHint && \array_key_exists($k = ltrim($typeHint, '\\').' $'.$name, $bindings)) {
$arguments[$key] = $this->getBindingValue($bindings[$k]);
continue;
}
if (\array_key_exists('$'.$name, $bindings)) {
$arguments[$key] = $this->getBindingValue($bindings['$'.$name]);
continue;
}
if ($typeHint && '\\' === $typeHint[0] && isset($bindings[$typeHint = substr($typeHint, 1)])) {
$arguments[$key] = $this->getBindingValue($bindings[$typeHint]);
continue;
}
if (isset($bindingNames[$name]) || isset($bindingNames[$parameter->name])) {
$bindingKey = array_search($binding, $bindings, true);
$argumentType = substr($bindingKey, 0, strpos($bindingKey, ' '));
$this->errorMessages[] = sprintf('Did you forget to add the type "%s" to argument "$%s" of method "%s::%s()"?', $argumentType, $parameter->name, $reflectionMethod->class, $reflectionMethod->name);
}
}
foreach ($names as $key => $name) {
if (\array_key_exists($name, $arguments) && (0 === $key || \array_key_exists($key - 1, $arguments))) {
$arguments[$key] = $arguments[$name];
unset($arguments[$name]);
}
}
if ($arguments !== $call[1]) {
ksort($arguments, \SORT_NATURAL);
$calls[$i][1] = $arguments;
}
}
if ($constructor) {
[, $arguments] = array_pop($calls);
if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
}
if ($calls !== $value->getMethodCalls()) {
$value->setMethodCalls($calls);
}
return parent::processValue($value, $isRoot);
}
/**
* @return mixed
*/
private function getBindingValue(BoundArgument $binding)
{
[$bindingValue, $bindingId] = $binding->getValues();
$this->usedBindings[$bindingId] = true;
unset($this->unusedBindings[$bindingId]);
return $bindingValue;
}
}

View file

@ -0,0 +1,204 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ExceptionInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
/**
* This replaces all ChildDefinition instances with their equivalent fully
* merged Definition instance.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveChildDefinitionsPass extends AbstractRecursivePass
{
private $currentPath;
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if ($isRoot) {
// yes, we are specifically fetching the definition from the
// container to ensure we are not operating on stale data
$value = $this->container->getDefinition($this->currentId);
}
if ($value instanceof ChildDefinition) {
$this->currentPath = [];
$value = $this->resolveDefinition($value);
if ($isRoot) {
$this->container->setDefinition($this->currentId, $value);
}
}
return parent::processValue($value, $isRoot);
}
/**
* Resolves the definition.
*
* @throws RuntimeException When the definition is invalid
*/
private function resolveDefinition(ChildDefinition $definition): Definition
{
try {
return $this->doResolveDefinition($definition);
} catch (ServiceCircularReferenceException $e) {
throw $e;
} catch (ExceptionInterface $e) {
$r = new \ReflectionProperty($e, 'message');
$r->setAccessible(true);
$r->setValue($e, sprintf('Service "%s": %s', $this->currentId, $e->getMessage()));
throw $e;
}
}
private function doResolveDefinition(ChildDefinition $definition): Definition
{
if (!$this->container->has($parent = $definition->getParent())) {
throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent));
}
$searchKey = array_search($parent, $this->currentPath);
$this->currentPath[] = $parent;
if (false !== $searchKey) {
throw new ServiceCircularReferenceException($parent, \array_slice($this->currentPath, $searchKey));
}
$parentDef = $this->container->findDefinition($parent);
if ($parentDef instanceof ChildDefinition) {
$id = $this->currentId;
$this->currentId = $parent;
$parentDef = $this->resolveDefinition($parentDef);
$this->container->setDefinition($parent, $parentDef);
$this->currentId = $id;
}
$this->container->log($this, sprintf('Resolving inheritance for "%s" (parent: %s).', $this->currentId, $parent));
$def = new Definition();
// merge in parent definition
// purposely ignored attributes: abstract, shared, tags, autoconfigured
$def->setClass($parentDef->getClass());
$def->setArguments($parentDef->getArguments());
$def->setMethodCalls($parentDef->getMethodCalls());
$def->setProperties($parentDef->getProperties());
if ($parentDef->isDeprecated()) {
$deprecation = $parentDef->getDeprecation('%service_id%');
$def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']);
}
$def->setFactory($parentDef->getFactory());
$def->setConfigurator($parentDef->getConfigurator());
$def->setFile($parentDef->getFile());
$def->setPublic($parentDef->isPublic());
$def->setLazy($parentDef->isLazy());
$def->setAutowired($parentDef->isAutowired());
$def->setChanges($parentDef->getChanges());
$def->setBindings($definition->getBindings() + $parentDef->getBindings());
$def->setSynthetic($definition->isSynthetic());
// overwrite with values specified in the decorator
$changes = $definition->getChanges();
if (isset($changes['class'])) {
$def->setClass($definition->getClass());
}
if (isset($changes['factory'])) {
$def->setFactory($definition->getFactory());
}
if (isset($changes['configurator'])) {
$def->setConfigurator($definition->getConfigurator());
}
if (isset($changes['file'])) {
$def->setFile($definition->getFile());
}
if (isset($changes['public'])) {
$def->setPublic($definition->isPublic());
} else {
$def->setPublic($parentDef->isPublic());
}
if (isset($changes['lazy'])) {
$def->setLazy($definition->isLazy());
}
if (isset($changes['deprecated'])) {
if ($definition->isDeprecated()) {
$deprecation = $definition->getDeprecation('%service_id%');
$def->setDeprecated($deprecation['package'], $deprecation['version'], $deprecation['message']);
} else {
$def->setDeprecated(false);
}
}
if (isset($changes['autowired'])) {
$def->setAutowired($definition->isAutowired());
}
if (isset($changes['shared'])) {
$def->setShared($definition->isShared());
}
if (isset($changes['decorated_service'])) {
$decoratedService = $definition->getDecoratedService();
if (null === $decoratedService) {
$def->setDecoratedService($decoratedService);
} else {
$def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2], $decoratedService[3] ?? ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
}
}
// merge arguments
foreach ($definition->getArguments() as $k => $v) {
if (is_numeric($k)) {
$def->addArgument($v);
} elseif (str_starts_with($k, 'index_')) {
$def->replaceArgument((int) substr($k, \strlen('index_')), $v);
} else {
$def->setArgument($k, $v);
}
}
// merge properties
foreach ($definition->getProperties() as $k => $v) {
$def->setProperty($k, $v);
}
// append method calls
if ($calls = $definition->getMethodCalls()) {
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
}
$def->addError($parentDef);
$def->addError($definition);
// these attributes are always taken from the child
$def->setAbstract($definition->isAbstract());
$def->setTags($definition->getTags());
// autoconfigure is never taken from parent (on purpose)
// and it's not legal on an instanceof
$def->setAutoconfigured($definition->isAutoconfigured());
if (!$def->hasTag('proxy')) {
foreach ($parentDef->getTag('proxy') as $v) {
$def->addTag('proxy', $v);
}
}
return $def;
}
}

View file

@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveClassPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isSynthetic() || null !== $definition->getClass()) {
continue;
}
if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $id)) {
if ($definition instanceof ChildDefinition && !class_exists($id)) {
throw new InvalidArgumentException(sprintf('Service definition "%s" has a parent but no class, and its name looks like an FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id));
}
$definition->setClass($id);
}
}
}
}

View file

@ -0,0 +1,131 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveDecoratorStackPass implements CompilerPassInterface
{
private $tag;
public function __construct(string $tag = 'container.stack')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->tag = $tag;
}
public function process(ContainerBuilder $container)
{
$stacks = [];
foreach ($container->findTaggedServiceIds($this->tag) as $id => $tags) {
$definition = $container->getDefinition($id);
if (!$definition instanceof ChildDefinition) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": only definitions with a "parent" can have the "%s" tag.', $id, $this->tag));
}
if (!$stack = $definition->getArguments()) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": the stack of decorators is empty.', $id));
}
$stacks[$id] = $stack;
}
if (!$stacks) {
return;
}
$resolvedDefinitions = [];
foreach ($container->getDefinitions() as $id => $definition) {
if (!isset($stacks[$id])) {
$resolvedDefinitions[$id] = $definition;
continue;
}
foreach (array_reverse($this->resolveStack($stacks, [$id]), true) as $k => $v) {
$resolvedDefinitions[$k] = $v;
}
$alias = $container->setAlias($id, $k);
if ($definition->getChanges()['public'] ?? false) {
$alias->setPublic($definition->isPublic());
}
if ($definition->isDeprecated()) {
$alias->setDeprecated(...array_values($definition->getDeprecation('%alias_id%')));
}
}
$container->setDefinitions($resolvedDefinitions);
}
private function resolveStack(array $stacks, array $path): array
{
$definitions = [];
$id = end($path);
$prefix = '.'.$id.'.';
if (!isset($stacks[$id])) {
return [$id => new ChildDefinition($id)];
}
if (key($path) !== $searchKey = array_search($id, $path)) {
throw new ServiceCircularReferenceException($id, \array_slice($path, $searchKey));
}
foreach ($stacks[$id] as $k => $definition) {
if ($definition instanceof ChildDefinition && isset($stacks[$definition->getParent()])) {
$path[] = $definition->getParent();
$definition = unserialize(serialize($definition)); // deep clone
} elseif ($definition instanceof Definition) {
$definitions[$decoratedId = $prefix.$k] = $definition;
continue;
} elseif ($definition instanceof Reference || $definition instanceof Alias) {
$path[] = (string) $definition;
} else {
throw new InvalidArgumentException(sprintf('Invalid service "%s": unexpected value of type "%s" found in the stack of decorators.', $id, get_debug_type($definition)));
}
$p = $prefix.$k;
foreach ($this->resolveStack($stacks, $path) as $k => $v) {
$definitions[$decoratedId = $p.$k] = $definition instanceof ChildDefinition ? $definition->setParent($k) : new ChildDefinition($k);
$definition = null;
}
array_pop($path);
}
if (1 === \count($path)) {
foreach ($definitions as $k => $definition) {
$definition->setPublic(false)->setTags([])->setDecoratedService($decoratedId);
}
$definition->setDecoratedService(null);
}
return $definitions;
}
}

View file

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
/**
* Replaces env var placeholders by their current values.
*/
class ResolveEnvPlaceholdersPass extends AbstractRecursivePass
{
protected function processValue($value, bool $isRoot = false)
{
if (\is_string($value)) {
return $this->container->resolveEnvPlaceholders($value, true);
}
if ($value instanceof Definition) {
$changes = $value->getChanges();
if (isset($changes['class'])) {
$value->setClass($this->container->resolveEnvPlaceholders($value->getClass(), true));
}
if (isset($changes['file'])) {
$value->setFile($this->container->resolveEnvPlaceholders($value->getFile(), true));
}
}
$value = parent::processValue($value, $isRoot);
if ($value && \is_array($value) && !$isRoot) {
$value = array_combine($this->container->resolveEnvPlaceholders(array_keys($value), true), $value);
}
return $value;
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class ResolveFactoryClassPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Definition && \is_array($factory = $value->getFactory()) && null === $factory[0]) {
if (null === $class = $value->getClass()) {
throw new RuntimeException(sprintf('The "%s" service is defined to be created by a factory, but is missing the factory class. Did you forget to define the factory or service class?', $this->currentId));
}
$factory[0] = $class;
$value->setFactory($factory);
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Propagate "container.hot_path" tags to referenced services.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveHotPathPass extends AbstractRecursivePass
{
private $tagName;
private $resolvedIds = [];
public function __construct(string $tagName = 'container.hot_path')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
try {
parent::process($container);
$container->getDefinition('service_container')->clearTag($this->tagName);
} finally {
$this->resolvedIds = [];
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof ArgumentInterface) {
return $value;
}
if ($value instanceof Definition && $isRoot) {
if ($value->isDeprecated()) {
return $value->clearTag($this->tagName);
}
$this->resolvedIds[$this->currentId] = true;
if (!$value->hasTag($this->tagName)) {
return $value;
}
}
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) {
$definition = $this->container->getDefinition($id);
if ($definition->isDeprecated() || $definition->hasTag($this->tagName)) {
return $value;
}
$definition->addTag($this->tagName);
if (isset($this->resolvedIds[$id])) {
parent::processValue($definition, false);
}
return $value;
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,177 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* Applies instanceof conditionals to definitions.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveInstanceofConditionalsPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getAutoconfiguredInstanceof() as $interface => $definition) {
if ($definition->getArguments()) {
throw new InvalidArgumentException(sprintf('Autoconfigured instanceof for type "%s" defines arguments but these are not supported and should be removed.', $interface));
}
}
$tagsToKeep = [];
if ($container->hasParameter('container.behavior_describing_tags')) {
$tagsToKeep = $container->getParameter('container.behavior_describing_tags');
}
foreach ($container->getDefinitions() as $id => $definition) {
$container->setDefinition($id, $this->processDefinition($container, $id, $definition, $tagsToKeep));
}
if ($container->hasParameter('container.behavior_describing_tags')) {
$container->getParameterBag()->remove('container.behavior_describing_tags');
}
}
private function processDefinition(ContainerBuilder $container, string $id, Definition $definition, array $tagsToKeep): Definition
{
$instanceofConditionals = $definition->getInstanceofConditionals();
$autoconfiguredInstanceof = $definition->isAutoconfigured() ? $container->getAutoconfiguredInstanceof() : [];
if (!$instanceofConditionals && !$autoconfiguredInstanceof) {
return $definition;
}
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
return $definition;
}
$conditionals = $this->mergeConditionals($autoconfiguredInstanceof, $instanceofConditionals, $container);
$definition->setInstanceofConditionals([]);
$shared = null;
$instanceofTags = [];
$instanceofCalls = [];
$instanceofBindings = [];
$reflectionClass = null;
$parent = $definition instanceof ChildDefinition ? $definition->getParent() : null;
foreach ($conditionals as $interface => $instanceofDefs) {
if ($interface !== $class && !($reflectionClass ?? $reflectionClass = $container->getReflectionClass($class, false) ?: false)) {
continue;
}
if ($interface !== $class && !is_subclass_of($class, $interface)) {
continue;
}
foreach ($instanceofDefs as $key => $instanceofDef) {
/** @var ChildDefinition $instanceofDef */
$instanceofDef = clone $instanceofDef;
$instanceofDef->setAbstract(true)->setParent($parent ?: '.abstract.instanceof.'.$id);
$parent = '.instanceof.'.$interface.'.'.$key.'.'.$id;
$container->setDefinition($parent, $instanceofDef);
$instanceofTags[] = $instanceofDef->getTags();
$instanceofBindings = $instanceofDef->getBindings() + $instanceofBindings;
foreach ($instanceofDef->getMethodCalls() as $methodCall) {
$instanceofCalls[] = $methodCall;
}
$instanceofDef->setTags([]);
$instanceofDef->setMethodCalls([]);
$instanceofDef->setBindings([]);
if (isset($instanceofDef->getChanges()['shared'])) {
$shared = $instanceofDef->isShared();
}
}
}
if ($parent) {
$bindings = $definition->getBindings();
$abstract = $container->setDefinition('.abstract.instanceof.'.$id, $definition);
$definition->setBindings([]);
$definition = serialize($definition);
if (Definition::class === \get_class($abstract)) {
// cast Definition to ChildDefinition
$definition = substr_replace($definition, '53', 2, 2);
$definition = substr_replace($definition, 'Child', 44, 0);
}
/** @var ChildDefinition $definition */
$definition = unserialize($definition);
$definition->setParent($parent);
if (null !== $shared && !isset($definition->getChanges()['shared'])) {
$definition->setShared($shared);
}
// Don't add tags to service decorators
$i = \count($instanceofTags);
while (0 <= --$i) {
foreach ($instanceofTags[$i] as $k => $v) {
if (null === $definition->getDecoratedService() || \in_array($k, $tagsToKeep, true)) {
foreach ($v as $v) {
if ($definition->hasTag($k) && \in_array($v, $definition->getTag($k))) {
continue;
}
$definition->addTag($k, $v);
}
}
}
}
$definition->setMethodCalls(array_merge($instanceofCalls, $definition->getMethodCalls()));
$definition->setBindings($bindings + $instanceofBindings);
// reset fields with "merge" behavior
$abstract
->setBindings([])
->setArguments([])
->setMethodCalls([])
->setDecoratedService(null)
->setTags([])
->setAbstract(true);
}
return $definition;
}
private function mergeConditionals(array $autoconfiguredInstanceof, array $instanceofConditionals, ContainerBuilder $container): array
{
// make each value an array of ChildDefinition
$conditionals = array_map(function ($childDef) { return [$childDef]; }, $autoconfiguredInstanceof);
foreach ($instanceofConditionals as $interface => $instanceofDef) {
// make sure the interface/class exists (but don't validate automaticInstanceofConditionals)
if (!$container->getReflectionClass($interface)) {
throw new RuntimeException(sprintf('"%s" is set as an "instanceof" conditional, but it does not exist.', $interface));
}
if (!isset($autoconfiguredInstanceof[$interface])) {
$conditionals[$interface] = [];
}
$conditionals[$interface][] = $instanceofDef;
}
return $conditionals;
}
}

View file

@ -0,0 +1,136 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Emulates the invalid behavior if the reference is not found within the
* container.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ResolveInvalidReferencesPass implements CompilerPassInterface
{
private $container;
private $signalingException;
private $currentId;
/**
* Process the ContainerBuilder to resolve invalid references.
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->signalingException = new RuntimeException('Invalid reference.');
try {
foreach ($container->getDefinitions() as $this->currentId => $definition) {
$this->processValue($definition);
}
} finally {
$this->container = $this->signalingException = null;
}
}
/**
* Processes arguments to determine invalid references.
*
* @return mixed
*
* @throws RuntimeException When an invalid reference is found
*/
private function processValue($value, int $rootLevel = 0, int $level = 0)
{
if ($value instanceof ServiceClosureArgument) {
$value->setValues($this->processValue($value->getValues(), 1, 1));
} elseif ($value instanceof ArgumentInterface) {
$value->setValues($this->processValue($value->getValues(), $rootLevel, 1 + $level));
} elseif ($value instanceof Definition) {
if ($value->isSynthetic() || $value->isAbstract()) {
return $value;
}
$value->setArguments($this->processValue($value->getArguments(), 0));
$value->setProperties($this->processValue($value->getProperties(), 1));
$value->setMethodCalls($this->processValue($value->getMethodCalls(), 2));
} elseif (\is_array($value)) {
$i = 0;
foreach ($value as $k => $v) {
try {
if (false !== $i && $k !== $i++) {
$i = false;
}
if ($v !== $processedValue = $this->processValue($v, $rootLevel, 1 + $level)) {
$value[$k] = $processedValue;
}
} catch (RuntimeException $e) {
if ($rootLevel < $level || ($rootLevel && !$level)) {
unset($value[$k]);
} elseif ($rootLevel) {
throw $e;
} else {
$value[$k] = null;
}
}
}
// Ensure numerically indexed arguments have sequential numeric keys.
if (false !== $i) {
$value = array_values($value);
}
} elseif ($value instanceof Reference) {
if ($this->container->has($id = (string) $value)) {
return $value;
}
$currentDefinition = $this->container->getDefinition($this->currentId);
// resolve decorated service behavior depending on decorator service
if ($currentDefinition->innerServiceId === $id && ContainerInterface::NULL_ON_INVALID_REFERENCE === $currentDefinition->decorationOnInvalid) {
return null;
}
$invalidBehavior = $value->getInvalidBehavior();
if (ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior && $value instanceof TypedReference && !$this->container->has($id)) {
$e = new ServiceNotFoundException($id, $this->currentId);
// since the error message varies by $id and $this->currentId, so should the id of the dummy errored definition
$this->container->register($id = sprintf('.errored.%s.%s', $this->currentId, $id), $value->getType())
->addError($e->getMessage());
return new TypedReference($id, $value->getType(), $value->getInvalidBehavior());
}
// resolve invalid behavior
if (ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
$value = null;
} elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) {
if (0 < $level || $rootLevel) {
throw $this->signalingException;
}
$value = null;
}
}
return $value;
}
}

View file

@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\Reference;
/**
* Resolves named arguments to their corresponding numeric index.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ResolveNamedArgumentsPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof AbstractArgument && $value->getText().'.' === $value->getTextWithContext()) {
$value->setContext(sprintf('A value found in service "%s"', $this->currentId));
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
$calls = $value->getMethodCalls();
$calls[] = ['__construct', $value->getArguments()];
foreach ($calls as $i => $call) {
[$method, $arguments] = $call;
$parameters = null;
$resolvedKeys = [];
$resolvedArguments = [];
foreach ($arguments as $key => $argument) {
if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) {
$argument->setContext(sprintf('Argument '.(\is_int($key) ? 1 + $key : '"%3$s"').' of '.('__construct' === $method ? 'service "%s"' : 'method call "%s::%s()"'), $this->currentId, $method, $key));
}
if (\is_int($key)) {
$resolvedKeys[$key] = $key;
$resolvedArguments[$key] = $argument;
continue;
}
if (null === $parameters) {
$r = $this->getReflectionMethod($value, $method);
$class = $r instanceof \ReflectionMethod ? $r->class : $this->currentId;
$method = $r->getName();
$parameters = $r->getParameters();
}
if (isset($key[0]) && '$' !== $key[0] && !class_exists($key) && !interface_exists($key, false)) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": did you forget to add the "$" prefix to argument "%s"?', $this->currentId, $key));
}
if (isset($key[0]) && '$' === $key[0]) {
foreach ($parameters as $j => $p) {
if ($key === '$'.$p->name) {
if ($p->isVariadic() && \is_array($argument)) {
foreach ($argument as $variadicArgument) {
$resolvedKeys[$j] = $j;
$resolvedArguments[$j++] = $variadicArgument;
}
} else {
$resolvedKeys[$j] = $p->name;
$resolvedArguments[$j] = $argument;
}
continue 2;
}
}
throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}
if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": the value of argument "%s" of method "%s()" must be null, an instance of "%s" or an instance of "%s", "%s" given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, get_debug_type($argument)));
}
$typeFound = false;
foreach ($parameters as $j => $p) {
if (!\array_key_exists($j, $resolvedArguments) && ProxyHelper::getTypeHint($r, $p, true) === $key) {
$resolvedKeys[$j] = $p->name;
$resolvedArguments[$j] = $argument;
$typeFound = true;
}
}
if (!$typeFound) {
throw new InvalidArgumentException(sprintf('Invalid service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}
}
if ($resolvedArguments !== $call[1]) {
ksort($resolvedArguments);
if (!$value->isAutowired() && !array_is_list($resolvedArguments)) {
ksort($resolvedKeys);
$resolvedArguments = array_combine($resolvedKeys, $resolvedArguments);
}
$calls[$i][1] = $resolvedArguments;
}
}
[, $arguments] = array_pop($calls);
if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
if ($calls !== $value->getMethodCalls()) {
$value->setMethodCalls($calls);
}
foreach ($value->getProperties() as $key => $argument) {
if ($argument instanceof AbstractArgument && $argument->getText().'.' === $argument->getTextWithContext()) {
$argument->setContext(sprintf('Property "%s" of service "%s"', $key, $this->currentId));
}
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Propagate the "container.no_preload" tag.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveNoPreloadPass extends AbstractRecursivePass
{
private const DO_PRELOAD_TAG = '.container.do_preload';
private $tagName;
private $resolvedIds = [];
public function __construct(string $tagName = 'container.no_preload')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/dependency-injection', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
try {
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isPublic() && !$definition->isPrivate() && !isset($this->resolvedIds[$id])) {
$this->resolvedIds[$id] = true;
$this->processValue($definition, true);
}
}
foreach ($container->getAliases() as $alias) {
if ($alias->isPublic() && !$alias->isPrivate() && !isset($this->resolvedIds[$id = (string) $alias]) && $container->hasDefinition($id)) {
$this->resolvedIds[$id] = true;
$this->processValue($container->getDefinition($id), true);
}
}
} finally {
$this->resolvedIds = [];
$this->container = null;
}
foreach ($container->getDefinitions() as $definition) {
if ($definition->hasTag(self::DO_PRELOAD_TAG)) {
$definition->clearTag(self::DO_PRELOAD_TAG);
} elseif (!$definition->isDeprecated() && !$definition->hasErrors()) {
$definition->addTag($this->tagName);
}
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->hasDefinition($id = (string) $value)) {
$definition = $this->container->getDefinition($id);
if (!isset($this->resolvedIds[$id]) && (!$definition->isPublic() || $definition->isPrivate())) {
$this->resolvedIds[$id] = true;
$this->processValue($definition, true);
}
return $value;
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
if ($value->hasTag($this->tagName) || $value->isDeprecated() || $value->hasErrors()) {
return $value;
}
if ($isRoot) {
$value->addTag(self::DO_PRELOAD_TAG);
}
return parent::processValue($value, $isRoot);
}
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
/**
* Resolves all parameter placeholders "%somevalue%" to their real values.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ResolveParameterPlaceHoldersPass extends AbstractRecursivePass
{
private $bag;
private $resolveArrays;
private $throwOnResolveException;
public function __construct($resolveArrays = true, $throwOnResolveException = true)
{
$this->resolveArrays = $resolveArrays;
$this->throwOnResolveException = $throwOnResolveException;
}
/**
* {@inheritdoc}
*
* @throws ParameterNotFoundException
*/
public function process(ContainerBuilder $container)
{
$this->bag = $container->getParameterBag();
try {
parent::process($container);
$aliases = [];
foreach ($container->getAliases() as $name => $target) {
$this->currentId = $name;
$aliases[$this->bag->resolveValue($name)] = $target;
}
$container->setAliases($aliases);
} catch (ParameterNotFoundException $e) {
$e->setSourceId($this->currentId);
throw $e;
}
$this->bag->resolve();
$this->bag = null;
}
protected function processValue($value, bool $isRoot = false)
{
if (\is_string($value)) {
try {
$v = $this->bag->resolveValue($value);
} catch (ParameterNotFoundException $e) {
if ($this->throwOnResolveException) {
throw $e;
}
$v = null;
$this->container->getDefinition($this->currentId)->addError($e->getMessage());
}
return $this->resolveArrays || !$v || !\is_array($v) ? $v : $value;
}
if ($value instanceof Definition) {
$value->setBindings($this->processValue($value->getBindings()));
$changes = $value->getChanges();
if (isset($changes['class'])) {
$value->setClass($this->bag->resolveValue($value->getClass()));
}
if (isset($changes['file'])) {
$value->setFile($this->bag->resolveValue($value->getFile()));
}
$tags = $value->getTags();
if (isset($tags['proxy'])) {
$tags['proxy'] = $this->bag->resolveValue($tags['proxy']);
$value->setTags($tags);
}
}
$value = parent::processValue($value, $isRoot);
if ($value && \is_array($value)) {
$value = array_combine($this->bag->resolveValue(array_keys($value)), $value);
}
return $value;
}
}

View file

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s" class is deprecated.', ResolvePrivatesPass::class);
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @deprecated since Symfony 5.2
*/
class ResolvePrivatesPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
foreach ($container->getDefinitions() as $id => $definition) {
if ($definition->isPrivate()) {
$definition->setPublic(false);
$definition->setPrivate(true);
}
}
foreach ($container->getAliases() as $id => $alias) {
if ($alias->isPrivate()) {
$alias->setPublic(false);
$alias->setPrivate(true);
}
}
}
}

View file

@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Replaces all references to aliases with references to the actual service.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ResolveReferencesToAliasesPass extends AbstractRecursivePass
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
parent::process($container);
foreach ($container->getAliases() as $id => $alias) {
$aliasId = (string) $alias;
$this->currentId = $id;
if ($aliasId !== $defId = $this->getDefinitionId($aliasId, $container)) {
$container->setAlias($id, $defId)->setPublic($alias->isPublic());
}
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof Reference) {
return parent::processValue($value, $isRoot);
}
$defId = $this->getDefinitionId($id = (string) $value, $this->container);
return $defId !== $id ? new Reference($defId, $value->getInvalidBehavior()) : $value;
}
private function getDefinitionId(string $id, ContainerBuilder $container): string
{
if (!$container->hasAlias($id)) {
return $id;
}
$alias = $container->getAlias($id);
if ($alias->isDeprecated()) {
$referencingDefinition = $container->hasDefinition($this->currentId) ? $container->getDefinition($this->currentId) : $container->getAlias($this->currentId);
if (!$referencingDefinition->isDeprecated()) {
$deprecation = $alias->getDeprecation($id);
trigger_deprecation($deprecation['package'], $deprecation['version'], rtrim($deprecation['message'], '. ').'. It is being referenced by the "%s" '.($container->hasDefinition($this->currentId) ? 'service.' : 'alias.'), $this->currentId);
}
}
$seen = [];
do {
if (isset($seen[$id])) {
throw new ServiceCircularReferenceException($id, array_merge(array_keys($seen), [$id]));
}
$seen[$id] = true;
$id = (string) $container->getAlias($id);
} while ($container->hasAlias($id));
return $id;
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Psr\Container\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* Compiler pass to inject their service locator to service subscribers.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ResolveServiceSubscribersPass extends AbstractRecursivePass
{
private $serviceLocator;
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference && $this->serviceLocator && \in_array((string) $value, [ContainerInterface::class, ServiceProviderInterface::class], true)) {
return new Reference($this->serviceLocator);
}
if (!$value instanceof Definition) {
return parent::processValue($value, $isRoot);
}
$serviceLocator = $this->serviceLocator;
$this->serviceLocator = null;
if ($value->hasTag('container.service_subscriber.locator')) {
$this->serviceLocator = $value->getTag('container.service_subscriber.locator')[0]['id'];
$value->clearTag('container.service_subscriber.locator');
}
try {
return parent::processValue($value);
} finally {
$this->serviceLocator = $serviceLocator;
}
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
/**
* Resolves all TaggedIteratorArgument arguments.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class ResolveTaggedIteratorArgumentPass extends AbstractRecursivePass
{
use PriorityTaggedServiceTrait;
/**
* {@inheritdoc}
*/
protected function processValue($value, bool $isRoot = false)
{
if (!$value instanceof TaggedIteratorArgument) {
return parent::processValue($value, $isRoot);
}
$value->setValues($this->findAndSortTaggedServices($value, $this->container));
return $value;
}
}

View file

@ -0,0 +1,142 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
/**
* Applies the "container.service_locator" tag by wrapping references into ServiceClosureArgument instances.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class ServiceLocatorTagPass extends AbstractRecursivePass
{
use PriorityTaggedServiceTrait;
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof ServiceLocatorArgument) {
if ($value->getTaggedIteratorArgument()) {
$value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container));
}
return self::register($this->container, $value->getValues());
}
if ($value instanceof Definition) {
$value->setBindings(parent::processValue($value->getBindings()));
}
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
return parent::processValue($value, $isRoot);
}
if (!$value->getClass()) {
$value->setClass(ServiceLocator::class);
}
$services = $value->getArguments()[0] ?? null;
if ($services instanceof TaggedIteratorArgument) {
$services = $this->findAndSortTaggedServices($services, $this->container);
}
if (!\is_array($services)) {
throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set.', $this->currentId));
}
$i = 0;
foreach ($services as $k => $v) {
if ($v instanceof ServiceClosureArgument) {
continue;
}
if (!$v instanceof Reference) {
throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, get_debug_type($v), $k));
}
if ($i === $k) {
unset($services[$k]);
$k = (string) $v;
++$i;
} elseif (\is_int($k)) {
$i = null;
}
$services[$k] = new ServiceClosureArgument($v);
}
ksort($services);
$value->setArgument(0, $services);
$id = '.service_locator.'.ContainerBuilder::hash($value);
if ($isRoot) {
if ($id !== $this->currentId) {
$this->container->setAlias($id, new Alias($this->currentId, false));
}
return $value;
}
$this->container->setDefinition($id, $value->setPublic(false));
return new Reference($id);
}
/**
* @param Reference[] $refMap
*/
public static function register(ContainerBuilder $container, array $refMap, string $callerId = null): Reference
{
foreach ($refMap as $id => $ref) {
if (!$ref instanceof Reference) {
throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', get_debug_type($ref), $id));
}
$refMap[$id] = new ServiceClosureArgument($ref);
}
$locator = (new Definition(ServiceLocator::class))
->addArgument($refMap)
->addTag('container.service_locator');
if (null !== $callerId && $container->hasDefinition($callerId)) {
$locator->setBindings($container->getDefinition($callerId)->getBindings());
}
if (!$container->hasDefinition($id = '.service_locator.'.ContainerBuilder::hash($locator))) {
$container->setDefinition($id, $locator);
}
if (null !== $callerId) {
$locatorId = $id;
// Locators are shared when they hold the exact same list of factories;
// to have them specialized per consumer service, we use a cloning factory
// to derivate customized instances from the prototype one.
$container->register($id .= '.'.$callerId, ServiceLocator::class)
->setFactory([new Reference($locatorId), 'withContext'])
->addTag('container.service_locator_context', ['id' => $callerId])
->addArgument($callerId)
->addArgument(new Reference('service_container'));
}
return new Reference($id);
}
}

View file

@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* This is a directed graph of your services.
*
* This information can be used by your compiler passes instead of collecting
* it themselves which improves performance quite a lot.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*
* @final
*/
class ServiceReferenceGraph
{
/**
* @var ServiceReferenceGraphNode[]
*/
private $nodes = [];
public function hasNode(string $id): bool
{
return isset($this->nodes[$id]);
}
/**
* Gets a node by identifier.
*
* @throws InvalidArgumentException if no node matches the supplied identifier
*/
public function getNode(string $id): ServiceReferenceGraphNode
{
if (!isset($this->nodes[$id])) {
throw new InvalidArgumentException(sprintf('There is no node with id "%s".', $id));
}
return $this->nodes[$id];
}
/**
* Returns all nodes.
*
* @return ServiceReferenceGraphNode[]
*/
public function getNodes(): array
{
return $this->nodes;
}
/**
* Clears all nodes.
*/
public function clear()
{
foreach ($this->nodes as $node) {
$node->clear();
}
$this->nodes = [];
}
/**
* Connects 2 nodes together in the Graph.
*/
public function connect(?string $sourceId, $sourceValue, ?string $destId, $destValue = null, Reference $reference = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false)
{
if (null === $sourceId || null === $destId) {
return;
}
$sourceNode = $this->createNode($sourceId, $sourceValue);
$destNode = $this->createNode($destId, $destValue);
$edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference, $lazy, $weak, $byConstructor);
$sourceNode->addOutEdge($edge);
$destNode->addInEdge($edge);
}
private function createNode(string $id, $value): ServiceReferenceGraphNode
{
if (isset($this->nodes[$id]) && $this->nodes[$id]->getValue() === $value) {
return $this->nodes[$id];
}
return $this->nodes[$id] = new ServiceReferenceGraphNode($id, $value);
}
}

View file

@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
/**
* Represents an edge in your service graph.
*
* Value is typically a reference.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ServiceReferenceGraphEdge
{
private $sourceNode;
private $destNode;
private $value;
private $lazy;
private $weak;
private $byConstructor;
public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null, bool $lazy = false, bool $weak = false, bool $byConstructor = false)
{
$this->sourceNode = $sourceNode;
$this->destNode = $destNode;
$this->value = $value;
$this->lazy = $lazy;
$this->weak = $weak;
$this->byConstructor = $byConstructor;
}
/**
* Returns the value of the edge.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Returns the source node.
*
* @return ServiceReferenceGraphNode
*/
public function getSourceNode()
{
return $this->sourceNode;
}
/**
* Returns the destination node.
*
* @return ServiceReferenceGraphNode
*/
public function getDestNode()
{
return $this->destNode;
}
/**
* Returns true if the edge is lazy, meaning it's a dependency not requiring direct instantiation.
*
* @return bool
*/
public function isLazy()
{
return $this->lazy;
}
/**
* Returns true if the edge is weak, meaning it shouldn't prevent removing the target service.
*
* @return bool
*/
public function isWeak()
{
return $this->weak;
}
/**
* Returns true if the edge links with a constructor argument.
*
* @return bool
*/
public function isReferencedByConstructor()
{
return $this->byConstructor;
}
}

View file

@ -0,0 +1,118 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Definition;
/**
* Represents a node in your service graph.
*
* Value is typically a definition, or an alias.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ServiceReferenceGraphNode
{
private $id;
private $inEdges = [];
private $outEdges = [];
private $value;
/**
* @param string $id The node identifier
* @param mixed $value The node value
*/
public function __construct(string $id, $value)
{
$this->id = $id;
$this->value = $value;
}
public function addInEdge(ServiceReferenceGraphEdge $edge)
{
$this->inEdges[] = $edge;
}
public function addOutEdge(ServiceReferenceGraphEdge $edge)
{
$this->outEdges[] = $edge;
}
/**
* Checks if the value of this node is an Alias.
*
* @return bool
*/
public function isAlias()
{
return $this->value instanceof Alias;
}
/**
* Checks if the value of this node is a Definition.
*
* @return bool
*/
public function isDefinition()
{
return $this->value instanceof Definition;
}
/**
* Returns the identifier.
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Returns the in edges.
*
* @return ServiceReferenceGraphEdge[]
*/
public function getInEdges()
{
return $this->inEdges;
}
/**
* Returns the out edges.
*
* @return ServiceReferenceGraphEdge[]
*/
public function getOutEdges()
{
return $this->outEdges;
}
/**
* Returns the value of this Node.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Clears all edges.
*/
public function clear()
{
$this->inEdges = $this->outEdges = [];
}
}

View file

@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\Config\Definition\BaseNode;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
/**
* Validates environment variable placeholders used in extension configuration with dummy values.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class ValidateEnvPlaceholdersPass implements CompilerPassInterface
{
private const TYPE_FIXTURES = ['array' => [], 'bool' => false, 'float' => 0.0, 'int' => 0, 'string' => ''];
private $extensionConfig = [];
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->extensionConfig = [];
if (!class_exists(BaseNode::class) || !$extensions = $container->getExtensions()) {
return;
}
$resolvingBag = $container->getParameterBag();
if (!$resolvingBag instanceof EnvPlaceholderParameterBag) {
return;
}
$defaultBag = new ParameterBag($resolvingBag->all());
$envTypes = $resolvingBag->getProvidedTypes();
foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) {
$values = [];
if (false === $i = strpos($env, ':')) {
$default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::TYPE_FIXTURES['string'];
$defaultType = null !== $default ? get_debug_type($default) : 'string';
$values[$defaultType] = $default;
} else {
$prefix = substr($env, 0, $i);
foreach ($envTypes[$prefix] ?? ['string'] as $type) {
$values[$type] = self::TYPE_FIXTURES[$type] ?? null;
}
}
foreach ($placeholders as $placeholder) {
BaseNode::setPlaceholder($placeholder, $values);
}
}
$processor = new Processor();
foreach ($extensions as $name => $extension) {
if (!($extension instanceof ConfigurationExtensionInterface || $extension instanceof ConfigurationInterface)
|| !$config = array_filter($container->getExtensionConfig($name))
) {
// this extension has no semantic configuration or was not called
continue;
}
$config = $resolvingBag->resolveValue($config);
if ($extension instanceof ConfigurationInterface) {
$configuration = $extension;
} elseif (null === $configuration = $extension->getConfiguration($config, $container)) {
continue;
}
$this->extensionConfig[$name] = $processor->processConfiguration($configuration, $config);
}
$resolvingBag->clearUnusedEnvPlaceholders();
}
/**
* @internal
*/
public function getExtensionConfig(): array
{
try {
return $this->extensionConfig;
} finally {
$this->extensionConfig = [];
}
}
}