8000 feature #32581 [DI] Allow dumping the container in one file instead o… · symfony/symfony@85058f5 · GitHub
[go: up one dir, main page]

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+
}
< 10000 code>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)
0