minor #24872 [DI] Add "container.hot_path" tag to flag the hot path and inline related services (nicolas-grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[DI] Add "container.hot_path" tag to flag the hot path and inline related services

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

This PR is the result of my quest to squeeze some performance out of 3.4/4.0.

It builds on two ideas:
- a new `container.inline` tag that identifies the services that are *always* needed. This tag is only applied to a very short list of bootstrapping services (`router`, `event_dispatcher`, `http_kernel` and `request_stack` only). Then, it is propagated to all dependencies of these services, with a special case for event listeners, where only listed events are propagated to their related listeners.
- replacing the PHP autoloader by plain inlined `require_once` in generated service factories, with the benefit of completely bypassing the autoloader for services and their class hierarchy.

The end result is significant, even on a simple Hello World.
Here is the Blackfire profile, results are consistent with `ab` benchmarks:

https://blackfire.io/profiles/compare/b5fa5ef0-755c-4967-b990-572305f8f381/graph

![capture du 2017-11-08 16-54-28](https://user-images.githubusercontent.com/243674/32558666-a3f439b2-c4a5-11e7-83a3-db588c3e21e5.png)

Commits
-------

f7cb559a06 [DI] Add "container.hot_path" tag to flag the hot path and inline related services
This commit is contained in:
Fabien Potencier 2017-11-09 06:24:55 -08:00
commit a3e0e49094
21 changed files with 578 additions and 35 deletions

View File

@ -24,6 +24,7 @@ class UnusedTagsPass implements CompilerPassInterface
private $whitelist = array(
'cache.pool.clearer',
'console.command',
'container.hot_path',
'container.service_locator',
'container.service_subscriber',
'controller.service_arguments',

View File

@ -43,6 +43,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\FragmentRendererPass;
use Symfony\Component\Form\DependencyInjection\FormPass;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Config\Resource\ClassExistenceResource;
use Symfony\Component\Translation\DependencyInjection\TranslationDumperPass;
use Symfony\Component\Translation\DependencyInjection\TranslationExtractorPass;
@ -83,6 +84,14 @@ class FrameworkBundle extends Bundle
{
parent::build($container);
$hotPathEvents = array(
KernelEvents::REQUEST,
KernelEvents::CONTROLLER,
KernelEvents::CONTROLLER_ARGUMENTS,
KernelEvents::RESPONSE,
KernelEvents::FINISH_REQUEST,
);
$container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
$container->addCompilerPass(new RegisterControllerArgumentLocatorsPass());
$container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING);
@ -90,7 +99,7 @@ class FrameworkBundle extends Bundle
$container->addCompilerPass(new ProfilerPass());
// must be registered before removing private services as some might be listeners/subscribers
// but as late as possible to get resolved parameters
$container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass((new RegisterListenersPass())->setHotPathEvents($hotPathEvents), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new TemplatingPass());
$this->addCompilerPassIfExists($container, AddConstraintValidatorsPass::class, PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new AddAnnotationsCachedReaderPass(), PassConfig::TYPE_BEFORE_REMOVING);

View File

@ -9,6 +9,7 @@
<service id="event_dispatcher" class="Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher" public="true">
<argument type="service" id="service_container" />
<tag name="container.hot_path" />
</service>
<service id="Symfony\Component\EventDispatcher\EventDispatcherInterface" alias="event_dispatcher" />
@ -17,6 +18,7 @@
<argument type="service" id="controller_resolver" />
<argument type="service" id="request_stack" />
<argument type="service" id="argument_resolver" />
<tag name="container.hot_path" />
</service>
<service id="Symfony\Component\HttpKernel\HttpKernelInterface" alias="http_kernel" />

View File

@ -22,7 +22,7 @@
"symfony/class-loader": "~3.2",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/config": "~3.4|~4.0",
"symfony/event-dispatcher": "^3.3.1|~4.0",
"symfony/event-dispatcher": "^3.4-beta4|~4.0-beta4",
"symfony/http-foundation": "^3.3.11|~4.0",
"symfony/http-kernel": "~3.4|~4.0",
"symfony/polyfill-mbstring": "~1.0",

View File

@ -89,6 +89,7 @@ class PassConfig
)),
new DefinitionErrorExceptionPass(),
new CheckExceptionOnInvalidReferenceBehaviorPass(),
new ResolveHotPathPass(),
));
}

View File

@ -0,0 +1,71 @@
<?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 = array();
public function __construct($tagName = 'container.hot_path')
{
$this->tagName = $tagName;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
try {
parent::process($container);
$container->getDefinition('service_container')->clearTag($this->tagName);
} finally {
$this->resolvedIds = array();
}
}
/**
* {@inheritdoc}
*/
protected function processValue($value, $isRoot = false)
{
if ($value instanceof ArgumentInterface) {
return $value;
}
if ($value instanceof Definition && $isRoot && (isset($this->resolvedIds[$this->currentId]) || !$value->hasTag($this->tagName))) {
return $value;
}
if ($value instanceof Reference && ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE !== $value->getInvalidBehavior() && $this->container->has($id = (string) $value)) {
$definition = $this->container->findDefinition($id);
if (!$definition->hasTag($this->tagName)) {
$this->resolvedIds[$id] = true;
$definition->addTag($this->tagName);
parent::processValue($definition, false);
}
return $value;
}
return parent::processValue($value, $isRoot);
}
}

View File

@ -63,6 +63,9 @@ class PhpDumper extends Dumper
private $usedMethodNames;
private $namespace;
private $asFiles;
private $hotPathTag;
private $inlineRequires;
private $inlinedRequires = array();
/**
* @var ProxyDumper
@ -108,16 +111,21 @@ class PhpDumper extends Dumper
public function dump(array $options = array())
{
$this->targetDirRegex = null;
$this->inlinedRequires = array();
$options = array_merge(array(
'class' => 'ProjectServiceContainer',
'base_class' => 'Container',
'namespace' => '',
'as_files' => false,
'debug' => true,
'hot_path_tag' => null,
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
), $options);
$this->namespace = $options['namespace'];
$this->asFiles = $options['as_files'];
$this->hotPathTag = $options['hot_path_tag'];
$this->inlineRequires = $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']);
$this->initializeMethodNamesMap($options['base_class']);
$this->docStar = $options['debug'] ? '*' : '';
@ -214,6 +222,7 @@ EOF;
}
$this->targetDirRegex = null;
$this->inlinedRequires = array();
$unusedEnvs = array();
foreach ($this->container->getEnvCounters() as $env => $use) {
@ -257,9 +266,13 @@ EOF;
array_unshift($inlinedDefinitions, $definition);
$collectLineage = $this->inlineRequires && !($this->hotPathTag && $definition->hasTag($this->hotPathTag));
$isNonLazyShared = !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared();
$calls = $behavior = array();
$lineage = $calls = $behavior = array();
foreach ($inlinedDefinitions as $iDefinition) {
if ($collectLineage && $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) {
$this->collectLineage($class, $lineage);
}
$this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior, $isNonLazyShared);
$isPreInstantiation = $isNonLazyShared && $iDefinition !== $definition && !$this->hasReference($cId, $iDefinition->getMethodCalls(), true) && !$this->hasReference($cId, $iDefinition->getProperties(), true);
$this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior, $isPreInstantiation);
@ -274,6 +287,13 @@ EOF;
continue;
}
if ($collectLineage && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id] && $this->container->has($id)
&& $this->isTrivialInstance($iDefinition = $this->container->findDefinition($id))
&& $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()
) {
$this->collectLineage($class, $lineage);
}
if ($callCount > 1) {
$name = $this->getNextVariableName();
$this->referenceVariables[$id] = new Variable($name);
@ -300,9 +320,48 @@ EOTXT;
$code .= "\n";
}
if ($lineage && $lineage = array_diff_key(array_flip($lineage), $this->inlinedRequires)) {
$code = "\n".$code;
foreach (array_reverse($lineage) as $file => $class) {
$code = sprintf(" require_once %s;\n", $file).$code;
}
}
return $code;
}
private function collectLineage($class, array &$lineage)
{
if (isset($lineage[$class])) {
return;
}
if (!$r = $this->container->getReflectionClass($class)) {
return;
}
if ($this->container instanceof $class) {
return;
}
$file = $r->getFileName();
if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) {
return;
}
if ($parent = $r->getParentClass()) {
$this->collectLineage($parent->name, $lineage);
}
foreach ($r->getInterfaces() as $parent) {
$this->collectLineage($parent->name, $lineage);
}
foreach ($r->getTraits() as $parent) {
$this->collectLineage($parent->name, $lineage);
}
$lineage[$class] = substr($exportedFile, 1, -1);
}
private function generateProxyClasses()
{
$definitions = $this->container->getDefinitions();
@ -509,10 +568,15 @@ EOTXT;
if (!$v || ($v instanceof Reference && 'service_container' === (string) $v)) {
continue;
}
if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) {
continue;
}
if (!is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) {
return false;
}
}
} elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) {
continue;
} elseif (!is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) {
return false;
}
@ -694,7 +758,7 @@ EOTXT;
$lazyInitialization = '';
}
$asFile = $this->asFiles && $definition->isShared();
$asFile = $this->asFiles && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag));
$methodName = $this->generateMethodName($id);
if ($asFile) {
$file = $methodName.'.php';
@ -760,7 +824,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared())) {
if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag)))) {
continue;
}
if ($definition->isPublic()) {
@ -778,7 +842,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && $definition->isShared()) {
if (!$definition->isSynthetic() && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag))) {
$code = $this->addService($id, $definition, $file);
yield $file => $code;
}
@ -899,6 +963,7 @@ EOF;
$code .= $this->asFiles ? $this->addFileMap() : '';
$code .= $this->addPrivateServices();
$code .= $this->addAliases();
$code .= $this->addInlineRequires();
$code .= <<<'EOF'
}
@ -1050,7 +1115,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && (!$this->asFiles || !$definition->isShared())) {
if (!$definition->isSynthetic() && (!$this->asFiles || !$definition->isShared() || ($this->hotPathTag && $definition->hasTag($this->hotPathTag)))) {
$code .= ' '.$this->export($id).' => '.$this->export($this->generateMethodName($id)).",\n";
}
}
@ -1069,7 +1134,7 @@ EOF;
$definitions = $this->container->getDefinitions();
ksort($definitions);
foreach ($definitions as $id => $definition) {
if (!$definition->isSynthetic() && $definition->isShared()) {
if (!$definition->isSynthetic() && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag))) {
$code .= sprintf(" %s => __DIR__.'/%s.php',\n", $this->export($id), $this->generateMethodName($id));
}
}
@ -1137,6 +1202,38 @@ EOF;
return $code." );\n";
}
private function addInlineRequires()
{
if (!$this->hotPathTag || !$this->inlineRequires) {
return '';
}
$lineage = array();
foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) {
$definition = $this->container->getDefinition($id);
$inlinedDefinitions = $this->getInlinedDefinitions($definition);
array_unshift($inlinedDefinitions, $definition);
foreach ($inlinedDefinitions as $iDefinition) {
if ($class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) {
$this->collectLineage($class, $lineage);
}
}
}
$code = "\n";
foreach ($lineage as $file) {
if (!isset($this->inlinedRequires[$file])) {
$this->inlinedRequires[$file] = true;
$code .= sprintf(" require_once %s;\n", $file);
}
}
return "\n" === $code ? '' : $code;
}
/**
* Adds default parameters method.
*
@ -1408,7 +1505,7 @@ EOF;
$id = (string) $argument;
if (!isset($calls[$id])) {
$calls[$id] = (int) $isPreInstantiation;
$calls[$id] = (int) ($isPreInstantiation && $this->container->has($id) && !$this->container->findDefinition($id)->isSynthetic());
}
if (!isset($behavior[$id])) {
$behavior[$id] = $argument->getInvalidBehavior();
@ -1746,9 +1843,7 @@ EOF;
return '$this';
}
if ($this->container->hasDefinition($id)) {
$definition = $this->container->getDefinition($id);
if ($this->container->hasDefinition($id) && ($definition = $this->container->getDefinition($id)) && !$definition->isSynthetic()) {
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
$code = 'null';
} elseif ($this->isTrivialInstance($definition)) {
@ -1756,7 +1851,7 @@ EOF;
if ($definition->isShared()) {
$code = sprintf('$this->services[\'%s\'] = %s', $id, $code);
}
} elseif ($this->asFiles && $definition->isShared()) {
} elseif ($this->asFiles && $definition->isShared() && !($this->hotPathTag && $definition->hasTag($this->hotPathTag))) {
$code = sprintf("\$this->load(__DIR__.'/%s.php')", $this->generateMethodName($id));
} else {
$code = sprintf('$this->%s()', $this->generateMethodName($id));

View File

@ -0,0 +1,51 @@
<?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\Tests\Compiler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Compiler\ResolveHotPathPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class ResolveHotPathPassTest extends TestCase
{
public function testProcess()
{
$container = new ContainerBuilder();
$container->register('foo')
->addTag('container.hot_path')
->addArgument(new IteratorArgument(array(new Reference('lazy'))))
->addArgument(new Reference('service_container'))
->addArgument(new Definition('', array(new Reference('bar'))))
->addArgument(new Reference('baz', ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE))
->addArgument(new Reference('missing'))
;
$container->register('lazy');
$container->register('bar');
$container->register('bar')->addArgument(new Reference('buz'));
$container->register('baz')->addArgument(new Reference('lazy'));
$container->register('baz')->addArgument(new Reference('lazy'));
$container->register('buz');
(new ResolveHotPathPass())->process($container);
$this->assertFalse($container->getDefinition('lazy')->hasTag('container.hot_path'));
$this->assertTrue($container->getDefinition('bar')->hasTag('container.hot_path'));
$this->assertTrue($container->getDefinition('buz')->hasTag('container.hot_path'));
$this->assertFalse($container->getDefinition('baz')->hasTag('container.hot_path'));
$this->assertFalse($container->getDefinition('service_container')->hasTag('container.hot_path'));
}
}

View File

@ -170,9 +170,10 @@ class PhpDumperTest extends TestCase
public function testDumpAsFiles()
{
$container = include self::$fixturesPath.'/containers/container9.php';
$container->getDefinition('bar')->addTag('hot');
$container->compile();
$dumper = new PhpDumper($container);
$dump = print_r($dumper->dump(array('as_files' => true, 'file' => __DIR__)), true);
$dump = print_r($dumper->dump(array('as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot')), true);
if ('\\' === DIRECTORY_SEPARATOR) {
$dump = str_replace('\\\\Fixtures\\\\includes\\\\foo.php', '/Fixtures/includes/foo.php', $dump);
}
@ -798,6 +799,21 @@ class PhpDumperTest extends TestCase
$this->assertSame($foo, $foo->bar->foobar->foo);
}
public function testHotPathOptimizations()
{
$container = include self::$fixturesPath.'/containers/container_inline_requires.php';
$container->setParameter('inline_requires', true);
$container->compile();
$dumper = new PhpDumper($container);
$dump = $dumper->dump(array('hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'inline_requires', 'file' => self::$fixturesPath.'/php/container_inline_requires.php'));
if ('\\' === DIRECTORY_SEPARATOR) {
$dump = str_replace("'\\\\includes\\\\HotPath\\\\", "'/includes/HotPath/", $dump);
}
$this->assertStringEqualsFile(self::$fixturesPath.'/php/container_inline_requires.php', $dump);
}
public function testDumpHandlesLiteralClassWithRootNamespace()
{
$container = new ContainerBuilder();

View File

@ -0,0 +1,17 @@
<?php
namespace Symfony\Tests\InlineRequires;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath;
$container = new ContainerBuilder();
$container->register(HotPath\C1::class)->addTag('container.hot_path')->setPublic(true);
$container->register(HotPath\C2::class)->addArgument(new Reference(HotPath\C3::class))->setPublic(true);
$container->register(HotPath\C3::class);
return $container;

View File

@ -0,0 +1,8 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath;
class C1 extends P1
{
use T1;
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath;
class C2
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath;
class C3
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath;
interface I1
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath;
class P1 implements I1
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath;
trait T1
{
}

View File

@ -0,0 +1,204 @@
<?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 ProjectServiceContainer extends Container
{
private $parameters;
private $targetDirs = array();
public function __construct()
{
$dir = __DIR__;
for ($i = 1; $i <= 5; ++$i) {
$this->targetDirs[$i] = $dir = dirname($dir);
}
$this->parameters = $this->getDefaultParameters();
$this->services = array();
$this->normalizedIds = array(
'symfony\\component\\dependencyinjection\\tests\\fixtures\\includes\\hotpath\\c1' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C1',
'symfony\\component\\dependencyinjection\\tests\\fixtures\\includes\\hotpath\\c2' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C2',
'symfony\\component\\dependencyinjection\\tests\\fixtures\\includes\\hotpath\\c3' => 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C3',
);
$this->methodMap = array(
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C1' => 'getC1Service',
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C2' => 'getC2Service',
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C3' => 'getC3Service',
);
$this->privates = array(
'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C3' => true,
);
$this->aliases = array();
require_once $this->targetDirs[1].'/includes/HotPath/I1.php';
require_once $this->targetDirs[1].'/includes/HotPath/P1.php';
require_once $this->targetDirs[1].'/includes/HotPath/T1.php';
require_once $this->targetDirs[1].'/includes/HotPath/C1.php';
}
public function getRemovedIds()
{
return array(
'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
);
}
public function compile()
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
public function isCompiled()
{
return true;
}
public function isFrozen()
{
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED);
return true;
}
/**
* Gets the public 'Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1' shared service.
*
* @return \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1
*/
protected function getC1Service()
{
return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1();
}
/**
* Gets the public 'Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2' shared service.
*
* @return \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2
*/
protected function getC2Service()
{
require_once $this->targetDirs[1].'/includes/HotPath/C2.php';
require_once $this->targetDirs[1].'/includes/HotPath/C3.php';
$a = ${($_ = isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3']) ? $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] : $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()) && false ?: '_'};
if (isset($this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'])) {
return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'];
}
return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2($a);
}
/**
* Gets the private 'Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3' shared service.
*
* @return \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3
*/
protected function getC3Service()
{
require_once $this->targetDirs[1].'/includes/HotPath/C3.php';
return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3();
}
public function getParameter($name)
{
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
$name = $this->normalizeParameterName($name);
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
}
}
if (isset($this->loadedDynamicParameters[$name])) {
return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
}
return $this->parameters[$name];
}
public function hasParameter($name)
{
$name = $this->normalizeParameterName($name);
return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters);
}
public function setParameter($name, $value)
{
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
}
public function getParameterBag()
{
if (null === $this->parameterBag) {
$parameters = $this->parameters;
foreach ($this->loadedDynamicParameters as $name => $loaded) {
$parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
}
$this->parameterBag = new FrozenParameterBag($parameters);
}
return $this->parameterBag;
}
private $loadedDynamicParameters = array();
private $dynamicParameters = array();
/**
* Computes a dynamic parameter.
*
* @param string The name of the dynamic parameter to load
*
* @return mixed The value of the dynamic parameter
*
* @throws InvalidArgumentException When the dynamic parameter does not exist
*/
private function getDynamicParameter($name)
{
throw new InvalidArgumentException(sprintf('The dynamic parameter "%s" must be defined.', $name));
}
private $normalizedParameterNames = array();
private function normalizeParameterName($name)
{
if (isset($this->normalizedParameterNames[$normalizedName = strtolower($name)]) || isset($this->parameters[$normalizedName]) || array_key_exists($normalizedName, $this->parameters)) {
$normalizedName = isset($this->normalizedParameterNames[$normalizedName]) ? $this->normalizedParameterNames[$normalizedName] : $normalizedName;
if ((string) $name !== $normalizedName) {
@trigger_error(sprintf('Parameter names will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since version 3.4.', $name, $normalizedName), E_USER_DEPRECATED);
}
} else {
$normalizedName = $this->normalizedParameterNames[$normalizedName] = (string) $name;
}
return $normalizedName;
}
/**
* Gets the default parameters.
*
* @return array An array of the default parameters
*/
protected function getDefaultParameters()
{
return array(
'inline_requires' => true,
);
}
}

View File

@ -13,25 +13,6 @@ return array(
'new_factory' => true,
);
[Container%s/getBarService.php] => <?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
// Returns the public 'bar' shared service.
$a = ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->load(__DIR__.'/getFoo_BazService.php')) && false ?: '_'};
if (isset($this->services['bar'])) {
return $this->services['bar'];
}
$this->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $this->getParameter('foo_bar'));
$a->configure($instance);
return $instance;
[Container%s/getBazService.php] => <?php
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
@ -168,7 +149,7 @@ $this->services['foo'] = $instance = \Bar\FooClass::getInstance('foo', $a, array
$instance->foo = 'bar';
$instance->moo = $a;
$instance->qux = array('bar' => 'foo is bar', 'foobar' => 'bar');
$instance->setBar(${($_ = isset($this->services['bar']) ? $this->services['bar'] : $this->load(__DIR__.'/getBarService.php')) && false ?: '_'});
$instance->setBar(${($_ = isset($this->services['bar']) ? $this->services['bar'] : $this->getBarService()) && false ?: '_'});
$instance->initialize();
sc_configure($instance);
@ -329,10 +310,10 @@ class Container%s extends Container
'request' => true,
);
$this->methodMap = array(
'bar' => 'getBarService',
'foo_bar' => 'getFooBarService',
);
$this->fileMap = array(
'bar' => __DIR__.'/getBarService.php',
'baz' => __DIR__.'/getBazService.php',
'configured_service' => __DIR__.'/getConfiguredServiceService.php',
'configured_service_simple' => __DIR__.'/getConfiguredServiceSimpleService.php',
@ -391,6 +372,26 @@ class Container%s extends Container
return require $file;
}
/**
* Gets the public 'bar' shared service.
*
* @return \Bar\FooClass
*/
protected function getBarService()
{
$a = ${($_ = isset($this->services['foo.baz']) ? $this->services['foo.baz'] : $this->load(__DIR__.'/getFoo_BazService.php')) && false ?: '_'};
if (isset($this->services['bar'])) {
return $this->services['bar'];
}
$this->services['bar'] = $instance = new \Bar\FooClass('foo', $a, $this->getParameter('foo_bar'));
$a->configure($instance);
return $instance;
}
/**
* Gets the public 'foo_bar' service.
*

View File

@ -28,6 +28,9 @@ class RegisterListenersPass implements CompilerPassInterface
protected $listenerTag;
protected $subscriberTag;
private $hotPathEvents = array();
private $hotPathTagName;
/**
* @param string $dispatcherService Service name of the event dispatcher in processed container
* @param string $listenerTag Tag name used for listener
@ -40,6 +43,14 @@ class RegisterListenersPass implements CompilerPassInterface
$this->subscriberTag = $subscriberTag;
}
public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path')
{
$this->hotPathEvents = array_flip($hotPathEvents);
$this->hotPathTagName = $tagName;
return $this;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
@ -65,6 +76,10 @@ class RegisterListenersPass implements CompilerPassInterface
}
$definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority));
if (isset($this->hotPathEvents[$event['event']])) {
$container->getDefinition($id)->addTag($this->hotPathTagName);
}
}
}
@ -91,6 +106,10 @@ class RegisterListenersPass implements CompilerPassInterface
foreach ($extractingDispatcher->listeners as $args) {
$args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]);
$definition->addMethodCall('addListener', $args);
if (isset($this->hotPathEvents[$args[0]])) {
$container->getDefinition($id)->addTag('container.hot_path');
}
}
$extractingDispatcher->listeners = array();
}

View File

@ -141,6 +141,18 @@ class RegisterListenersPassTest extends TestCase
$this->assertEquals($expectedCalls, $definition->getMethodCalls());
}
public function testHotPathEvents()
{
$container = new ContainerBuilder();
$container->register('foo', SubscriberService::class)->addTag('kernel.event_subscriber', array());
$container->register('event_dispatcher', 'stdClass');
(new RegisterListenersPass())->setHotPathEvents(array('event'))->process($container);
$this->assertTrue($container->getDefinition('foo')->hasTag('container.hot_path'));
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class"

View File

@ -818,6 +818,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
'file' => $cache->getPath(),
'as_files' => true,
'debug' => $this->debug,
'hot_path_tag' => !$this->loadClassCache ? 'container.hot_path' : null,
));
$rootCode = array_pop($content);