Permalink
Browse files

feature #27783 [DI] Add ServiceLocatorArgument to generate array-base…

…d locators optimized for OPcache shared memory (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

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

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | -
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Right now, to generate service locators, we use collections of closures described using `ServiceClosureArgument`. This works well, but it doesn't scale well when the number of services grows, because we have to load as many closures as there are services, even if we never call them.

This PR introduces `ServiceLocatorArgument`, which describes the same thing, but allows dumping optimized locators: instead of a collection of closures, this generates a static array that OPcache can put in shared memory (see fixtures for example.)

Once this PR is merged, we'll be able to update `ServiceLocatorPass::register()` to leverage it and generate these optimized locators everywhere. One particular I have in mind in the locator used by `ServiceArgumentResolver`, which can grow fast (it has as many entries as there are actions.)

Commits
-------

6c8e957 [DI] Add ServiceLocatorArgument to generate array-based locators optimized for OPcache shared memory
  • Loading branch information...
nicolas-grekas committed Jul 7, 2018
2 parents 1cf8146 + 6c8e957 commit 6d3f63d6f313faec3c102644bb2d2d7587bce30b
Showing with 525 additions and 147 deletions.
  1. +3 −0 src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php
  2. +3 −2 src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php
  3. +6 −4 src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php
  4. +3 −8 src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml
  5. +2 −7 src/Symfony/Bundle/FrameworkBundle/Resources/config/test.xml
  6. +1 −1 src/Symfony/Bundle/FrameworkBundle/composer.json
  7. +3 −8 src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
  8. +1 −1 src/Symfony/Bundle/SecurityBundle/composer.json
  9. +1 −34 src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php
  10. +54 −0 src/Symfony/Component/DependencyInjection/Argument/ReferenceSetArgumentTrait.php
  11. +40 −0 src/Symfony/Component/DependencyInjection/Argument/ServiceLocator.php
  12. +22 −0 src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php
  13. +1 −0 src/Symfony/Component/DependencyInjection/CHANGELOG.md
  14. +2 −5 src/Symfony/Component/DependencyInjection/Compiler/RegisterEnvVarProcessorsPass.php
  15. +4 −0 src/Symfony/Component/DependencyInjection/Compiler/ServiceLocatorTagPass.php
  16. +25 −0 src/Symfony/Component/DependencyInjection/Container.php
  17. +10 −0 src/Symfony/Component/DependencyInjection/ContainerBuilder.php
  18. +57 −61 src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
  19. +4 −0 src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
  20. +3 −0 src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
  21. +11 −0 src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php
  22. +10 −1 src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
  23. +12 −0 src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
  24. +1 −0 src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
  25. +27 −0 src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
  26. +34 −0 src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
  27. +46 −0 ...ny/Component/DependencyInjection/Tests/Fixtures/containers/container_service_locator_argument.php
  28. +1 −3 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services26.php
  29. +4 −3 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_rot13_env.php
  30. +128 −0 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_service_locator_argument.php
  31. +6 −9 src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php
@@ -17,6 +17,7 @@
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@@ -334,6 +335,8 @@ protected function describeContainerDefinition(Definition $definition, array $op
$argumentsInformation[] = sprintf('Service(%s)', (string) $argument);
} elseif ($argument instanceof IteratorArgument) {
$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) {
$argumentsInformation[] = 'Inlined Service';
} else {
@@ -14,6 +14,7 @@
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@@ -387,8 +388,8 @@ private function getArgumentNodes(array $arguments, \DOMDocument $dom)
if ($argument instanceof Reference) {
$argumentXML->setAttribute('type', 'service');
$argumentXML->setAttribute('id', (string) $argument);
} elseif ($argument instanceof IteratorArgument) {
$argumentXML->setAttribute('type', 'iterator');
} elseif ($argument instanceof IteratorArgument || $argument instanceof ServiceLocatorArgument) {
$argumentXML->setAttribute('type', $argument instanceof IteratorArgument ? 'iterator' : 'service_locator');
foreach ($this->getArgumentNodes($argument->getValues(), $dom) as $childArgumentXML) {
$argumentXML->appendChild($childArgumentXML);
@@ -11,8 +11,8 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
@@ -32,7 +32,7 @@ public function process(ContainerBuilder $container)
foreach ($definitions as $id => $definition) {
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 @@ public function process(ContainerBuilder $container)
$alias = $aliases[$target];
}
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) {
$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);
}
}
}
@@ -58,14 +58,9 @@
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
<tag name="kernel.event_subscriber" />
<argument type="service">
<service class="Symfony\Component\DependencyInjection\ServiceLocator">
<tag name="container.service_locator" />
<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 type="service_locator">
<argument key="session" type="service" id="session" on-invalid="ignore" />
<argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
</argument>
</service>
@@ -24,13 +24,8 @@
<service id="test.session.listener" class="Symfony\Component\HttpKernel\EventListener\TestSessionListener">
<tag name="kernel.event_subscriber" />
<argument type="service">
<service class="Symfony\Component\DependencyInjection\ServiceLocator">
<tag name="container.service_locator" />
<argument type="collection">
<argument key="session" type="service" id="session" on-invalid="ignore" />
</argument>
</service>
<argument type="service_locator">
<argument key="session" type="service" id="session" on-invalid="ignore" />
</argument>
</service>
@@ -19,7 +19,7 @@
"php": "^7.1.3",
"ext-xml": "*",
"symfony/cache": "~3.4|~4.0",
"symfony/dependency-injection": "^4.1.1",
"symfony/dependency-injection": "^4.2",
"symfony/config": "~4.2",
"symfony/event-dispatcher": "^4.1",
"symfony/http-foundation": "^4.1",
@@ -27,14 +27,9 @@
<service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" />
<service id="security.helper" class="Symfony\Component\Security\Core\Security">
<argument type="service">
<service class="Symfony\Component\DependencyInjection\ServiceLocator">
<tag name="container.service_locator" />
<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 type="service_locator">
<argument key="security.token_storage" type="service" id="security.token_storage" />
<argument key="security.authorization_checker" type="service" id="security.authorization_checker" />
</argument>
</service>
<service id="Symfony\Component\Security\Core\Security" alias="security.helper" />
@@ -20,7 +20,7 @@
"ext-xml": "*",
"symfony/config": "^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"
},
"require-dev": {
@@ -11,45 +11,12 @@
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.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class IteratorArgument implements ArgumentInterface
{
private $values;
/**
* @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;
}
use ReferenceSetArgumentTrait;
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
@@ -5,6 +5,7 @@ CHANGELOG
-----
* added `ServiceSubscriberTrait`
* added `ServiceLocatorArgument` for creating optimized service-locators
4.1.0
-----
@@ -11,13 +11,11 @@
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
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\ServiceLocator;
use Symfony\Component\DependencyInjection\Reference;
/**
@@ -41,7 +39,7 @@ public function process(ContainerBuilder $container)
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EnvVarProcessorInterface::class));
}
foreach ($class::getProvidedTypes() as $prefix => $type) {
$processors[$prefix] = new ServiceClosureArgument(new Reference($id));
$processors[$prefix] = new Reference($id);
$types[$prefix] = self::validateProvidedTypes($type, $class);
}
}
@@ -56,9 +54,8 @@ public function process(ContainerBuilder $container)
}
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)
->setArguments(array($processors))
;
}
}
@@ -13,6 +13,7 @@
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -28,6 +29,9 @@
{
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')) {
return parent::processValue($value, $isRoot);
}
Oops, something went wrong.

0 comments on commit 6d3f63d

Please sign in to comment.