Skip to content

Commit 85058f5

Browse files
feature #32581 [DI] Allow dumping the container in one file instead of many files (nicolas-grekas)
This PR was merged into the 4.4 branch. Discussion ---------- [DI] Allow dumping the container in one file instead of many files | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Replaces #32119 As spotted by @lyrixx, putting all service factories in one big container can be easier to manage with workers. It could also play well with PHP7.4's preloading. This PR adds a `container.dumper.inline_factories` parameter to enable this behavior. When it is set to true, a single big container file is created. Commits ------- c893986 [DI] Allow dumping the container in one file instead of many files
2 parents 537114d + c893986 commit 85058f5

File tree

5 files changed

+901
-23
lines changed

5 files changed

+901
-23
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* added support for dumping the container in one file instead of many files
78
* deprecated support for short factories and short configurators in Yaml
89
* deprecated `tagged` in favor of `tagged_iterator`
910
* deprecated passing an instance of `Symfony\Component\DependencyInjection\Parameter` as class name to `Symfony\Component\DependencyInjection\Definition`

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

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class PhpDumper extends Dumper
7272
private $namespace;
7373
private $asFiles;
7474
private $hotPathTag;
75+
private $inlineFactories;
7576
private $inlineRequires;
7677
private $inlinedRequires = [];
7778
private $circularReferences = [];
@@ -134,6 +135,7 @@ public function dump(array $options = [])
134135
'as_files' => false,
135136
'debug' => true,
136137
'hot_path_tag' => 'container.hot_path',
138+
'inline_factories_parameter' => 'container.dumper.inline_factories',
137139
'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
138140
'service_locator_tag' => 'container.service_locator',
139141
'build_time' => time(),
@@ -143,6 +145,7 @@ public function dump(array $options = [])
143145
$this->namespace = $options['namespace'];
144146
$this->asFiles = $options['as_files'];
145147
$this->hotPathTag = $options['hot_path_tag'];
148+
$this->inlineFactories = $this->asFiles && $options['inline_factories_parameter'] && $this->container->hasParameter($options['inline_factories_parameter']) && $this->container->getParameter($options['inline_factories_parameter']);
146149
$this->inlineRequires = $options['inline_class_loader_parameter'] && $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']);
147150
$this->serviceLocatorTag = $options['service_locator_tag'];
148151

@@ -216,13 +219,17 @@ public function dump(array $options = [])
216219
}
217220
}
218221

222+
$proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null;
223+
219224
$code =
220225
$this->startClass($options['class'], $baseClass, $baseClassWithNamespace).
221226
$this->addServices($services).
222227
$this->addDeprecatedAliases().
223228
$this->addDefaultParametersMethod()
224229
;
225230

231+
$proxyClasses = $proxyClasses ?? $this->generateProxyClasses();
232+
226233
if ($this->addGetService) {
227234
$code = preg_replace(
228235
"/(\r?\n\r?\n public function __construct.+?\\{\r?\n)/s",
@@ -259,13 +266,24 @@ public function dump(array $options = [])
259266
$files['removed-ids.php'] = $c .= "];\n";
260267
}
261268

262-
foreach ($this->generateServiceFiles($services) as $file => $c) {
263-
$files[$file] = $fileStart.$c;
269+
if (!$this->inlineFactories) {
270+
foreach ($this->generateServiceFiles($services) as $file => $c) {
271+
$files[$file] = $fileStart.$c;
272+
}
273+
foreach ($proxyClasses as $file => $c) {
274+
$files[$file] = "<?php\n".$c;
275+
}
264276
}
265-
foreach ($this->generateProxyClasses() as $file => $c) {
266-
$files[$file] = "<?php\n".$c;
277+
278+
$code .= $this->endClass();
279+
280+
if ($this->inlineFactories) {
281+
foreach ($proxyClasses as $c) {
282+
$code .= $c;
283+
}
267284
}
268-
$files[$options['class'].'.php'] = $code.$this->endClass();
285+
286+
$files[$options['class'].'.php'] = $code;
269287
$hash = ucfirst(strtr(ContainerBuilder::hash($files), '._', 'xx'));
270288
$code = [];
271289

@@ -304,7 +322,7 @@ public function dump(array $options = [])
304322
EOF;
305323
} else {
306324
$code .= $this->endClass();
307-
foreach ($this->generateProxyClasses() as $c) {
325+
foreach ($proxyClasses as $c) {
308326
$code .= $c;
309327
}
310328
}
@@ -431,8 +449,9 @@ private function collectLineage($class, array &$lineage)
431449
$lineage[$class] = substr($exportedFile, 1, -1);
432450
}
433451

434-
private function generateProxyClasses()
452+
private function generateProxyClasses(): array
435453
{
454+
$proxyClasses = [];
436455
$alreadyGenerated = [];
437456
$definitions = $this->container->getDefinitions();
438457
$strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments');
@@ -451,19 +470,39 @@ private function generateProxyClasses()
451470
if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition)) {
452471
continue;
453472
}
473+
474+
if ($this->inlineRequires) {
475+
$lineage = [];
476+
$this->collectLineage($class, $lineage);
477+
478+
$code = '';
479+
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
480+
if ($this->inlineFactories) {
481+
$this->inlinedRequires[$file] = true;
482+
}
483+
$file = preg_replace('#^\\$this->targetDirs\[(\d++)\]#', sprintf('\dirname(__DIR__, %d + $1)', $this->asFiles), $file);
484+
$code .= sprintf("include_once %s;\n", $file);
485+
}
486+
487+
$proxyCode = $code.$proxyCode;
488+
}
489+
454490
if ($strip) {
455491
$proxyCode = "<?php\n".$proxyCode;
456492
$proxyCode = substr(Kernel::stripComments($proxyCode), 5);
457493
}
458-
yield sprintf('%s.php', explode(' ', $proxyCode, 3)[1]) => $proxyCode;
494+
495+
$proxyClasses[sprintf('%s.php', explode(' ', $proxyCode, 3)[1])] = $proxyCode;
459496
}
497+
498+
return $proxyClasses;
460499
}
461500

462501
private function addServiceInclude(string $cId, Definition $definition): string
463502
{
464503
$code = '';
465504

466-
if ($this->inlineRequires && !$this->isHotPath($definition)) {
505+
if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) {
467506
$lineage = [];
468507
foreach ($this->inlinedDefinitions as $def) {
469508
if (!$def->isDeprecated() && \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) {
@@ -693,7 +732,7 @@ private function addService(string $id, Definition $definition): array
693732
$lazyInitialization = '';
694733
}
695734

696-
$asFile = $this->asFiles && !$this->isHotPath($definition);
735+
$asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition);
697736
$methodName = $this->generateMethodName($id);
698737
if ($asFile) {
699738
$file = $methodName.'.php';
@@ -719,17 +758,16 @@ protected function {$methodName}($lazyInitialization)
719758
$this->serviceCalls = [];
720759
$this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls);
721760

722-
$code .= $this->addServiceInclude($id, $definition);
761+
if ($definition->isDeprecated()) {
762+
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
763+
}
723764

724765
if ($this->getProxyDumper()->isProxyCandidate($definition)) {
725766
$factoryCode = $asFile ? ($definition->isShared() ? "\$this->load('%s.php', false)" : '$this->factories[%2$s](false)') : '$this->%s(false)';
726767
$code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, sprintf($factoryCode, $methodName, $this->doExport($id)));
727768
}
728769

729-
if ($definition->isDeprecated()) {
730-
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
731-
}
732-
770+
$code .= $this->addServiceInclude($id, $definition);
733771
$code .= $this->addInlineService($id, $definition);
734772

735773
if ($asFile) {
@@ -1044,7 +1082,7 @@ public function __construct()
10441082

10451083
$code .= $this->addSyntheticIds();
10461084
$code .= $this->addMethodMap();
1047-
$code .= $this->asFiles ? $this->addFileMap() : '';
1085+
$code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : '';
10481086
$code .= $this->addAliases();
10491087
$code .= $this->addInlineRequires();
10501088
$code .= <<<EOF
@@ -1063,7 +1101,7 @@ public function isCompiled()
10631101
EOF;
10641102
$code .= $this->addRemovedIds();
10651103

1066-
if ($this->asFiles) {
1104+
if ($this->asFiles && !$this->inlineFactories) {
10671105
$code .= <<<EOF
10681106
10691107
protected function load(\$file, \$lazyLoad = true)
@@ -1079,10 +1117,10 @@ protected function load(\$file, \$lazyLoad = true)
10791117
if (!$proxyDumper->isProxyCandidate($definition)) {
10801118
continue;
10811119
}
1082-
if ($this->asFiles) {
1120+
if ($this->asFiles && !$this->inlineFactories) {
10831121
$proxyLoader = '$this->load("{$class}.php")';
1084-
} elseif ($this->namespace) {
1085-
$proxyLoader = 'class_alias("'.$this->namespace.'\\\\{$class}", $class, false)';
1122+
} elseif ($this->namespace || $this->inlineFactories) {
1123+
$proxyLoader = 'class_alias(__NAMESPACE__."\\\\$class", $class, false)';
10861124
} else {
10871125
$proxyLoader = '';
10881126
}
@@ -1160,7 +1198,7 @@ private function addMethodMap(): string
11601198
$definitions = $this->container->getDefinitions();
11611199
ksort($definitions);
11621200
foreach ($definitions as $id => $definition) {
1163-
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->isHotPath($definition))) {
1201+
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) {
11641202
$code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n";
11651203
}
11661204
}
@@ -1257,6 +1295,11 @@ private function addInlineRequires(): string
12571295

12581296
foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) {
12591297
$definition = $this->container->getDefinition($id);
1298+
1299+
if ($this->getProxyDumper()->isProxyCandidate($definition)) {
1300+
continue;
1301+
}
1302+
12601303
$inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]);
12611304

12621305
foreach ($inlinedDefinitions as $def) {
@@ -1604,7 +1647,7 @@ private function dumpValue($value, bool $interpolate = true): string
16041647
continue;
16051648
}
16061649
$definition = $this->container->findDefinition($id = (string) $v);
1607-
$load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->isHotPath($definition) : reset($e);
1650+
$load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e);
16081651
$serviceMap .= sprintf("\n %s => [%s, %s, %s, %s],",
16091652
$this->export($k),
16101653
$this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false),
@@ -1747,7 +1790,7 @@ private function getServiceCall(string $id, Reference $reference = null): string
17471790
$code = sprintf('$this->%s[%s] = %s', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code);
17481791
}
17491792
$code = "($code)";
1750-
} elseif ($this->asFiles && !$this->isHotPath($definition)) {
1793+
} elseif ($this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition)) {
17511794
$code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id));
17521795
if (!$definition->isShared()) {
17531796
$factory = sprintf('$this->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id));

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Psr\Container\ContainerInterface;
16+
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
1617
use Symfony\Component\Config\FileLocator;
1718
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1819
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
@@ -42,6 +43,8 @@
4243

4344
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
4445
require_once __DIR__.'/../Fixtures/includes/classes.php';
46+
require_once __DIR__.'/../Fixtures/includes/foo.php';
47+
require_once __DIR__.'/../Fixtures/includes/foo_lazy.php';
4548

4649
class PhpDumperTest extends TestCase
4750
{
@@ -234,6 +237,59 @@ public function testDumpAsFiles()
234237
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_as_files.txt', $dump);
235238
}
236239

240+
public function testDumpAsFilesWithFactoriesInlined()
241+
{
242+
$container = include self::$fixturesPath.'/containers/container9.php';
243+
$container->setParameter('container.dumper.inline_factories', true);
244+
$container->setParameter('container.dumper.inline_class_loader', true);
245+
246+
$container->getDefinition('bar')->addTag('hot');
247+
$container->register('non_shared_foo', \Bar\FooClass::class)
248+
->setFile(realpath(self::$fixturesPath.'/includes/foo.php'))
249+
->setShared(false)
250+
->setPublic(true);
251+
$container->register('throwing_one', \Bar\FooClass::class)
252+
->addArgument(new Reference('errored_one', ContainerBuilder::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE))
253+
->setPublic(true);
254+
$container->register('errored_one', 'stdClass')
255+
->addError('No-no-no-no');
256+
$container->compile();
257+
258+
$dumper = new PhpDumper($container);
259+
$dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'build_time' => 1563381341]), true);
260+
261+
if ('\\' === \DIRECTORY_SEPARATOR) {
262+
$dump = str_replace('\\\\Fixtures\\\\includes\\\\', '/Fixtures/includes/', $dump);
263+
}
264+
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_inlined_factories.txt', $dump);
265+
}
266+
267+
/**
268+
* @requires function \Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper::getProxyCode
269+
*/
270+
public function testDumpAsFilesWithLazyFactoriesInlined()
271+
{
272+
$container = new ContainerBuilder();
273+
$container->setParameter('container.dumper.inline_factories', true);
274+
$container->setParameter('container.dumper.inline_class_loader', true);
275+
276+
$container->register('lazy_foo', \Bar\FooClass::class)
277+
->addArgument(new Definition(\Bar\FooLazyClass::class))
278+
->setPublic(true)
279+
->setLazy(true);
280+
281+
$container->compile();
282+
283+
$dumper = new PhpDumper($container);
284+
$dumper->setProxyDumper(new ProxyDumper());
285+
$dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'build_time' => 1563381341]), true);
286+
287+
if ('\\' === \DIRECTORY_SEPARATOR) {
288+
$dump = str_replace('\\\\Fixtures\\\\includes\\\\', '/Fixtures/includes/', $dump);
289+
}
290+
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_lazy_inlined_factories.txt', $dump);
291+
}
292+
237293
public function testNonSharedLazyDumpAsFiles()
238294
{
239295
$container = include self::$fixturesPath.'/containers/container_non_shared_lazy.php';

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy