Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8e2b365

Browse files
committed
prepend extension configs with file loaders
1 parent 6234adb commit 8e2b365

17 files changed

+201
-38
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ CHANGELOG
99
* Have `ServiceLocator` implement `ServiceCollectionInterface`
1010
* Add `#[Lazy]` attribute as shortcut for `#[Autowire(lazy: [bool|string])]` and `#[Autoconfigure(lazy: [bool|string])]`
1111
* Add `#[AutowireMethodOf]` attribute to autowire a method of a service as a callable
12+
* Add argument `$prepend` to `FileLoader::construct()` to prepend loaded configuration instead of appending it
13+
* [BC BREAK] When used in the `prependExtension()` methods, the `ContainerConfigurator::import()` method now prepends the configuration instead of appending it
1214

1315
7.0
1416
---

src/Symfony/Component/DependencyInjection/Extension/AbstractExtension.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ final public function prepend(ContainerBuilder $container): void
4949
$this->prependExtension($configurator, $container);
5050
};
5151

52-
$this->executeConfiguratorCallback($container, $callback, $this);
52+
$this->executeConfiguratorCallback($container, $callback, $this, true);
5353
}
5454

5555
final public function load(array $configs, ContainerBuilder $container): void

src/Symfony/Component/DependencyInjection/Extension/ExtensionTrait.php

+6-6
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
*/
3131
trait ExtensionTrait
3232
{
33-
private function executeConfiguratorCallback(ContainerBuilder $container, \Closure $callback, ConfigurableExtensionInterface $subject): void
33+
private function executeConfiguratorCallback(ContainerBuilder $container, \Closure $callback, ConfigurableExtensionInterface $subject, bool $prepend = false): void
3434
{
3535
$env = $container->getParameter('kernel.environment');
36-
$loader = $this->createContainerLoader($container, $env);
36+
$loader = $this->createContainerLoader($container, $env, $prepend);
3737
$file = (new \ReflectionObject($subject))->getFileName();
3838
$bundleLoader = $loader->getResolver()->resolve($file);
3939
if (!$bundleLoader instanceof PhpFileLoader) {
@@ -50,15 +50,15 @@ private function executeConfiguratorCallback(ContainerBuilder $container, \Closu
5050
}
5151
}
5252

53-
private function createContainerLoader(ContainerBuilder $container, string $env): DelegatingLoader
53+
private function createContainerLoader(ContainerBuilder $container, string $env, bool $prepend): DelegatingLoader
5454
{
5555
$buildDir = $container->getParameter('kernel.build_dir');
5656
$locator = new FileLocator();
5757
$resolver = new LoaderResolver([
58-
new XmlFileLoader($container, $locator, $env),
59-
new YamlFileLoader($container, $locator, $env),
58+
new XmlFileLoader($container, $locator, $env, $prepend),
59+
new YamlFileLoader($container, $locator, $env, $prepend),
6060
new IniFileLoader($container, $locator, $env),
61-
new PhpFileLoader($container, $locator, $env, new ConfigBuilderGenerator($buildDir)),
61+
new PhpFileLoader($container, $locator, $env, new ConfigBuilderGenerator($buildDir), $prepend),
6262
new GlobFileLoader($container, $locator, $env),
6363
new DirectoryLoader($container, $locator, $env),
6464
new ClosureLoader($container, $env),

src/Symfony/Component/DependencyInjection/Loader/FileLoader.php

+46-1
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,17 @@ abstract class FileLoader extends BaseFileLoader
4545
/** @var array<string, Alias> */
4646
protected array $aliases = [];
4747
protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = true;
48+
protected bool $prepend = false;
49+
protected array $extensionConfigs = [];
50+
protected int $importing = 0;
4851

49-
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null)
52+
/**
53+
* @param bool $prepend Whether to prepend extension config instead of appending them
54+
*/
55+
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null, bool $prepend = false)
5056
{
5157
$this->container = $container;
58+
$this->prepend = $prepend;
5259

5360
parent::__construct($locator, $env);
5461
}
@@ -66,6 +73,7 @@ public function import(mixed $resource, ?string $type = null, bool|string $ignor
6673
throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors)));
6774
}
6875

76+
++$this->importing;
6977
try {
7078
return parent::import(...$args);
7179
} catch (LoaderLoadException $e) {
@@ -82,6 +90,8 @@ public function import(mixed $resource, ?string $type = null, bool|string $ignor
8290
if (__FILE__ !== $frame['file']) {
8391
throw $e;
8492
}
93+
} finally {
94+
--$this->importing;
8595
}
8696

8797
return null;
@@ -217,6 +227,41 @@ public function registerAliasesForSinglyImplementedInterfaces(): void
217227
$this->interfaces = $this->singlyImplemented = $this->aliases = [];
218228
}
219229

230+
final protected function loadExtensionConfig(string $namespace, array $config): void
231+
{
232+
if (!$this->prepend) {
233+
$this->container->loadFromExtension($namespace, $config);
234+
235+
return;
236+
}
237+
238+
if ($this->importing) {
239+
if (!isset($this->extensionConfigs[$namespace])) {
240+
$this->extensionConfigs[$namespace] = [];
241+
}
242+
array_unshift($this->extensionConfigs[$namespace], $config);
243+
244+
return;
245+
}
246+
247+
$this->container->prependExtensionConfig($namespace, $config);
248+
}
249+
250+
final protected function loadExtensionConfigs(): void
251+
{
252+
if ($this->importing || [] === $this->extensionConfigs) {
253+
return;
254+
}
255+
256+
foreach ($this->extensionConfigs as $namespace => $configs) {
257+
foreach ($configs as $config) {
258+
$this->container->prependExtensionConfig($namespace, $config);
259+
}
260+
}
261+
262+
$this->extensionConfigs = [];
263+
}
264+
220265
/**
221266
* Registers a definition in the container with its instanceof-conditionals.
222267
*/

src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php

+13-4
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ class PhpFileLoader extends FileLoader
3636
protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false;
3737
private ?ConfigBuilderGeneratorInterface $generator;
3838

39-
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null, ?ConfigBuilderGeneratorInterface $generator = null)
39+
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, ?string $env = null, ?ConfigBuilderGeneratorInterface $generator = null, bool $prepend = false)
4040
{
41-
parent::__construct($container, $locator, $env);
41+
parent::__construct($container, $locator, $env, $prepend);
4242
$this->generator = $generator;
4343
}
4444

@@ -145,10 +145,19 @@ class_exists(ContainerConfigurator::class);
145145

146146
$callback(...$arguments);
147147

148-
/** @var ConfigBuilderInterface $configBuilder */
148+
$this->loadFromExtensions($configBuilders);
149+
}
150+
151+
/**
152+
* @param iterable<ConfigBuilderInterface> $configBuilders
153+
*/
154+
private function loadFromExtensions(iterable $configBuilders): void
155+
{
149156
foreach ($configBuilders as $configBuilder) {
150-
$containerConfigurator->extension($configBuilder->getExtensionAlias(), $configBuilder->toArray());
157+
$this->loadExtensionConfig($configBuilder->getExtensionAlias(), $configBuilder->toArray());
151158
}
159+
160+
$this->loadExtensionConfigs();
152161
}
153162

154163
/**

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ private function validateExtensions(\DOMDocument $dom, string $file): void
808808
}
809809

810810
// can it be handled by an extension?
811-
if (!$this->container->hasExtension($node->namespaceURI)) {
811+
if (!$this->prepend && !$this->container->hasExtension($node->namespaceURI)) {
812812
$extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getNamespace(), $this->container->getExtensions()));
813813
throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? implode('", "', $extensionNamespaces) : 'none'));
814814
}
@@ -830,8 +830,10 @@ private function loadFromExtensions(\DOMDocument $xml): void
830830
$values = [];
831831
}
832832

833-
$this->container->loadFromExtension($node->namespaceURI, $values);
833+
$this->loadExtensionConfig($node->namespaceURI, $values);
834834
}
835+
836+
$this->loadExtensionConfigs();
835837
}
836838

837839
/**

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

+23-15
Original file line numberDiff line numberDiff line change
@@ -129,22 +129,28 @@ public function load(mixed $resource, ?string $type = null): mixed
129129
return null;
130130
}
131131

132-
$this->loadContent($content, $path);
132+
++$this->importing;
133+
try {
134+
$this->loadContent($content, $path);
133135

134-
// per-env configuration
135-
if ($this->env && isset($content['when@'.$this->env])) {
136-
if (!\is_array($content['when@'.$this->env])) {
137-
throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path));
138-
}
136+
// per-env configuration
137+
if ($this->env && isset($content['when@'.$this->env])) {
138+
if (!\is_array($content['when@'.$this->env])) {
139+
throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path));
140+
}
139141

140-
$env = $this->env;
141-
$this->env = null;
142-
try {
143-
$this->loadContent($content['when@'.$env], $path);
144-
} finally {
145-
$this->env = $env;
142+
$env = $this->env;
143+
$this->env = null;
144+
try {
145+
$this->loadContent($content['when@'.$env], $path);
146+
} finally {
147+
$this->env = $env;
148+
}
146149
}
150+
} finally {
151+
--$this->importing;
147152
}
153+
$this->loadExtensionConfigs();
148154

149155
return null;
150156
}
@@ -802,7 +808,7 @@ private function validate(mixed $content, string $file): ?array
802808
continue;
803809
}
804810

805-
if (!$this->container->hasExtension($namespace)) {
811+
if (!$this->prepend && !$this->container->hasExtension($namespace)) {
806812
$extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions()));
807813
throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in "%s"). Looked for namespace "%s", found "%s".', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'));
808814
}
@@ -941,12 +947,14 @@ private function loadFromExtensions(array $content): void
941947
continue;
942948
}
943949

944-
if (!\is_array($values) && null !== $values) {
950+
if (!\is_array($values)) {
945951
$values = [];
946952
}
947953

948-
$this->container->loadFromExtension($namespace, $values);
954+
$this->loadExtensionConfig($namespace, $values);
949955
}
956+
957+
$this->loadExtensionConfigs();
950958
}
951959

952960
private function checkDefinition(string $id, array $definition, string $file): void

src/Symfony/Component/DependencyInjection/Tests/Extension/AbstractExtensionTest.php

+34-7
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,55 @@ public function configure(DefinitionConfigurator $definition): void
5454
self::assertSame($expected, $this->processConfiguration($extension));
5555
}
5656

57-
public function testPrependAppendExtensionConfig()
57+
public function testPrependExtensionConfig()
5858
{
5959
$extension = new class() extends AbstractExtension {
60+
public function configure(DefinitionConfigurator $definition): void
61+
{
62+
$definition->rootNode()
63+
->children()
64+
->scalarNode('foo')->end()
65+
->end();
66+
}
67+
6068
public function prependExtension(ContainerConfigurator $container, ContainerBuilder $builder): void
6169
{
62-
// append config
63-
$container->extension('third', ['foo' => 'append']);
70+
// prepend config from plain array
71+
$container->extension('third', ['foo' => 'pong'], true);
72+
73+
// prepend config from external file
74+
$container->import('../Fixtures/config/packages/ping.yaml');
75+
}
76+
77+
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
78+
{
79+
$container->parameters()->set('foo_param', $config['foo']);
80+
}
6481

65-
// prepend config
66-
$container->extension('third', ['foo' => 'prepend'], true);
82+
public function getAlias(): string
83+
{
84+
return 'third';
6785
}
6886
};
6987

7088
$container = $this->processPrependExtension($extension);
7189

7290
$expected = [
73-
['foo' => 'prepend'],
91+
['foo' => 'a'],
92+
['foo' => 'c1'],
93+
['foo' => 'c2'],
94+
['foo' => 'b'],
95+
['foo' => 'ping'],
96+
['foo' => 'zaa'],
97+
['foo' => 'pong'],
7498
['foo' => 'bar'],
75-
['foo' => 'append'],
7699
];
77100

78101
self::assertSame($expected, $container->getExtensionConfig('third'));
102+
103+
$container = $this->processLoadExtension($extension, $expected);
104+
105+
self::assertSame('bar', $container->getParameter('foo_param'));
79106
}
80107

81108
public function testLoadExtension()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
imports:
2+
- { resource: './third_a.yaml' }
3+
- { resource: './third_b.yaml' }
4+
5+
third:
6+
foo: ping
7+
8+
when@test:
9+
third:
10+
foo: zaa
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
third:
2+
foo: a
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
imports:
2+
- { resource: './third_c.yaml' }
3+
4+
third:
5+
foo: b
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
third:
2+
foo: c1
3+
4+
when@test:
5+
third:
6+
foo: c2

src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/extensions/services1.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111
<service id="project.service.foo" class="BAR" public="true"/>
1212
</services>
1313

14-
<project:bar babar="babar">
14+
<project:bar foo="ping">
1515
<another />
1616
<another2>%project.parameter.foo%</another2>
1717
</project:bar>
1818

19+
<when env="test">
20+
<project:bar foo="zaa">
21+
</project:bar>
22+
</when>
1923
</container>

src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ public function testLoad()
4747
$this->assertEquals('foo', $container->getParameter('foo'), '->load() loads a PHP file resource');
4848
}
4949

50+
public function testPrependExtensionConfig()
51+
{
52+
$container = new ContainerBuilder();
53+
$container->registerExtension(new \AcmeExtension());
54+
$container->prependExtensionConfig('acme', ['foo' => 'bar']);
55+
$loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Fixtures'), 'prod', new ConfigBuilderGenerator(sys_get_temp_dir()), true);
56+
$loader->load('config/config_builder.php');
57+
58+
$expected = [
59+
['color' => 'blue'],
60+
['foo' => 'bar'],
61+
];
62+
$this->assertSame($expected, $container->getExtensionConfig('acme'));
63+
}
64+
5065
public function testConfigServices()
5166
{
5267
$fixtures = realpath(__DIR__.'/../Fixtures');

src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php

+14
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,20 @@ public function testExtensions()
604604
}
605605
}
606606

607+
public function testPrependExtensionConfig()
608+
{
609+
$container = new ContainerBuilder();
610+
$container->prependExtensionConfig('http://www.example.com/schema/project', ['foo' => 'bar']);
611+
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'), prepend: true);
612+
$loader->load('extensions/services1.xml');
613+
614+
$expected = [
615+
['foo' => 'ping'],
616+
['foo' => 'bar'],
617+
];
618+
$this->assertSame($expected, $container->getExtensionConfig('http://www.example.com/schema/project'));
619+
}
620+
607621
public function testExtensionInPhar()
608622
{
609623
if (\extension_loaded('suhosin') && !str_contains(\ini_get('suhosin.executor.include.whitelist'), 'phar')) {

0 commit comments

Comments
 (0)