Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e457b24

Browse filesBrowse files
committed
bug #36103 [DI] fix preloading script generation (nicolas-grekas)
This PR was merged into the 4.4 branch. Discussion ---------- [DI] fix preloading script generation | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - (fabbot failure is a false positive) On master, we should work on being able to preload more classes (esp. all cache-warmup artifacts). But for 4.4, this is good enough. Submitted as a bug fix because 1. the current code that deals with preloading kinda-works, but only on "dev" mode... and 2. fixing it provides a nice boost! Small bench on a hello world: - before: 380 req/s - after: 580 req/s That's +50%! Pro-tip: adding a few `class_exists()` as done in this PR for the classes that are always used in the implementations (e.g. `new Foo()` in the constructor) will help the preload-script generator to work optimally. Without them, it will discover the symbols to preload only if they're found on methods. Some of those `class_exists()` are mandatory, in relation to anonymous classes and https://bugs.php.net/79349 Commits ------- a10fc4d [DI] fix preloading script generation
2 parents abefccf + a10fc4d commit e457b24
Copy full SHA for e457b24

30 files changed

+245
-14
lines changed

‎src/Symfony/Bridge/Twig/Extension/TranslationExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bridge/Twig/Extension/TranslationExtension.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
use Twig\TokenParser\AbstractTokenParser;
2525
use Twig\TwigFilter;
2626

27+
// Help opcache.preload discover always-needed symbols
28+
class_exists(TranslatorInterface::class);
29+
2730
/**
2831
* Provides integration of the Translation component with Twig.
2932
*

‎src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass;
2424
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
2525
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
26+
use Symfony\Component\Cache\Adapter\ApcuAdapter;
27+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
28+
use Symfony\Component\Cache\Adapter\ChainAdapter;
29+
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
30+
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
2631
use Symfony\Component\Cache\DependencyInjection\CacheCollectorPass;
2732
use Symfony\Component\Cache\DependencyInjection\CachePoolClearerPass;
2833
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
@@ -32,6 +37,7 @@
3237
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
3338
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
3439
use Symfony\Component\DependencyInjection\ContainerBuilder;
40+
use Symfony\Component\Dotenv\Dotenv;
3541
use Symfony\Component\ErrorHandler\ErrorHandler;
3642
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
3743
use Symfony\Component\Form\DependencyInjection\FormPass;
@@ -58,6 +64,19 @@
5864
use Symfony\Component\Validator\DependencyInjection\AddAutoMappingConfigurationPass;
5965
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
6066
use Symfony\Component\Validator\DependencyInjection\AddValidatorInitializersPass;
67+
use Symfony\Component\VarExporter\Internal\Hydrator;
68+
use Symfony\Component\VarExporter\Internal\Registry;
69+
70+
// Help opcache.preload discover always-needed symbols
71+
class_exists(ApcuAdapter::class);
72+
class_exists(ArrayAdapter::class);
73+
class_exists(ChainAdapter::class);
74+
class_exists(PhpArrayAdapter::class);
75+
class_exists(PhpFilesAdapter::class);
76+
class_exists(Dotenv::class);
77+
class_exists(ErrorHandler::class);
78+
class_exists(Hydrator::class);
79+
class_exists(Registry::class);
6180

6281
/**
6382
* Bundle.

‎src/Symfony/Bundle/FrameworkBundle/Routing/Router.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Routing/Router.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@
2020
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
2121
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
2222
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
23+
use Symfony\Component\Routing\Annotation\Route;
2324
use Symfony\Component\Routing\RequestContext;
2425
use Symfony\Component\Routing\RouteCollection;
2526
use Symfony\Component\Routing\Router as BaseRouter;
2627

28+
// Help opcache.preload discover always-needed symbols
29+
class_exists(RedirectableCompiledUrlMatcher::class);
30+
class_exists(Route::class);
31+
2732
/**
2833
* This Router creates the Loader only when the cache is empty.
2934
*

‎src/Symfony/Bundle/TwigBundle/TwigBundle.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/TwigBundle/TwigBundle.php
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@
2020
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
2121
use Symfony\Component\DependencyInjection\ContainerBuilder;
2222
use Symfony\Component\HttpKernel\Bundle\Bundle;
23+
use Twig\Cache\FilesystemCache;
24+
use Twig\Extension\CoreExtension;
25+
use Twig\Extension\EscaperExtension;
26+
use Twig\Extension\OptimizerExtension;
27+
use Twig\Extension\StagingExtension;
28+
use Twig\ExtensionSet;
29+
30+
// Help opcache.preload discover always-needed symbols
31+
class_exists(FilesystemCache::class);
32+
class_exists(CoreExtension::class);
33+
class_exists(EscaperExtension::class);
34+
class_exists(OptimizerExtension::class);
35+
class_exists(StagingExtension::class);
36+
class_exists(ExtensionSet::class);
2337

2438
/**
2539
* Bundle.

‎src/Symfony/Component/Cache/Adapter/AdapterInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Cache/Adapter/AdapterInterface.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
use Psr\Cache\CacheItemPoolInterface;
1515
use Symfony\Component\Cache\CacheItem;
1616

17+
// Help opcache.preload discover always-needed symbols
18+
class_exists(CacheItem::class);
19+
1720
/**
1821
* Interface for adapters managing instances of Symfony's CacheItem.
1922
*

‎src/Symfony/Component/DependencyInjection/Container.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Container.php
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\DependencyInjection;
1313

14+
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceLocator as ArgumentServiceLocator;
1416
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
1517
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1618
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
@@ -22,6 +24,10 @@
2224
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
2325
use Symfony\Contracts\Service\ResetInterface;
2426

27+
// Help opcache.preload discover always-needed symbols
28+
class_exists(RewindableGenerator::class);
29+
class_exists(ArgumentServiceLocator::class);
30+
2531
/**
2632
* Container is a dependency injection container.
2733
*

‎src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+67-14Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class PhpDumper extends Dumper
8181
private $inlinedRequires = [];
8282
private $circularReferences = [];
8383
private $singleUsePrivateIds = [];
84+
private $preload = [];
8485
private $addThrow = false;
8586
private $addGetService = false;
8687
private $locatedIds = [];
@@ -142,6 +143,7 @@ public function dump(array $options = [])
142143
'hot_path_tag' => 'container.hot_path',
143144
'inline_factories_parameter' => 'container.dumper.inline_factories',
144145
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
146+
'preload_classes' => [],
145147
'service_locator_tag' => 'container.service_locator',
146148
'build_time' => time(),
147149
], $options);
@@ -226,8 +228,12 @@ public function dump(array $options = [])
226228

227229
$proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null;
228230

231+
if ($options['preload_classes']) {
232+
$this->preload = array_combine($options['preload_classes'], $options['preload_classes']);
233+
}
234+
229235
$code =
230-
$this->startClass($options['class'], $baseClass, $preload).
236+
$this->startClass($options['class'], $baseClass).
231237
$this->addServices($services).
232238
$this->addDeprecatedAliases().
233239
$this->addDefaultParametersMethod()
@@ -302,7 +308,7 @@ public function dump(array $options = [])
302308
$id = hash('crc32', $hash.$time);
303309
$this->asFiles = false;
304310

305-
if ($preload && null !== $autoloadFile = $this->getAutoloadFile()) {
311+
if ($this->preload && null !== $autoloadFile = $this->getAutoloadFile()) {
306312
$autoloadFile = substr($this->export($autoloadFile), 2, -1);
307313

308314
$code[$options['class'].'.preload.php'] = <<<EOF
@@ -320,8 +326,13 @@ public function dump(array $options = [])
320326
321327
EOF;
322328

323-
foreach ($preload as $class) {
324-
$code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class);
329+
foreach ($this->preload as $class) {
330+
if (!$class || false !== strpos($class, '$')) {
331+
continue;
332+
}
333+
if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) {
334+
$code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class);
335+
}
325336
}
326337

327338
$code[$options['class'].'.preload.php'] .= <<<'EOF'
@@ -367,6 +378,7 @@ public function dump(array $options = [])
367378
$this->circularReferences = [];
368379
$this->locatedIds = [];
369380
$this->exportedVariables = [];
381+
$this->preload = [];
370382

371383
$unusedEnvs = [];
372384
foreach ($this->container->getEnvCounters() as $env => $use) {
@@ -542,8 +554,10 @@ private function addServiceInclude(string $cId, Definition $definition): string
542554
if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) {
543555
$lineage = [];
544556
foreach ($this->inlinedDefinitions as $def) {
545-
if (!$def->isDeprecated() && \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) {
546-
$this->collectLineage($class, $lineage);
557+
if (!$def->isDeprecated()) {
558+
foreach ($this->getClasses($def) as $class) {
559+
$this->collectLineage($class, $lineage);
560+
}
547561
}
548562
}
549563

@@ -552,9 +566,10 @@ private function addServiceInclude(string $cId, Definition $definition): string
552566
&& ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior
553567
&& $this->container->has($id)
554568
&& $this->isTrivialInstance($def = $this->container->findDefinition($id))
555-
&& \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())
556569
) {
557-
$this->collectLineage($class, $lineage);
570+
foreach ($this->getClasses($def) as $class) {
571+
$this->collectLineage($class, $lineage);
572+
}
558573
}
559574
}
560575

@@ -804,6 +819,12 @@ protected function {$methodName}($lazyInitialization)
804819

805820
if ($definition->isDeprecated()) {
806821
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
822+
} else {
823+
foreach ($this->inlinedDefinitions as $def) {
824+
foreach ($this->getClasses($def) as $class) {
825+
$this->preload[$class] = $class;
826+
}
827+
}
807828
}
808829

809830
if ($this->getProxyDumper()->isProxyCandidate($definition)) {
@@ -960,7 +981,15 @@ private function addServices(array &$services = null): string
960981
$definitions = $this->container->getDefinitions();
961982
ksort($definitions);
962983
foreach ($definitions as $id => $definition) {
963-
$services[$id] = $definition->isSynthetic() ? null : $this->addService($id, $definition);
984+
if (!$definition->isSynthetic()) {
985+
$services[$id] = $this->addService($id, $definition);
986+
} else {
987+
$services[$id] = null;
988+
989+
foreach ($this->getClasses($definition) as $class) {
990+
$this->preload[$class] = $class;
991+
}
992+
}
964993
}
965994

966995
foreach ($definitions as $id => $definition) {
@@ -1061,7 +1090,7 @@ private function addNewInstance(Definition $definition, string $return = '', str
10611090
return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail;
10621091
}
10631092

1064-
private function startClass(string $class, string $baseClass, ?array &$preload): string
1093+
private function startClass(string $class, string $baseClass): string
10651094
{
10661095
$namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
10671096

@@ -1124,7 +1153,7 @@ public function __construct()
11241153
$code .= $this->addMethodMap();
11251154
$code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : '';
11261155
$code .= $this->addAliases();
1127-
$code .= $this->addInlineRequires($preload);
1156+
$code .= $this->addInlineRequires();
11281157
$code .= <<<EOF
11291158
}
11301159
@@ -1324,7 +1353,7 @@ protected function {$methodNameAlias}()
13241353
return $code;
13251354
}
13261355

1327-
private function addInlineRequires(?array &$preload): string
1356+
private function addInlineRequires(): string
13281357
{
13291358
if (!$this->hotPathTag || !$this->inlineRequires) {
13301359
return '';
@@ -1342,8 +1371,7 @@ private function addInlineRequires(?array &$preload): string
13421371
$inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]);
13431372

13441373
foreach ($inlinedDefinitions as $def) {
1345-
if (\is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) {
1346-
$preload[$class] = $class;
1374+
foreach ($this->getClasses($def) as $class) {
13471375
$this->collectLineage($class, $lineage);
13481376
}
13491377
}
@@ -2065,4 +2093,29 @@ private function getAutoloadFile(): ?string
20652093

20662094
return null;
20672095
}
2096+
2097+
private function getClasses(Definition $definition): array
2098+
{
2099+
$classes = [];
2100+
2101+
while ($definition instanceof Definition) {
2102+
$classes[] = trim($definition->getClass(), '\\');
2103+
$factory = $definition->getFactory();
2104+
2105+
if (!\is_array($factory)) {
2106+
$factory = [$factory];
2107+
}
2108+
2109+
if (\is_string($factory[0])) {
2110+
if (false !== $i = strrpos($factory[0], '::')) {
2111+
$factory[0] = substr($factory[0], 0, $i);
2112+
}
2113+
$classes[] = trim($factory[0], '\\');
2114+
}
2115+
2116+
$definition = $factory[0];
2117+
}
2118+
2119+
return array_filter($classes);
2120+
}
20682121
}

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt
+18Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,24 @@ class ProjectServiceContainer extends Container
531531
}
532532
}
533533

534+
[ProjectServiceContainer.preload.php] => <?php
535+
%A
536+
537+
$classes = [];
538+
$classes[] = 'Bar\FooClass';
539+
$classes[] = 'Baz';
540+
$classes[] = 'ConfClass';
541+
$classes[] = 'Bar';
542+
$classes[] = 'BazClass';
543+
$classes[] = 'Foo';
544+
$classes[] = 'LazyContext';
545+
$classes[] = 'FooBarBaz';
546+
$classes[] = 'FactoryClass';
547+
$classes[] = 'Request';
548+
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
549+
550+
%A
551+
534552
[ProjectServiceContainer.php] => <?php
535553

536554
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,16 @@ class ProjectServiceContainer extends Container
536536

537537
$classes = [];
538538
$classes[] = 'Bar\FooClass';
539+
$classes[] = 'Baz';
540+
$classes[] = 'ConfClass';
541+
$classes[] = 'Bar';
542+
$classes[] = 'BazClass';
543+
$classes[] = 'Foo';
544+
$classes[] = 'LazyContext';
545+
$classes[] = 'FooBarBaz';
546+
$classes[] = 'FactoryClass';
547+
$classes[] = 'Request';
548+
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
539549

540550
%A
541551

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ class FooClass_%s extends \Bar\FooClass implements \ProxyManager\Proxy\VirtualPr
165165
%A
166166
}
167167

168+
[ProjectServiceContainer.preload.php] => <?php
169+
%A
170+
171+
$classes = [];
172+
$classes[] = 'Bar\FooClass';
173+
$classes[] = 'Bar\FooLazyClass';
174+
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
175+
176+
%A
177+
168178
[ProjectServiceContainer.php] => <?php
169179

170180
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.

‎src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ class ProjectServiceContainer extends Container
8383
}
8484
}
8585

86+
[ProjectServiceContainer.preload.php] => <?php
87+
%A
88+
89+
$classes = [];
90+
$classes[] = 'Bar\FooLazyClass';
91+
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
92+
93+
%A
94+
8695
[ProjectServiceContainer.php] => <?php
8796

8897
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.

‎src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
use Symfony\Component\VarDumper\Cloner\VarCloner;
1616
use Symfony\Component\VarDumper\Dumper\CliDumper;
1717

18+
// Help opcache.preload discover always-needed symbols
19+
class_exists(CliDumper::class);
20+
1821
/**
1922
* @author Nicolas Grekas <p@tchwork.com>
2023
*/

‎src/Symfony/Component/HttpFoundation/AcceptHeader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/AcceptHeader.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace Symfony\Component\HttpFoundation;
1313

14+
// Help opcache.preload discover always-needed symbols
15+
class_exists(AcceptHeaderItem::class);
16+
1417
/**
1518
* Represents an Accept-* header.
1619
*

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.