[DI] Add ServiceLocatorArgument to generate array-based locators optimized for OPcache shared memory

This commit is contained in:
Nicolas Grekas 2018-06-30 08:39:51 +02:00
parent 04b2c2db4f
commit 6c8e9576a3
31 changed files with 525 additions and 147 deletions

View File

@ -17,6 +17,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@ -334,6 +335,8 @@ class TextDescriptor extends Descriptor
$argumentsInformation[] = sprintf('Service(%s)', (string) $argument); $argumentsInformation[] = sprintf('Service(%s)', (string) $argument);
} elseif ($argument instanceof IteratorArgument) { } elseif ($argument instanceof IteratorArgument) {
$argumentsInformation[] = sprintf('Iterator (%d element(s))', count($argument->getValues())); $argumentsInformation[] = sprintf('Iterator (%d element(s))', count($argument->getValues()));
} elseif ($argument instanceof ServiceLocatorArgument) {
$argumentsInformation[] = sprintf('Service locator (%d element(s))', count($argument->getValues()));
} elseif ($argument instanceof Definition) { } elseif ($argument instanceof Definition) {
$argumentsInformation[] = 'Inlined Service'; $argumentsInformation[] = 'Inlined Service';
} else { } else {

View File

@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Console\Descriptor;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@ -387,8 +388,8 @@ class XmlDescriptor extends Descriptor
if ($argument instanceof Reference) { if ($argument instanceof Reference) {
$argumentXML->setAttribute('type', 'service'); $argumentXML->setAttribute('type', 'service');
$argumentXML->setAttribute('id', (string) $argument); $argumentXML->setAttribute('id', (string) $argument);
} elseif ($argument instanceof IteratorArgument) { } elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) {
$argumentXML->setAttribute('type', 'iterator'); $argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator');
foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) { foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) {
$argumentXML->appendChild($childArgumentXML); $argumentXML->appendChild($childArgumentXML);

View File

@ -11,8 +11,8 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
@ -32,7 +32,7 @@ class TestServiceContainerWeakRefPass implements CompilerPassInterface
foreach ($definitions as $id => $definition) { foreach ($definitions as $id => $definition) {
if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate()) && !$definition->getErrors() && !$definition->isAbstract()) { if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate()) && !$definition->getErrors() && !$definition->isAbstract()) {
$privateServices[$id] = new ServiceClosureArgument(new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE)); $privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE);
} }
} }
@ -44,13 +44,15 @@ class TestServiceContainerWeakRefPass implements CompilerPassInterface
$alias = $aliases[$target]; $alias = $aliases[$target];
} }
if (isset($definitions[$target]) && !$definitions[$target]->getErrors() && !$definitions[$target]->isAbstract()) { if (isset($definitions[$target]) && !$definitions[$target]->getErrors() && !$definitions[$target]->isAbstract()) {
$privateServices[$id] = new ServiceClosureArgument(new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE)); $privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE);
} }
} }
} }
if ($privateServices) { if ($privateServices) {
$definitions['test.private_services_locator']->replaceArgument(0, $privateServices); $id = (string) ServiceLocatorTagPass::register($container, $privateServices);
$container->setDefinition('test.private_services_locator', $container->getDefinition($id))->setPublic(true);
$container->removeDefinition($id);
} }
} }
} }

View File

@ -58,14 +58,9 @@
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener"> <service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
<tag name="kernel.event_subscriber" /> <tag name="kernel.event_subscriber" />
<argument type="service"> <argument type="service_locator">
<service class="Symfony\Component\DependencyInjection\ServiceLocator"> <argument key="session" type="service" id="session" on-invalid="ignore" />
<tag name="container.service_locator" /> <argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
<argument type="collection">
<argument key="session" type="service" id="session" on-invalid="ignore" />
<argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
</argument>
</service>
</argument> </argument>
</service> </service>

View File

@ -24,13 +24,8 @@
<service id="test.session.listener" class="Symfony\Component\HttpKernel\EventListener\TestSessionListener"> <service id="test.session.listener" class="Symfony\Component\HttpKernel\EventListener\TestSessionListener">
<tag name="kernel.event_subscriber" /> <tag name="kernel.event_subscriber" />
<argument type="service"> <argument type="service_locator">
<service class="Symfony\Component\DependencyInjection\ServiceLocator"> <argument key="session" type="service" id="session" on-invalid="ignore" />
<tag name="container.service_locator" />
<argument type="collection">
<argument key="session" type="service" id="session" on-invalid="ignore" />
</argument>
</service>
</argument> </argument>
</service> </service>

View File

@ -19,7 +19,7 @@
"php": "^7.1.3", "php": "^7.1.3",
"ext-xml": "*", "ext-xml": "*",
"symfony/cache": "~3.4|~4.0", "symfony/cache": "~3.4|~4.0",
"symfony/dependency-injection": "^4.1.1", "symfony/dependency-injection": "^4.2",
"symfony/config": "~4.2", "symfony/config": "~4.2",
"symfony/event-dispatcher": "^4.1", "symfony/event-dispatcher": "^4.1",
"symfony/http-foundation": "^4.1", "symfony/http-foundation": "^4.1",

View File

@ -27,14 +27,9 @@
<service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" /> <service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" />
<service id="security.helper" class="Symfony\Component\Security\Core\Security"> <service id="security.helper" class="Symfony\Component\Security\Core\Security">
<argument type="service"> <argument type="service_locator">
<service class="Symfony\Component\DependencyInjection\ServiceLocator"> <argument key="security.token_storage" type="service" id="security.token_storage" />
<tag name="container.service_locator" /> <argument key="security.authorization_checker" type="service" id="security.authorization_checker" />
<argument type="collection">
<argument key="security.token_storage" type="service" id="security.token_storage" />
<argument key="security.authorization_checker" type="service" id="security.authorization_checker" />
</argument>
</service>
</argument> </argument>
</service> </service>
<service id="Symfony\Component\Security\Core\Security" alias="security.helper" /> <service id="Symfony\Component\Security\Core\Security" alias="security.helper" />

View File

@ -20,7 +20,7 @@
"ext-xml": "*", "ext-xml": "*",
"symfony/config": "^4.2", "symfony/config": "^4.2",
"symfony/security": "~4.2", "symfony/security": "~4.2",
"symfony/dependency-injection": "^3.4.3|^4.0.3", "symfony/dependency-injection": "^4.2",
"symfony/http-kernel": "^4.1" "symfony/http-kernel": "^4.1"
}, },
"require-dev": { "require-dev": {

View File

@ -11,9 +11,6 @@
namespace Symfony\Component\DependencyInjection\Argument; namespace Symfony\Component\DependencyInjection\Argument;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/** /**
* Represents a collection of values to lazily iterate over. * Represents a collection of values to lazily iterate over.
* *
@ -21,35 +18,5 @@ use Symfony\Component\DependencyInjection\Reference;
*/ */
class IteratorArgument implements ArgumentInterface class IteratorArgument implements ArgumentInterface
{ {
private $values; use ReferenceSetArgumentTrait;
/**
* @param Reference[] $values
*/
public function __construct(array $values)
{
$this->setValues($values);
}
/**
* @return array The values to lazily iterate over
*/
public function getValues()
{
return $this->values;
}
/**
* @param Reference[] $values The service references to lazily iterate over
*/
public function setValues(array $values)
{
foreach ($values as $k => $v) {
if (null !== $v && !$v instanceof Reference) {
throw new InvalidArgumentException(sprintf('An IteratorArgument must hold only Reference instances, "%s" given.', is_object($v) ? get_class($v) : gettype($v)));
}
}
$this->values = $values;
}
} }

View File

@ -0,0 +1,54 @@
<?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\Argument;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
trait ReferenceSetArgumentTrait
{
private $values;
/**
* @param Reference[] $values
*/
public function __construct(array $values)
{
$this->setValues($values);
}
/**
* @return Reference[] The values in the set
*/
public function getValues()
{
return $this->values;
}
/**
* @param Reference[] $values The service references to put in the set
*/
public function setValues(array $values)
{
foreach ($values as $k => $v) {
if (null !== $v && !$v instanceof Reference) {
throw new InvalidArgumentException(sprintf('A %s must hold only Reference instances, "%s" given.', __CLASS__, is_object($v) ? get_class($v) : gettype($v)));
}
}
$this->values = $values;
}
}

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\Argument;
use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class ServiceLocator extends BaseServiceLocator
{
private $factory;
private $serviceMap;
public function __construct(\Closure $factory, array $serviceMap)
{
$this->factory = $factory;
$this->serviceMap = $serviceMap;
parent::__construct($serviceMap);
}
/**
* {@inheritdoc}
*/
public function get($id)
{
return isset($this->serviceMap[$id]) ? ($this->factory)(...$this->serviceMap[$id]) : parent::get($id);
}
}

View File

@ -0,0 +1,22 @@
<?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\Argument;
/**
* Represents a closure acting as a service locator.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServiceLocatorArgument implements ArgumentInterface
{
use ReferenceSetArgumentTrait;
}

View File

@ -5,6 +5,7 @@ CHANGELOG
----- -----
* added `ServiceSubscriberTrait` * added `ServiceSubscriberTrait`
* added `ServiceLocatorArgument` for creating optimized service-locators
4.1.0 4.1.0
----- -----

View File

@ -11,13 +11,11 @@
namespace Symfony\Component\DependencyInjection\Compiler; namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\EnvVarProcessor; use Symfony\Component\DependencyInjection\EnvVarProcessor;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
/** /**
@ -41,7 +39,7 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class)); throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
} }
foreach ($class::getProvidedTypes() as $prefix => $type) { foreach ($class::getProvidedTypes() as $prefix => $type) {
$processors[$prefix] = new ServiceClosureArgument(new Reference($id)); $processors[$prefix] = new Reference($id);
$types[$prefix] = self::validateProvidedTypes($type, $class); $types[$prefix] = self::validateProvidedTypes($type, $class);
} }
} }
@ -56,9 +54,8 @@ class RegisterEnvVarProcessorsPass implements CompilerPassInterface
} }
if ($processors) { if ($processors) {
$container->register('container.env_var_processors_locator', ServiceLocator::class) $container->setAlias('container.env_var_processors_locator', (string) ServiceLocatorTagPass::register($container, $processors))
->setPublic(true) ->setPublic(true)
->setArguments(array($processors))
; ;
} }
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@ -28,6 +29,9 @@ final class ServiceLocatorTagPass extends AbstractRecursivePass
{ {
protected function processValue($value, $isRoot = false) protected function processValue($value, $isRoot = false)
{ {
if ($value instanceof ServiceLocatorArgument) {
return self::register($this->container, $value->getValues());
}
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) { if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
return parent::processValue($value, $isRoot); return parent::processValue($value, $isRoot);
} }

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\DependencyInjection;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
@ -401,6 +402,30 @@ class Container implements ResettableContainerInterface
} }
} }
/**
* @internal
*/
final protected function getService($registry, $id, $method, $load)
{
if ('service_container' === $id) {
return $this;
}
if (\is_string($load)) {
throw new RuntimeException($load);
}
if (null === $method) {
return false !== $registry ? $this->{$registry}[$id] ?? null : null;
}
if (false !== $registry) {
return $this->{$registry}[$id] ?? $this->{$registry}[$id] = $load ? $this->load($method) : $this->{$method}();
}
if (!$load) {
return $this->{$method}();
}
return ($factory = $this->factories[$id] ?? $this->factories['service_container'][$id] ?? null) ? $factory() : $this->load($method);
}
private function __clone() private function __clone()
{ {
} }

View File

@ -15,6 +15,8 @@ use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Compiler\Compiler; use Symfony\Component\DependencyInjection\Compiler\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\PassConfig; use Symfony\Component\DependencyInjection\Compiler\PassConfig;
@ -1215,6 +1217,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $count; return $count;
}); });
} elseif ($value instanceof ServiceLocatorArgument) {
$refs = array();
foreach ($value->getValues() as $k => $v) {
if ($v) {
$refs[$k] = array($v);
}
}
$value = new ServiceLocator(\Closure::fromCallable(array($this, 'resolveServices')), $refs);
} elseif ($value instanceof Reference) { } elseif ($value instanceof Reference) {
$value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices); $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices);
} elseif ($value instanceof Definition) { } elseif ($value instanceof Definition) {

View File

@ -14,6 +14,8 @@ namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\DependencyInjection\Variable;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass;
@ -22,6 +24,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Exception\EnvParameterException; use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
@ -71,6 +74,8 @@ class PhpDumper extends Dumper
private $circularReferences = array(); private $circularReferences = array();
private $singleUsePrivateIds = array(); private $singleUsePrivateIds = array();
private $addThrow = false; private $addThrow = false;
private $locatedIds = array();
private $serviceLocatorTag;
/** /**
* @var ProxyDumper * @var ProxyDumper
@ -113,6 +118,7 @@ class PhpDumper extends Dumper
*/ */
public function dump(array $options = array()) public function dump(array $options = array())
{ {
$this->locatedIds = array();
$this->targetDirRegex = null; $this->targetDirRegex = null;
$this->inlinedRequires = array(); $this->inlinedRequires = array();
$options = array_merge(array( $options = array_merge(array(
@ -123,6 +129,7 @@ class PhpDumper extends Dumper
'debug' => true, 'debug' => true,
'hot_path_tag' => 'container.hot_path', 'hot_path_tag' => 'container.hot_path',
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
'service_locator_tag' => 'container.service_locator',
'build_time' => time(), 'build_time' => time(),
), $options); ), $options);
@ -131,6 +138,7 @@ class PhpDumper extends Dumper
$this->asFiles = $options['as_files']; $this->asFiles = $options['as_files'];
$this->hotPathTag = $options['hot_path_tag']; $this->hotPathTag = $options['hot_path_tag'];
$this->inlineRequires = $options['inline_class_loader_parameter'] && $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']); $this->inlineRequires = $options['inline_class_loader_parameter'] && $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']);
$this->serviceLocatorTag = $options['service_locator_tag'];
if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
$baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass); $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
@ -269,6 +277,7 @@ EOF;
$this->targetDirRegex = null; $this->targetDirRegex = null;
$this->inlinedRequires = array(); $this->inlinedRequires = array();
$this->circularReferences = array(); $this->circularReferences = array();
$this->locatedIds = array();
$unusedEnvs = array(); $unusedEnvs = array();
foreach ($this->container->getEnvCounters() as $env => $use) { foreach ($this->container->getEnvCounters() as $env => $use) {
@ -508,7 +517,7 @@ EOTXT
throw new ServiceCircularReferenceException($id, array($id)); throw new ServiceCircularReferenceException($id, array($id));
} }
$code .= $this->addNewInstance($def, '$'.$name, ' = ', $id); $code .= $this->addNewInstance($def, ' $'.$name.' = ', $id);
if (!$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) { if (!$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) {
$code .= $this->addServiceProperties($def, $name); $code .= $this->addServiceProperties($def, $name);
@ -552,7 +561,7 @@ EOTXT
$instantiation .= ' = '; $instantiation .= ' = ';
} }
$code = $this->addNewInstance($definition, $return, $instantiation, $id); $code = $this->addNewInstance($definition, ' '.$return.$instantiation, $id);
if (!$isSimpleInstance) { if (!$isSimpleInstance) {
$code .= "\n"; $code .= "\n";
@ -816,7 +825,7 @@ EOF;
} }
if ($definition->isPublic()) { if ($definition->isPublic()) {
$publicServices .= $this->addService($id, $definition); $publicServices .= $this->addService($id, $definition);
} elseif (!$this->isTrivialInstance($definition)) { } elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) {
$privateServices .= $this->addService($id, $definition); $privateServices .= $this->addService($id, $definition);
} }
} }
@ -829,7 +838,7 @@ EOF;
$definitions = $this->container->getDefinitions(); $definitions = $this->container->getDefinitions();
ksort($definitions); ksort($definitions);
foreach ($definitions as $id => $definition) { foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && !$this->isHotPath($definition) && ($definition->isPublic() || !$this->isTrivialInstance($definition))) { if (!$definition->isSynthetic() && !$this->isHotPath($definition) && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) {
$code = $this->addService($id, $definition, $file); $code = $this->addService($id, $definition, $file);
if (!$definition->isShared()) { if (!$definition->isShared()) {
@ -850,10 +859,18 @@ EOF;
} }
} }
private function addNewInstance(Definition $definition, $return, $instantiation, $id) private function addNewInstance(Definition $definition, string $return = '', string $id = null)
{ {
$class = $this->dumpValue($definition->getClass()); $tail = $return ? ";\n" : '';
$return = ' '.$return.$instantiation;
if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) {
$arguments = array();
foreach ($definition->getArgument(0) as $k => $argument) {
$arguments[$k] = $argument->getValues()[0];
}
return $return.$this->dumpValue(new ServiceLocatorArgument($arguments)).$tail;
}
$arguments = array(); $arguments = array();
foreach ($definition->getArguments() as $value) { foreach ($definition->getArguments() as $value) {
@ -862,6 +879,7 @@ EOF;
if (null !== $definition->getFactory()) { if (null !== $definition->getFactory()) {
$callable = $definition->getFactory(); $callable = $definition->getFactory();
if (is_array($callable)) { if (is_array($callable)) {
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) {
throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a')); throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a'));
@ -869,34 +887,34 @@ EOF;
if ($callable[0] instanceof Reference if ($callable[0] instanceof Reference
|| ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) {
return $return.sprintf("%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); return $return.sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
} }
$class = $this->dumpValue($callable[0]); $class = $this->dumpValue($callable[0]);
// If the class is a string we can optimize away // If the class is a string we can optimize away
if (0 === strpos($class, "'") && false === strpos($class, '$')) { if (0 === strpos($class, "'") && false === strpos($class, '$')) {
if ("''" === $class) { if ("''" === $class) {
throw new RuntimeException(sprintf('Cannot dump definition: The "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id)); throw new RuntimeException(sprintf('Cannot dump definition: %s service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id ? 'The "'.$id.'"' : 'inline'));
} }
return $return.sprintf("%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); return $return.sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
} }
if (0 === strpos($class, 'new ')) { if (0 === strpos($class, 'new ')) {
return $return.sprintf("(%s)->%s(%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : ''); return $return.sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
} }
return $return.sprintf("[%s, '%s'](%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : ''); return $return.sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail;
} }
return $return.sprintf("%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); return $return.sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail;
} }
if (false !== strpos($class, '$')) { if (null === $class = $definition->getClass()) {
return sprintf(" \$class = %s;\n\n%snew \$class(%s);\n", $class, $return, implode(', ', $arguments)); throw new RuntimeException('Cannot dump definitions which have no class nor factory.');
} }
return $return.sprintf("new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail;
} }
private function startClass(string $class, string $baseClass, string $baseClassWithNamespace): string private function startClass(string $class, string $baseClass, string $baseClassWithNamespace): string
@ -1537,6 +1555,27 @@ EOF;
return implode("\n", $code); return implode("\n", $code);
} }
if ($value instanceof ServiceLocatorArgument) {
$serviceMap = '';
foreach ($value->getValues() as $k => $v) {
if (!$v) {
continue;
}
$definition = $this->container->findDefinition($id = (string) $v);
$load = !($e = $definition->getErrors()) ? $this->asFiles && !$this->isHotPath($definition) : reset($e);
$serviceMap .= sprintf("\n %s => array(%s, %s, %s, %s),",
$this->export($k),
$this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false),
$this->export($id),
$this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id).($load ? '.php' : '') : null),
$this->export($load)
);
$this->locatedIds[$id] = true;
}
return sprintf('new \%s(\Closure::fromCallable(array($this, \'getService\')), array(%s%s))', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '');
}
} finally { } finally {
list($this->definitionVariables, $this->referenceVariables, $this->variableCount) = $scope; list($this->definitionVariables, $this->referenceVariables, $this->variableCount) = $scope;
} }
@ -1559,50 +1598,7 @@ EOF;
throw new RuntimeException('Cannot dump definitions which have a configurator.'); throw new RuntimeException('Cannot dump definitions which have a configurator.');
} }
$arguments = array(); return $this->addNewInstance($value);
foreach ($value->getArguments() as $argument) {
$arguments[] = $this->dumpValue($argument);
}
if (null !== $value->getFactory()) {
$factory = $value->getFactory();
if (is_string($factory)) {
return sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory)), implode(', ', $arguments));
}
if (is_array($factory)) {
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) {
throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a'));
}
$class = $this->dumpValue($factory[0]);
if (is_string($factory[0])) {
return sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $factory[1], implode(', ', $arguments));
}
if ($factory[0] instanceof Definition) {
if (0 === strpos($class, 'new ')) {
return sprintf('(%s)->%s(%s)', $class, $factory[1], implode(', ', $arguments));
}
return sprintf("[%s, '%s'](%s)", $class, $factory[1], implode(', ', $arguments));
}
if ($factory[0] instanceof Reference) {
return sprintf('%s->%s(%s)', $class, $factory[1], implode(', ', $arguments));
}
}
throw new RuntimeException('Cannot dump definition because of invalid factory');
}
$class = $value->getClass();
if (null === $class) {
throw new RuntimeException('Cannot dump definitions which have no class nor factory.');
}
return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments));
} elseif ($value instanceof Variable) { } elseif ($value instanceof Variable) {
return '$'.$value; return '$'.$value;
} elseif ($value instanceof Reference) { } elseif ($value instanceof Reference) {
@ -1696,7 +1692,7 @@ EOF;
return sprintf('$this->throw(%s)', $this->export(reset($e))); return sprintf('$this->throw(%s)', $this->export(reset($e)));
} }
$code = substr($this->addNewInstance($definition, '', '', $id), 8, -2); $code = $this->addNewInstance($definition, '', $id);
if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
$code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code); $code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code);
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Dumper;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Parameter;
@ -280,6 +281,9 @@ class XmlDumper extends Dumper
} elseif ($value instanceof IteratorArgument) { } elseif ($value instanceof IteratorArgument) {
$element->setAttribute('type', 'iterator'); $element->setAttribute('type', 'iterator');
$this->convertParameters($value->getValues(), $type, $element, 'key'); $this->convertParameters($value->getValues(), $type, $element, 'key');
} elseif ($value instanceof ServiceLocatorArgument) {
$element->setAttribute('type', 'service_locator');
$this->convertParameters($value->getValues(), $type, $element, 'key');
} elseif ($value instanceof Reference) { } elseif ($value instanceof Reference) {
$element->setAttribute('type', 'service'); $element->setAttribute('type', 'service');
$element->setAttribute('id', (string) $value); $element->setAttribute('id', (string) $value);

View File

@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
@ -233,6 +234,8 @@ class YamlDumper extends Dumper
} }
if ($value instanceof IteratorArgument) { if ($value instanceof IteratorArgument) {
$tag = 'iterator'; $tag = 'iterator';
} elseif ($value instanceof ServiceLocatorArgument) {
$tag = 'service_locator';
} else { } else {
throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_class($value))); throw new RuntimeException(sprintf('Unspecified Yaml tag for type "%s".', get_class($value)));
} }

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator; namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
@ -92,6 +93,16 @@ function inline(string $class = null): InlineServiceConfigurator
return new InlineServiceConfigurator(new Definition($class)); return new InlineServiceConfigurator(new Definition($class));
} }
/**
* Creates a service locator.
*
* @param ReferenceConfigurator[] $values
*/
function service_locator(array $values): ServiceLocatorArgument
{
return new ServiceLocatorArgument(AbstractConfigurator::processValue($values, true));
}
/** /**
* Creates a lazy iterator. * Creates a lazy iterator.
* *

View File

@ -17,6 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -510,7 +511,15 @@ class XmlFileLoader extends FileLoader
try { try {
$arguments[$key] = new IteratorArgument($arg); $arguments[$key] = new IteratorArgument($arg);
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="iterator" only accepts collections of type="service" references in "%s".', $name, $file)); throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" only accepts collections of type="service" references in "%s".', $name, $file));
}
break;
case 'service_locator':
$arg = $this->getArgumentsAsPhp($arg, $name, $file, false);
try {
$arguments[$key] = new ServiceLocatorArgument($arg);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" only accepts maps of type="service" references in "%s".', $name, $file));
} }
break; break;
case 'tagged': case 'tagged':

View File

@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -691,6 +692,17 @@ class YamlFileLoader extends FileLoader
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file)); throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file));
} }
} }
if ('service_locator' === $value->getTag()) {
if (!is_array($argument)) {
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file));
}
$argument = $this->resolveServices($argument, $file, $isParameter);
try {
return new ServiceLocatorArgument($argument);
} catch (InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file));
}
}
if ('tagged' === $value->getTag()) { if ('tagged' === $value->getTag()) {
if (!is_string($argument) || !$argument) { if (!is_string($argument) || !$argument) {
throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string in "%s".', $file)); throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string in "%s".', $file));

View File

@ -259,6 +259,7 @@
<xsd:enumeration value="string" /> <xsd:enumeration value="string" />
<xsd:enumeration value="constant" /> <xsd:enumeration value="constant" />
<xsd:enumeration value="iterator" /> <xsd:enumeration value="iterator" />
<xsd:enumeration value="service_locator" />
<xsd:enumeration value="tagged" /> <xsd:enumeration value="tagged" />
</xsd:restriction> </xsd:restriction>
</xsd:simpleType> </xsd:simpleType>

View File

@ -1431,6 +1431,33 @@ class ContainerBuilderTest extends TestCase
$container->get('errored_definition'); $container->get('errored_definition');
} }
public function testServiceLocatorArgument()
{
$container = include __DIR__.'/Fixtures/containers/container_service_locator_argument.php';
$container->compile();
$locator = $container->get('bar')->locator;
$this->assertInstanceOf(ServiceLocator::class, $locator);
$this->assertSame($container->get('foo1'), $locator->get('foo1'));
$this->assertEquals(new \stdClass(), $locator->get('foo2'));
$this->assertSame($locator->get('foo2'), $locator->get('foo2'));
$this->assertEquals(new \stdClass(), $locator->get('foo3'));
$this->assertNotSame($locator->get('foo3'), $locator->get('foo3'));
try {
$locator->get('foo4');
$this->fail('RuntimeException expected.');
} catch (RuntimeException $e) {
$this->assertSame('BOOM', $e->getMessage());
}
$this->assertNull($locator->get('foo5'));
$container->set('foo5', $foo5 = new \stdClass());
$this->assertSame($foo5, $locator->get('foo5'));
}
} }
class FooClass class FooClass

View File

@ -17,10 +17,12 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface; use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
@ -1015,6 +1017,38 @@ class PhpDumperTest extends TestCase
$container = new \Symfony_DI_PhpDumper_Errored_Definition(); $container = new \Symfony_DI_PhpDumper_Errored_Definition();
$container->get('runtime_error'); $container->get('runtime_error');
} }
public function testServiceLocatorArgument()
{
$container = include self::$fixturesPath.'/containers/container_service_locator_argument.php';
$container->compile();
$dumper = new PhpDumper($container);
$dump = $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Service_Locator_Argument'));
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_service_locator_argument.php', str_replace(str_replace('\\', '\\\\', self::$fixturesPath.DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR), '%path%', $dump));
eval('?>'.$dump);
$container = new \Symfony_DI_PhpDumper_Service_Locator_Argument();
$locator = $container->get('bar')->locator;
$this->assertInstanceOf(ArgumentServiceLocator::class, $locator);
$this->assertSame($container->get('foo1'), $locator->get('foo1'));
$this->assertEquals(new \stdClass(), $locator->get('foo2'));
$this->assertSame($locator->get('foo2'), $locator->get('foo2'));
$this->assertEquals(new \stdClass(), $locator->get('foo3'));
$this->assertNotSame($locator->get('foo3'), $locator->get('foo3'));
try {
$locator->get('foo4');
$this->fail('RuntimeException expected.');
} catch (RuntimeException $e) {
$this->assertSame('BOOM', $e->getMessage());
}
$this->assertNull($locator->get('foo5'));
$container->set('foo5', $foo5 = new \stdClass());
$this->assertSame($foo5, $locator->get('foo5'));
}
} }
class Rot13EnvVarProcessor implements EnvVarProcessorInterface class Rot13EnvVarProcessor implements EnvVarProcessorInterface

View File

@ -0,0 +1,46 @@
<?php
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
$container = new ContainerBuilder();
$container
->register('foo1', 'stdClass')
->setPublic(true)
;
$container
->register('foo2', 'stdClass')
;
$container
->register('foo3', 'stdClass')
->setShared(false)
;
$container
->register('foo4', 'stdClass')
->addError('BOOM')
;
$container
->register('foo5', 'stdClass')
->setPublic(true)
->setSynthetic(true)
;
$container
->register('bar', 'stdClass')
->setProperty('locator', new ServiceLocatorArgument(array(
'foo1' => new Reference('foo1'),
'foo2' => new Reference('foo2'),
'foo3' => new Reference('foo3'),
'foo4' => new Reference('foo4', $container::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE),
'foo5' => new Reference('foo5', $container::IGNORE_ON_UNINITIALIZED_REFERENCE),
)))
->setPublic(true)
;
return $container;

View File

@ -82,9 +82,7 @@ class Symfony_DI_PhpDumper_Test_EnvParameters extends Container
*/ */
protected function getTestService() protected function getTestService()
{ {
$class = $this->getEnv('FOO'); return $this->services['test'] = new ${($_ = $this->getEnv('FOO')) && false ?: "_"}($this->getEnv('Bar'), 'foo'.$this->getEnv('string:FOO').'baz', $this->getEnv('int:Baz'));
return $this->services['test'] = new $class($this->getEnv('Bar'), 'foo'.$this->getEnv('string:FOO').'baz', $this->getEnv('int:Baz'));
} }
public function getParameter($name) public function getParameter($name)

View File

@ -56,6 +56,7 @@ class Symfony_DI_PhpDumper_Test_Rot13Parameters extends Container
public function getRemovedIds() public function getRemovedIds()
{ {
return array( return array(
'.service_locator.GU08LT9' => true,
'Psr\\Container\\ContainerInterface' => true, 'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
); );
@ -78,9 +79,9 @@ class Symfony_DI_PhpDumper_Test_Rot13Parameters extends Container
*/ */
protected function getContainer_EnvVarProcessorsLocatorService() protected function getContainer_EnvVarProcessorsLocatorService()
{ {
return $this->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\ServiceLocator(array('rot13' => function () { return $this->services['container.env_var_processors_locator'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator(\Closure::fromCallable(array($this, 'getService')), array(
return ($this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\Rot13EnvVarProcessor()); 'rot13' => array('services', 'Symfony\\Component\\DependencyInjection\\Tests\\Dumper\\Rot13EnvVarProcessor', 'getRot13EnvVarProcessorService', false),
})); ));
} }
public function getParameter($name) public function getParameter($name)

View File

@ -0,0 +1,128 @@
<?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
/**
* This class has been auto-generated
* by the Symfony Dependency Injection Component.
*
* @final since Symfony 3.3
*/
class Symfony_DI_PhpDumper_Service_Locator_Argument extends Container
{
private $parameters;
private $targetDirs = array();
/**
* @internal but protected for BC on cache:clear
*/
protected $privates = array();
public function __construct()
{
$this->services = $this->privates = array();
$this->syntheticIds = array(
'foo5' => true,
);
$this->methodMap = array(
'bar' => 'getBarService',
'foo1' => 'getFoo1Service',
);
$this->aliases = array();
}
public function reset()
{
$this->privates = array();
parent::reset();
}
public function compile()
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
public function isCompiled()
{
return true;
}
public function getRemovedIds()
{
return array(
'.service_locator.38dy3OH' => true,
'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
'foo2' => true,
'foo3' => true,
'foo4' => true,
);
}
/**
* Gets the public 'bar' shared service.
*
* @return \stdClass
*/
protected function getBarService()
{
$this->services['bar'] = $instance = new \stdClass();
$instance->locator = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator(\Closure::fromCallable(array($this, 'getService')), array(
'foo1' => array('services', 'foo1', 'getFoo1Service', false),
'foo2' => array('privates', 'foo2', 'getFoo2Service', false),
'foo3' => array(false, 'foo3', 'getFoo3Service', false),
'foo4' => array('privates', 'foo4', NULL, 'BOOM'),
'foo5' => array('services', 'foo5', NULL, false),
));
return $instance;
}
/**
* Gets the public 'foo1' shared service.
*
* @return \stdClass
*/
protected function getFoo1Service()
{
return $this->services['foo1'] = new \stdClass();
}
/**
* Gets the private 'foo2' shared service.
*
* @return \stdClass
*/
protected function getFoo2Service()
{
return $this->privates['foo2'] = new \stdClass();
}
/**
* Gets the private 'foo3' service.
*
* @return \stdClass
*/
protected function getFoo3Service()
{
return new \stdClass();
}
/**
* Gets the private 'foo4' shared service.
*
* @return \stdClass
*/
protected function getFoo4Service()
{
return $this->privates['foo4'] = new \stdClass();
}
}

View File

@ -79,14 +79,11 @@ class ProjectServiceContainer extends Container
*/ */
protected function getFooServiceService() protected function getFooServiceService()
{ {
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber((new \Symfony\Component\DependencyInjection\ServiceLocator(array('Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => function (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition { return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator(\Closure::fromCallable(array($this, 'getService')), array(
return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? $this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition()); 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition' => array('privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'getCustomDefinitionService', false),
}, 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => function (): \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber { 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber' => array('services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false),
return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()); 'bar' => array('services', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\TestServiceSubscriber', 'getTestServiceSubscriberService', false),
}, 'bar' => function (): \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition { 'baz' => array('privates', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\CustomDefinition', 'getCustomDefinitionService', false),
return ($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] ?? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber()); )))->withContext('foo_service', $this));
}, 'baz' => function (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? $this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition());
})))->withContext('foo_service', $this));
} }
} }