minor #25867 [DI] Put non-shared service factories in closures (nicolas-grekas)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[DI] Put non-shared service factories in closures

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

With this change, non-shared services are moved to dedicated files (unless they are on the hot path).
Previously, they were always dumped as methods.

The goal of this change is to dump factories as methods *if and only if* the services they build are on the hot-path. By doing so, it will become very easy to figure out which services are on the hot path, vs the rest. And then people will be able to optimize their configurations: if too many things are dumped as  methods, it will trivially mean some laziness is missing in definitions.

I spotted this while reviewing the dumped container of Blackfire, where we sometimes have long chains of dependencies that are on the hot path for no real reason - mixed with big non-shared factories (Sonata admin blocks in our case.)

Commits
-------

22c5325 [DI] Put non-shared service factories in closures
This commit is contained in:
Nicolas Grekas 2018-02-04 11:44:28 +01:00
commit 7a461cff0c
3 changed files with 37 additions and 18 deletions

View File

@ -43,6 +43,7 @@ class Container implements ResettableContainerInterface
protected $services = array();
protected $fileMap = array();
protected $methodMap = array();
protected $factories = array();
protected $aliases = array();
protected $loading = array();
protected $resolving = array();
@ -227,6 +228,9 @@ class Container implements ResettableContainerInterface
if ('service_container' === $id) {
return $this;
}
if (isset($this->factories[$id])) {
return $this->factories[$id]();
}
if (isset($this->loading[$id])) {
throw new ServiceCircularReferenceException($id, array_keys($this->loading));
@ -296,7 +300,7 @@ class Container implements ResettableContainerInterface
*/
public function reset()
{
$this->services = array();
$this->services = $this->factories = array();
}
/**

View File

@ -713,7 +713,7 @@ EOTXT
$lazyInitialization = '';
}
$asFile = $this->asFiles && $definition->isShared() && !$this->isHotPath($definition);
$asFile = $this->asFiles && !$this->isHotPath($definition);
$methodName = $this->generateMethodName($id);
if ($asFile) {
$file = $methodName.'.php';
@ -787,7 +787,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition))) {
if ($definition->isSynthetic() || ($this->asFiles && !$this->isHotPath($definition))) {
continue;
}
if ($definition->isPublic()) {
@ -805,8 +805,15 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) {
if (!$definition->isSynthetic() && !$this->isHotPath($definition)) {
$code = $this->addService($id, $definition, $file);
if (!$definition->isShared()) {
$code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code)));
$factory = sprintf('$this->factories%s[\'%s\']', $definition->isPublic() ? '' : "['service_container']", $id);
$code = sprintf("\n%s = function () {\n%s};\n\nreturn %1\$s();\n", $factory, $code);
}
yield $file => $code;
}
}
@ -1038,7 +1045,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || !$definition->isShared() || $this->isHotPath($definition))) {
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->isHotPath($definition))) {
$code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n";
}
}
@ -1052,7 +1059,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && $definition->isPublic() && $definition->isShared() && !$this->isHotPath($definition)) {
if (!$definition->isSynthetic() && $definition->isPublic() && !$this->isHotPath($definition)) {
$code .= sprintf(" %s => __DIR__.'/%s.php',\n", $this->doExport($id), $this->generateMethodName($id));
}
}
@ -1623,8 +1630,12 @@ EOF;
if ($definition->isShared()) {
$code = sprintf('$this->%s[\'%s\'] = %s', $definition->isPublic() ? 'services' : 'privates', $id, $code);
}
} elseif ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition)) {
} elseif ($this->asFiles && !$this->isHotPath($definition)) {
$code = sprintf("\$this->load(__DIR__.'/%s.php')", $this->generateMethodName($id));
if (!$definition->isShared()) {
$factory = sprintf('$this->factories%s[\'%s\']', $definition->isPublic() ? '' : "['service_container']", $id);
$code = sprintf('(isset(%s) ? %1$s() : %s)', $factory, $code);
}
} else {
$code = sprintf('$this->%s()', $this->generateMethodName($id));
}

View File

@ -179,6 +179,20 @@ $this->services['foo.baz'] = $instance = \BazClass::getInstance();
return $instance;
[Container%s/getFooBarService.php] => <?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
$this->factories['foo_bar'] = function () {
// Returns the public 'foo_bar' service.
return new \Bar\FooClass(($this->services['deprecated_service'] ?? $this->load(__DIR__.'/getDeprecatedServiceService.php')));
};
return $this->factories['foo_bar']();
[Container%s/getFooWithInlineService.php] => <?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
@ -331,7 +345,6 @@ class ProjectServiceContainer extends Container
);
$this->methodMap = array(
'bar' => 'getBarService',
'foo_bar' => 'getFooBarService',
);
$this->fileMap = array(
'BAR' => __DIR__.'/getBAR2Service.php',
@ -347,6 +360,7 @@ class ProjectServiceContainer extends Container
'factory_service_simple' => __DIR__.'/getFactoryServiceSimpleService.php',
'foo' => __DIR__.'/getFooService.php',
'foo.baz' => __DIR__.'/getFoo_BazService.php',
'foo_bar' => __DIR__.'/getFooBarService.php',
'foo_with_inline' => __DIR__.'/getFooWithInlineService.php',
'lazy_context' => __DIR__.'/getLazyContextService.php',
'lazy_context_ignore_invalid_ref' => __DIR__.'/getLazyContextIgnoreInvalidRefService.php',
@ -404,16 +418,6 @@ class ProjectServiceContainer extends Container
return $instance;
}
/**
* Gets the public 'foo_bar' service.
*
* @return \Bar\FooClass
*/
protected function getFooBarService()
{
return new \Bar\FooClass(($this->services['deprecated_service'] ?? $this->load(__DIR__.'/getDeprecatedServiceService.php')));
}
public function getParameter($name)
{
$name = (string) $name;