Skip to content

Commit 858dca4

Browse files
feature #40214 [FrameworkBundle] allow container/routing configurators to vary by env (nicolas-grekas)
This PR was merged into the 5.3-dev branch. Discussion ---------- [FrameworkBundle] allow container/routing configurators to vary by env | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | #40215 | License | MIT | Doc PR | - Inspired by symfony/webpack-encore#900 and by a chat on Slack with @weaverryan This aims at allowing conditional configuration, which would allow merging config files in one. Using the PHP-DSL: ```php $container ->when(env: 'prod') ->services() ->set(Foo::class) //... ``` In Yaml: ```yaml framework: secret: '%env(APP_SECRET)%' when@dev: services: App\FooForDev: ~ when@test: framework: test: true session: storage_factory_id: session.storage.mock_file ``` In XML (omitting namespaces): ```xml <when env="test"> <framework test="true"> <!-- ... --> </framework> </when> ``` A similar syntax is also provided for routes, with support for annotations: `@Route(env="prod")` defines a route that is enabled only on the "prod" env. Commits ------- 108375b [FrameworkBundle] allow container/routing configurators to vary by env
2 parents bf30fa4 + 108375b commit 858dca4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+474
-66
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,10 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co
10111011
$container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class)
10121012
->setPublic(false)
10131013
->addTag('routing.loader', ['priority' => -10])
1014-
->addArgument(new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE));
1014+
->setArguments([
1015+
new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE),
1016+
'%kernel.environment%',
1017+
]);
10151018

10161019
$container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class)
10171020
->setPublic(false)

src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public function registerContainerConfiguration(LoaderInterface $loader)
152152
};
153153

154154
try {
155-
$this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file), $loader);
155+
$this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader);
156156
} finally {
157157
$instanceof = [];
158158
$kernelLoader->registerAliasesForSinglyImplementedInterfaces();
@@ -193,7 +193,7 @@ public function loadRoutes(LoaderInterface $loader)
193193
return $routes->build();
194194
}
195195

196-
$this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file));
196+
$this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment()));
197197

198198
foreach ($collection as $route) {
199199
$controller = $route->getDefault('_controller');

src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php

+6
Original file line numberDiff line numberDiff line change
@@ -49,36 +49,42 @@
4949
->set('routing.loader.xml', XmlFileLoader::class)
5050
->args([
5151
service('file_locator'),
52+
'%kernel.environment%',
5253
])
5354
->tag('routing.loader')
5455

5556
->set('routing.loader.yml', YamlFileLoader::class)
5657
->args([
5758
service('file_locator'),
59+
'%kernel.environment%',
5860
])
5961
->tag('routing.loader')
6062

6163
->set('routing.loader.php', PhpFileLoader::class)
6264
->args([
6365
service('file_locator'),
66+
'%kernel.environment%',
6467
])
6568
->tag('routing.loader')
6669

6770
->set('routing.loader.glob', GlobFileLoader::class)
6871
->args([
6972
service('file_locator'),
73+
'%kernel.environment%',
7074
])
7175
->tag('routing.loader')
7276

7377
->set('routing.loader.directory', DirectoryLoader::class)
7478
->args([
7579
service('file_locator'),
80+
'%kernel.environment%',
7681
])
7782
->tag('routing.loader')
7883

7984
->set('routing.loader.container', ContainerLoader::class)
8085
->args([
8186
tagged_locator('routing.route_loader'),
87+
'%kernel.environment%',
8288
])
8389
->tag('routing.loader')
8490

src/Symfony/Bundle/FrameworkBundle/composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"php": ">=7.2.5",
2020
"ext-xml": "*",
2121
"symfony/cache": "^5.2",
22-
"symfony/config": "^5.0",
22+
"symfony/config": "^5.3",
2323
"symfony/dependency-injection": "^5.3",
2424
"symfony/deprecation-contracts": "^2.1",
2525
"symfony/event-dispatcher": "^5.1",
@@ -30,7 +30,7 @@
3030
"symfony/polyfill-php80": "^1.15",
3131
"symfony/filesystem": "^4.4|^5.0",
3232
"symfony/finder": "^4.4|^5.0",
33-
"symfony/routing": "^5.2"
33+
"symfony/routing": "^5.3"
3434
},
3535
"require-dev": {
3636
"doctrine/annotations": "^1.10.4",

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ abstract class FileLoader extends Loader
3131

3232
private $currentDir;
3333

34-
public function __construct(FileLocatorInterface $locator)
34+
public function __construct(FileLocatorInterface $locator, string $env = null)
3535
{
3636
$this->locator = $locator;
37+
parent::__construct($env);
3738
}
3839

3940
/**

src/Symfony/Component/Config/Loader/Loader.php

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
abstract class Loader implements LoaderInterface
2222
{
2323
protected $resolver;
24+
protected $env;
25+
26+
public function __construct(string $env = null)
27+
{
28+
$this->env = $env;
29+
}
2430

2531
/**
2632
* {@inheritdoc}

src/Symfony/Component/DependencyInjection/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Add `%env(not:...)%` processor to negate boolean values
99
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
1010
* Add autoconfigurable attributes
11+
* Add support for per-env configuration in loaders
1112

1213
5.2.0
1314
-----

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@ class ClosureLoader extends Loader
2525
{
2626
private $container;
2727

28-
public function __construct(ContainerBuilder $container)
28+
public function __construct(ContainerBuilder $container, string $env = null)
2929
{
3030
$this->container = $container;
31+
parent::__construct($env);
3132
}
3233

3334
/**
3435
* {@inheritdoc}
3536
*/
3637
public function load($resource, string $type = null)
3738
{
38-
$resource($this->container);
39+
$resource($this->container, $this->env);
3940
}
4041

4142
/**

src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php

+20-1
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@ class ContainerConfigurator extends AbstractConfigurator
3535
private $path;
3636
private $file;
3737
private $anonymousCount = 0;
38+
private $env;
3839

39-
public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file)
40+
public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, string $env = null)
4041
{
4142
$this->container = $container;
4243
$this->loader = $loader;
4344
$this->instanceof = &$instanceof;
4445
$this->path = $path;
4546
$this->file = $file;
47+
$this->env = $env;
4648
}
4749

4850
final public function extension(string $namespace, array $config)
@@ -71,6 +73,23 @@ final public function services(): ServicesConfigurator
7173
return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount);
7274
}
7375

76+
/**
77+
* @return static
78+
*/
79+
final public function when(string $env): self
80+
{
81+
if ($env === $this->env) {
82+
return clone $this;
83+
}
84+
85+
$instanceof = $this->instanceof;
86+
$clone = clone $this;
87+
$clone->container = new ContainerBuilder(clone $this->container->getParameterBag());
88+
$clone->instanceof = &$instanceof;
89+
90+
return $clone;
91+
}
92+
7493
/**
7594
* @return static
7695
*/

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ abstract class FileLoader extends BaseFileLoader
3939
protected $singlyImplemented = [];
4040
protected $autoRegisterAliasesForSinglyImplementedInterfaces = true;
4141

42-
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator)
42+
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null)
4343
{
4444
$this->container = $container;
4545

46-
parent::__construct($locator);
46+
parent::__construct($locator, $env);
4747
}
4848

4949
/**

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

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public function load($resource, string $type = null)
4444
$this->container->setParameter($key, $this->phpize($value));
4545
}
4646
}
47+
48+
if ($this->env && \is_array($result['parameters@'.$this->env] ?? null)) {
49+
foreach ($result['parameters@'.$this->env] as $key => $value) {
50+
$this->container->setParameter($key, $this->phpize($value));
51+
}
52+
}
4753
}
4854

4955
/**

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function load($resource, string $type = null)
4747
$callback = $load($path);
4848

4949
if (\is_object($callback) && \is_callable($callback)) {
50-
$callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource), $this->container, $this);
50+
$callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $this->container, $this);
5151
}
5252
} finally {
5353
$this->instanceof = [];

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

+31-18
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,36 @@ public function load($resource, string $type = null)
5050

5151
$this->container->fileExists($path);
5252

53-
$defaults = $this->getServiceDefaults($xml, $path);
53+
$this->loadXml($xml, $path);
54+
55+
if ($this->env) {
56+
$xpath = new \DOMXPath($xml);
57+
$xpath->registerNamespace('container', self::NS);
58+
foreach ($xpath->query(sprintf('//container:when[@env="%s"]', $this->env)) ?: [] as $root) {
59+
$this->loadXml($xml, $path, $root);
60+
}
61+
}
62+
}
63+
64+
private function loadXml(\DOMDocument $xml, string $path, \DOMNode $root = null): void
65+
{
66+
$defaults = $this->getServiceDefaults($xml, $path, $root);
5467

5568
// anonymous services
56-
$this->processAnonymousServices($xml, $path);
69+
$this->processAnonymousServices($xml, $path, $root);
5770

5871
// imports
59-
$this->parseImports($xml, $path);
72+
$this->parseImports($xml, $path, $root);
6073

6174
// parameters
62-
$this->parseParameters($xml, $path);
75+
$this->parseParameters($xml, $path, $root);
6376

6477
// extensions
65-
$this->loadFromExtensions($xml);
78+
$this->loadFromExtensions($xml, $root);
6679

6780
// services
6881
try {
69-
$this->parseDefinitions($xml, $path, $defaults);
82+
$this->parseDefinitions($xml, $path, $defaults, $root);
7083
} finally {
7184
$this->instanceof = [];
7285
$this->registerAliasesForSinglyImplementedInterfaces();
@@ -89,19 +102,19 @@ public function supports($resource, string $type = null)
89102
return 'xml' === $type;
90103
}
91104

92-
private function parseParameters(\DOMDocument $xml, string $file)
105+
private function parseParameters(\DOMDocument $xml, string $file, \DOMNode $root = null)
93106
{
94-
if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) {
107+
if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) {
95108
$this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file));
96109
}
97110
}
98111

99-
private function parseImports(\DOMDocument $xml, string $file)
112+
private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = null)
100113
{
101114
$xpath = new \DOMXPath($xml);
102115
$xpath->registerNamespace('container', self::NS);
103116

104-
if (false === $imports = $xpath->query('//container:imports/container:import')) {
117+
if (false === $imports = $xpath->query('.//container:imports/container:import', $root)) {
105118
return;
106119
}
107120

@@ -112,19 +125,19 @@ private function parseImports(\DOMDocument $xml, string $file)
112125
}
113126
}
114127

115-
private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults)
128+
private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, \DOMNode $root = null)
116129
{
117130
$xpath = new \DOMXPath($xml);
118131
$xpath->registerNamespace('container', self::NS);
119132

120-
if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype|//container:services/container:stack')) {
133+
if (false === $services = $xpath->query('.//container:services/container:service|.//container:services/container:prototype|.//container:services/container:stack', $root)) {
121134
return;
122135
}
123136
$this->setCurrentDir(\dirname($file));
124137

125138
$this->instanceof = [];
126139
$this->isLoadingInstanceof = true;
127-
$instanceof = $xpath->query('//container:services/container:instanceof');
140+
$instanceof = $xpath->query('.//container:services/container:instanceof', $root);
128141
foreach ($instanceof as $service) {
129142
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition()));
130143
}
@@ -170,12 +183,12 @@ private function parseDefinitions(\DOMDocument $xml, string $file, Definition $d
170183
}
171184
}
172185

173-
private function getServiceDefaults(\DOMDocument $xml, string $file): Definition
186+
private function getServiceDefaults(\DOMDocument $xml, string $file, \DOMNode $root = null): Definition
174187
{
175188
$xpath = new \DOMXPath($xml);
176189
$xpath->registerNamespace('container', self::NS);
177190

178-
if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) {
191+
if (null === $defaultsNode = $xpath->query('.//container:services/container:defaults', $root)->item(0)) {
179192
return new Definition();
180193
}
181194

@@ -393,7 +406,7 @@ private function parseFileToDOM(string $file): \DOMDocument
393406
/**
394407
* Processes anonymous services.
395408
*/
396-
private function processAnonymousServices(\DOMDocument $xml, string $file)
409+
private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null)
397410
{
398411
$definitions = [];
399412
$count = 0;
@@ -403,7 +416,7 @@ private function processAnonymousServices(\DOMDocument $xml, string $file)
403416
$xpath->registerNamespace('container', self::NS);
404417

405418
// anonymous services as arguments/properties
406-
if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]|//container:bind[not(@id)]|//container:factory[not(@service)]|//container:configurator[not(@service)]')) {
419+
if (false !== $nodes = $xpath->query('.//container:argument[@type="service"][not(@id)]|.//container:property[@type="service"][not(@id)]|.//container:bind[not(@id)]|.//container:factory[not(@service)]|.//container:configurator[not(@service)]', $root)) {
407420
foreach ($nodes as $node) {
408421
if ($services = $this->getChildren($node, 'service')) {
409422
// give it a unique name
@@ -422,7 +435,7 @@ private function processAnonymousServices(\DOMDocument $xml, string $file)
422435
}
423436

424437
// anonymous services "in the wild"
425-
if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) {
438+
if (false !== $nodes = $xpath->query('.//container:services/container:service[not(@id)]', $root)) {
426439
foreach ($nodes as $node) {
427440
throw new InvalidArgumentException(sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo()));
428441
}

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

+16-2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ public function load($resource, string $type = null)
129129
return;
130130
}
131131

132+
$this->loadContent($content, $path);
133+
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+
}
139+
140+
$this->loadContent($content['when@'.$this->env], $path);
141+
}
142+
}
143+
144+
private function loadContent($content, $path)
145+
{
132146
// imports
133147
$this->parseImports($content, $path);
134148

@@ -770,7 +784,7 @@ private function validate($content, string $file): ?array
770784
}
771785

772786
foreach ($content as $namespace => $data) {
773-
if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
787+
if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) {
774788
continue;
775789
}
776790

@@ -907,7 +921,7 @@ private function resolveServices($value, string $file, bool $isParameter = false
907921
private function loadFromExtensions(array $content)
908922
{
909923
foreach ($content as $namespace => $values) {
910-
if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
924+
if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) {
911925
continue;
912926
}
913927

0 commit comments

Comments
 (0)