Skip to content

Commit c893986

Browse files
[DI] Allow dumping the container in one file instead of many files
1 parent 52e9fb9 commit c893986

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

@@ -215,13 +218,17 @@ public function dump(array $options = [])
215218
}
216219
}
217220

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

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

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

@@ -303,7 +321,7 @@ public function dump(array $options = [])
303321
EOF;
304322
} else {
305323
$code .= $this->endClass();
306-
foreach ($this->generateProxyClasses() as $c) {
324+
foreach ($proxyClasses as $c) {
307325
$code .= $c;
308326
}
309327
}
@@ -422,8 +440,9 @@ private function collectLineage($class, array &$lineage)
422440
$lineage[$class] = substr($exportedFile, 1, -1);
423441
}
424442

425-
private function generateProxyClasses()
443+
private function generateProxyClasses(): array
426444
{
445+
$proxyClasses = [];
427446
$alreadyGenerated = [];
428447
$definitions = $this->container->getDefinitions();
429448
$strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments');
@@ -442,19 +461,39 @@ private function generateProxyClasses()
442461
if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition)) {
443462
continue;
444463
}
464+
465+
if ($this->inlineRequires) {
466+
$lineage = [];
467+
$this->collectLineage($class, $lineage);
468+
469+
$code = '';
470+
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
471+
if ($this->inlineFactories) {
472+
$this->inlinedRequires[$file] = true;
473+
}
474+
$file = preg_replace('#^\\$this->targetDirs\[(\d++)\]#', sprintf('\dirname(__DIR__, %d + $1)', $this->asFiles), $file);
475+
$code .= sprintf("include_once %s;\n", $file);
476+
}
477+
478+
$proxyCode = $code.$proxyCode;
479+
}
480+
445481
if ($strip) {
446482
$proxyCode = "<?php\n".$proxyCode;
447483
$proxyCode = substr(Kernel::stripComments($proxyCode), 5);
448484
}
449-
yield sprintf('%s.php', explode(' ', $proxyCode, 3)[1]) => $proxyCode;
485+
486+
$proxyClasses[sprintf('%s.php', explode(' ', $proxyCode, 3)[1])] = $proxyCode;
450487
}
488+
489+
return $proxyClasses;
451490
}
452491

453492
private function addServiceInclude(string $cId, Definition $definition): string
454493
{
455494
$code = '';
456495

457-
if ($this->inlineRequires && !$this->isHotPath($definition)) {
496+
if ($this->inlineRequires && (!$this->isHotPath($definition) || $this->getProxyDumper()->isProxyCandidate($definition))) {
458497
$lineage = [];
459498
foreach ($this->inlinedDefinitions as $def) {
460499
if (!$def->isDeprecated() && \is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) {
@@ -685,7 +724,7 @@ private function addService(string $id, Definition $definition): array
685724
$lazyInitialization = '';
686725
}
687726

688-
$asFile = $this->asFiles && !$this->isHotPath($definition);
727+
$asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition);
689728
$methodName = $this->generateMethodName($id);
690729
if ($asFile) {
691730
$file = $methodName.'.php';
@@ -711,17 +750,16 @@ protected function {$methodName}($lazyInitialization)
711750
$this->serviceCalls = [];
712751
$this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls);
713752

714-
$code .= $this->addServiceInclude($id, $definition);
753+
if ($definition->isDeprecated()) {
754+
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
755+
}
715756

716757
if ($this->getProxyDumper()->isProxyCandidate($definition)) {
717758
$factoryCode = $asFile ? ($definition->isShared() ? "\$this->load('%s.php', false)" : '$this->factories[%2$s](false)') : '$this->%s(false)';
718759
$code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, sprintf($factoryCode, $methodName, $this->doExport($id)));
719760
}
720761

721-
if ($definition->isDeprecated()) {
722-
$code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
723-
}
724-
762+
$code .= $this->addServiceInclude($id, $definition);
725763
$code .= $this->addInlineService($id, $definition);
726764

727765
if ($asFile) {
@@ -1024,7 +1062,7 @@ public function __construct()
10241062

10251063
$code .= $this->addSyntheticIds();
10261064
$code .= $this->addMethodMap();
1027-
$code .= $this->asFiles ? $this->addFileMap() : '';
1065+
$code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : '';
10281066
$code .= $this->addAliases();
10291067
$code .= $this->addInlineRequires();
10301068
$code .= <<<EOF
@@ -1043,7 +1081,7 @@ public function isCompiled()
10431081
EOF;
10441082
$code .= $this->addRemovedIds();
10451083

1046-
if ($this->asFiles) {
1084+
if ($this->asFiles && !$this->inlineFactories) {
10471085
$code .= <<<EOF
10481086
10491087
protected function load(\$file, \$lazyLoad = true)
@@ -1059,10 +1097,10 @@ protected function load(\$file, \$lazyLoad = true)
10591097
if (!$proxyDumper->isProxyCandidate($definition)) {
10601098
continue;
10611099
}
1062-
if ($this->asFiles) {
1100+
if ($this->asFiles && !$this->inlineFactories) {
10631101
$proxyLoader = '$this->load("{$class}.php")';
1064-
} elseif ($this->namespace) {
1065-
$proxyLoader = 'class_alias("'.$this->namespace.'\\\\{$class}", $class, false)';
1102+
} elseif ($this->namespace || $this->inlineFactories) {
1103+
$proxyLoader = 'class_alias(__NAMESPACE__."\\\\$class", $class, false)';
10661104
} else {
10671105
$proxyLoader = '';
10681106
}
@@ -1140,7 +1178,7 @@ private function addMethodMap(): string
11401178
$definitions = $this->container->getDefinitions();
11411179
ksort($definitions);
11421180
foreach ($definitions as $id => $definition) {
1143-
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->isHotPath($definition))) {
1181+
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) {
11441182
$code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n";
11451183
}
11461184
}
@@ -1237,6 +1275,11 @@ private function addInlineRequires(): string
12371275

12381276
foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) {
12391277
$definition = $this->container->getDefinition($id);
1278+
1279+
if ($this->getProxyDumper()->isProxyCandidate($definition)) {
1280+
continue;
1281+
}
1282+
12401283
$inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]);
12411284

12421285
foreach ($inlinedDefinitions as $def) {
@@ -1578,7 +1621,7 @@ private function dumpValue($value, bool $interpolate = true): string
15781621
continue;
15791622
}
15801623
$definition = $this->container->findDefinition($id = (string) $v);
1581-
$load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->isHotPath($definition) : reset($e);
1624+
$load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e);
15821625
$serviceMap .= sprintf("\n %s => [%s, %s, %s, %s],",
15831626
$this->export($k),
15841627
$this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false),
@@ -1716,7 +1759,7 @@ private function getServiceCall(string $id, Reference $reference = null): string
17161759
$code = sprintf('$this->%s[%s] = %s', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code);
17171760
}
17181761
$code = "($code)";
1719-
} elseif ($this->asFiles && !$this->isHotPath($definition)) {
1762+
} elseif ($this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition)) {
17201763
$code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id));
17211764
if (!$definition->isShared()) {
17221765
$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)