Skip to content

Commit c128f55

Browse files
[DependencyInjection][FrameworkBundle] Use php-serialize to dump the container for debug/lint commands
1 parent d92477a commit c128f55

15 files changed

+183
-27
lines changed

src/Symfony/Bundle/FrameworkBundle/Command/BuildDebugContainerTrait.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde
3939
return $this->container;
4040
}
4141

42-
if (!$kernel->isDebug() || !$kernel->getContainer()->getParameter('debug.container.dump') || !(new ConfigCache($kernel->getContainer()->getParameter('debug.container.dump'), true))->isFresh()) {
42+
$file = $kernel->isDebug() ? $kernel->getContainer()->getParameter('debug.container.dump') : false;
43+
44+
if (!$file || !(new ConfigCache($file, true))->isFresh()) {
4345
$buildContainer = \Closure::bind(function () {
4446
$this->initializeBundles();
4547

@@ -57,13 +59,17 @@ protected function getContainerBuilder(KernelInterface $kernel): ContainerBuilde
5759
return $containerBuilder;
5860
}, $kernel, $kernel::class);
5961
$container = $buildContainer();
60-
(new XmlFileLoader($container, new FileLocator()))->load($kernel->getContainer()->getParameter('debug.container.dump'));
61-
$locatorPass = new ServiceLocatorTagPass();
62-
$locatorPass->process($container);
6362

64-
$container->getCompilerPassConfig()->setBeforeOptimizationPasses([]);
65-
$container->getCompilerPassConfig()->setOptimizationPasses([]);
66-
$container->getCompilerPassConfig()->setBeforeRemovingPasses([]);
63+
if (str_ends_with($file, '.xml') && is_file(substr_replace($file, '.ser', -4))) {
64+
$dumpedContainer = unserialize(file_get_contents(substr_replace($file, '.ser', -4)));
65+
$container->setDefinitions($dumpedContainer->getDefinitions());
66+
$container->setAliases($dumpedContainer->getAliases());
67+
$container->__construct($dumpedContainer->getParameterBag());
68+
} else {
69+
(new XmlFileLoader($container, new FileLocator()))->load($file);
70+
$locatorPass = new ServiceLocatorTagPass();
71+
$locatorPass->process($container);
72+
}
6773
}
6874

6975
return $this->container = $container;

src/Symfony/Bundle/FrameworkBundle/Command/ContainerLintCommand.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ private function getContainerBuilder(): ContainerBuilder
7979
}
8080

8181
$kernel = $this->getApplication()->getKernel();
82-
$kernelContainer = $kernel->getContainer();
82+
$container = $kernel->getContainer();
83+
$file = $container->isDebug() ? $container->getParameter('debug.container.dump') : false;
8384

84-
if (!$kernel->isDebug() || !$kernelContainer->getParameter('debug.container.dump') || !(new ConfigCache($kernelContainer->getParameter('debug.container.dump'), true))->isFresh()) {
85+
if (!$file || !(new ConfigCache($file, true))->isFresh()) {
8586
if (!$kernel instanceof Kernel) {
8687
throw new RuntimeException(\sprintf('This command does not support the application kernel: "%s" does not extend "%s".', get_debug_type($kernel), Kernel::class));
8788
}
@@ -93,12 +94,17 @@ private function getContainerBuilder(): ContainerBuilder
9394
}, $kernel, $kernel::class);
9495
$container = $buildContainer();
9596
} else {
96-
if (!$kernelContainer instanceof Container) {
97-
throw new RuntimeException(\sprintf('This command does not support the application container: "%s" does not extend "%s".', get_debug_type($kernelContainer), Container::class));
97+
if (str_ends_with($file, '.xml') && is_file(substr_replace($file, '.ser', -4))) {
98+
$container = unserialize(file_get_contents(substr_replace($file, '.ser', -4)));
99+
} else {
100+
(new XmlFileLoader($container = new ContainerBuilder(new EnvPlaceholderParameterBag()), new FileLocator()))->load($file);
98101
}
99102

100-
(new XmlFileLoader($container = new ContainerBuilder($parameterBag = new EnvPlaceholderParameterBag()), new FileLocator()))->load($kernelContainer->getParameter('debug.container.dump'));
103+
if (!$container instanceof ContainerBuilder) {
104+
throw new RuntimeException(\sprintf('This command does not support the application container: "%s" is not a "%s".', get_debug_type($container), ContainerBuilder::class));
105+
}
101106

107+
$parameterBag = $container->getParameterBag();
102108
$refl = new \ReflectionProperty($parameterBag, 'resolved');
103109
$refl->setValue($parameterBag, true);
104110

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/ContainerBuilderDebugDumpPass.php

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313

1414
use Symfony\Component\Config\ConfigCache;
1515
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16+
use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718
use Symfony\Component\DependencyInjection\Dumper\XmlDumper;
19+
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
20+
use Symfony\Component\Filesystem\Filesystem;
1821

1922
/**
2023
* Dumps the ContainerBuilder to a cache file so that it can be used by
@@ -31,9 +34,38 @@ public function process(ContainerBuilder $container): void
3134
return;
3235
}
3336

34-
$cache = new ConfigCache($container->getParameter('debug.container.dump'), true);
35-
if (!$cache->isFresh()) {
36-
$cache->write((new XmlDumper($container))->dump(), $container->getResources());
37+
$file = $container->getParameter('debug.container.dump');
38+
$cache = new ConfigCache($file, true);
39+
if ($cache->isFresh()) {
40+
return;
41+
}
42+
$cache->write((new XmlDumper($container))->dump(), $container->getResources());
43+
44+
if (!str_ends_with($file, '.xml')) {
45+
return;
46+
}
47+
48+
$file = substr_replace($file, '.ser', -4);
49+
50+
try {
51+
$dump = new ContainerBuilder(clone $container->getParameterBag());
52+
$dump->setDefinitions(unserialize(serialize($container->getDefinitions())));
53+
$dump->setAliases($container->getAliases());
54+
55+
if (($bag = $container->getParameterBag()) instanceof EnvPlaceholderParameterBag) {
56+
(new ResolveEnvPlaceholdersPass(null))->process($dump);
57+
$dump->__construct(new EnvPlaceholderParameterBag($container->resolveEnvPlaceholders($bag->all())));
58+
}
59+
60+
$fs = new Filesystem();
61+
$fs->dumpFile($file, serialize($dump));
62+
$fs->chmod($file, 0666, umask());
63+
} catch (\Throwable $e) {
64+
$container->getCompiler()->log($this, $e->getMessage());
65+
// ignore serialization and file-system errors
66+
if (file_exists($file)) {
67+
@unlink($file);
68+
}
3769
}
3870
}
3971
}

src/Symfony/Component/DependencyInjection/Alias.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,20 @@ public function __toString(): string
103103
{
104104
return $this->id;
105105
}
106+
107+
public function __serialize(): array
108+
{
109+
$data = [];
110+
foreach ((array) $this as $k => $v) {
111+
if (!$v) {
112+
continue;
113+
}
114+
if (false !== $i = strrpos($k, "\0")) {
115+
$k = substr($k, 1 + $i);
116+
}
117+
$data[$k] = $v;
118+
}
119+
120+
return $data;
121+
}
106122
}

src/Symfony/Component/DependencyInjection/Argument/AbstractArgument.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
final class AbstractArgument
1818
{
19+
use ArgumentTrait;
20+
1921
private string $text;
2022
private string $context = '';
2123

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
/**
15+
* Helps reduce the size of the dumped container when using php-serialize.
16+
*
17+
* @internal
18+
*/
19+
trait ArgumentTrait
20+
{
21+
public function __serialize(): array
22+
{
23+
$data = [];
24+
foreach ((array) $this as $k => $v) {
25+
if (null === $v) {
26+
continue;
27+
}
28+
if (false !== $i = strrpos($k, "\0")) {
29+
$k = substr($k, 1 + $i);
30+
}
31+
$data[$k] = $v;
32+
}
33+
34+
return $data;
35+
}
36+
}

src/Symfony/Component/DependencyInjection/Argument/BoundArgument.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,29 @@
1616
*/
1717
final class BoundArgument implements ArgumentInterface
1818
{
19+
use ArgumentTrait;
20+
1921
public const SERVICE_BINDING = 0;
2022
public const DEFAULTS_BINDING = 1;
2123
public const INSTANCEOF_BINDING = 2;
2224

2325
private static int $sequence = 0;
2426

27+
private mixed $value = null;
2528
private ?int $identifier = null;
2629
private ?bool $used = null;
30+
private int $type = 0;
31+
private ?string $file = null;
2732

2833
public function __construct(
29-
private mixed $value,
34+
mixed $value,
3035
bool $trackUsage = true,
31-
private int $type = 0,
32-
private ?string $file = null,
36+
int $type = 0,
37+
?string $file = null,
3338
) {
39+
$this->value = $value;
40+
$this->type = $type;
41+
$this->file = $file;
3442
if ($trackUsage) {
3543
$this->identifier = ++self::$sequence;
3644
} else {

src/Symfony/Component/DependencyInjection/Argument/IteratorArgument.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
class IteratorArgument implements ArgumentInterface
2020
{
21+
use ArgumentTrait;
22+
2123
private array $values;
2224

2325
public function __construct(array $values)

src/Symfony/Component/DependencyInjection/Argument/ServiceClosureArgument.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
*/
2121
class ServiceClosureArgument implements ArgumentInterface
2222
{
23+
use ArgumentTrait;
24+
2325
private array $values;
2426

2527
public function __construct(mixed $value)

src/Symfony/Component/DependencyInjection/Argument/ServiceLocatorArgument.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@
1111

1212
namespace Symfony\Component\DependencyInjection\Argument;
1313

14+
use Symfony\Component\DependencyInjection\Loader\Configurator\Traits\ArgumentTrait;
15+
1416
/**
1517
* Represents a closure acting as a service locator.
1618
*
1719
* @author Nicolas Grekas <p@tchwork.com>
1820
*/
1921
class ServiceLocatorArgument implements ArgumentInterface
2022
{
23+
use ArgumentTrait;
24+
2125
private array $values;
2226
private ?TaggedIteratorArgument $taggedIteratorArgument = null;
2327

src/Symfony/Component/DependencyInjection/Argument/TaggedIteratorArgument.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
*/
1919
class TaggedIteratorArgument extends IteratorArgument
2020
{
21-
private mixed $indexAttribute;
22-
private ?string $defaultIndexMethod;
23-
private ?string $defaultPriorityMethod;
21+
private mixed $indexAttribute = null;
22+
private ?string $defaultIndexMethod = null;
23+
private ?string $defaultPriorityMethod = null;
2424

2525
/**
2626
* @param string $tag The name of the tag identifying the target services

src/Symfony/Component/DependencyInjection/Compiler/ResolveEnvPlaceholdersPass.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,35 @@ class ResolveEnvPlaceholdersPass extends AbstractRecursivePass
2020
{
2121
protected bool $skipScalars = false;
2222

23+
/**
24+
* @param string|true|null $format A sprintf() format returning the replacement for each env var name or
25+
* null to resolve back to the original "%env(VAR)%" format or
26+
* true to resolve to the actual values of the referenced env vars
27+
*/
28+
public function __construct(
29+
private string|bool|null $format = true,
30+
) {
31+
}
32+
2333
protected function processValue(mixed $value, bool $isRoot = false): mixed
2434
{
2535
if (\is_string($value)) {
26-
return $this->container->resolveEnvPlaceholders($value, true);
36+
return $this->container->resolveEnvPlaceholders($value, $this->format);
2737
}
2838
if ($value instanceof Definition) {
2939
$changes = $value->getChanges();
3040
if (isset($changes['class'])) {
31-
$value->setClass($this->container->resolveEnvPlaceholders($value->getClass(), true));
41+
$value->setClass($this->container->resolveEnvPlaceholders($value->getClass(), $this->format));
3242
}
3343
if (isset($changes['file'])) {
34-
$value->setFile($this->container->resolveEnvPlaceholders($value->getFile(), true));
44+
$value->setFile($this->container->resolveEnvPlaceholders($value->getFile(), $this->format));
3545
}
3646
}
3747

3848
$value = parent::processValue($value, $isRoot);
3949

4050
if ($value && \is_array($value) && !$isRoot) {
41-
$value = array_combine($this->container->resolveEnvPlaceholders(array_keys($value), true), $value);
51+
$value = array_combine($this->container->resolveEnvPlaceholders(array_keys($value), $this->format), $value);
4252
}
4353

4454
return $value;

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,4 +820,20 @@ public function hasErrors(): bool
820820
{
821821
return (bool) $this->errors;
822822
}
823+
824+
public function __serialize(): array
825+
{
826+
$data = [];
827+
foreach ((array) $this as $k => $v) {
828+
if (false !== $i = strrpos($k, "\0")) {
829+
$k = substr($k, 1 + $i);
830+
}
831+
if (!$v xor 'shared' === $k) {
832+
continue;
833+
}
834+
$data[$k] = $v;
835+
}
836+
837+
return $data;
838+
}
823839
}

src/Symfony/Component/DependencyInjection/Reference.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,22 @@ public function __toString(): string
3434
*/
3535
public function getInvalidBehavior(): int
3636
{
37-
return $this->invalidBehavior;
37+
return $this->invalidBehavior ??= ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
38+
}
39+
40+
public function __serialize(): array
41+
{
42+
$data = [];
43+
foreach ((array) $this as $k => $v) {
44+
if (false !== $i = strrpos($k, "\0")) {
45+
$k = substr($k, 1 + $i);
46+
}
47+
if ('invalidBehavior' === $k && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $v) {
48+
continue;
49+
}
50+
$data[$k] = $v;
51+
}
52+
53+
return $data;
3854
}
3955
}

src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ public static function getSubscribedServices(): array
452452
'autowired' => new ServiceClosureArgument(new TypedReference('service.id', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'autowired', [new Autowire(service: 'service.id')])),
453453
'autowired.nullable' => new ServiceClosureArgument(new TypedReference('service.id', 'stdClass', ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'autowired.nullable', [new Autowire(service: 'service.id')])),
454454
'autowired.parameter' => new ServiceClosureArgument('foobar'),
455-
'autowire.decorated' => new ServiceClosureArgument(new Reference('.service_locator.oNVewcO.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
455+
'autowire.decorated' => new ServiceClosureArgument(new Reference('.service_locator.Di.wrC8.inner', ContainerInterface::NULL_ON_INVALID_REFERENCE)),
456456
'target' => new ServiceClosureArgument(new TypedReference('stdClass', 'stdClass', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'target', [new Target('someTarget')])),
457457
];
458458
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));

0 commit comments

Comments
 (0)