Skip to content

Commit f20c7a3

Browse files
Merge branch '7.4' into 8.0
* 7.4: [Config][DependencyInjection] Deprecate the fluent PHP format for semantic configuration [Routing] Allow when@env inside `new RoutesConfig()` trees [Messenger] Simplify code [DependencyInjection] Split ImportsConfig and ParametersConfig out of ServicesConfig
2 parents 10356c8 + c8ddf3c commit f20c7a3

12 files changed

+114
-72
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ CHANGELOG
2929
* Deprecate using `$this` or its internal scope from PHP config files; use the `$loader` variable instead
3030
* Deprecate XML configuration format, use YAML or PHP instead
3131
* Deprecate `ExtensionInterface::getXsdValidationBasePath()` and `getNamespace()`
32+
* Deprecate the fluent PHP format for semantic configuration, instantiate builders inline with the config array as argument and return them instead
3233

3334
7.3
3435
---

Loader/Config/ImportsConfig.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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\Config;
13+
14+
/**
15+
* @psalm-type Imports = list<string|array{
16+
* resource: string,
17+
* type?: string|null,
18+
* ignore_errors?: bool,
19+
* }>
20+
*/
21+
class ImportsConfig
22+
{
23+
/**
24+
* @param Imports $config
25+
*/
26+
public function __construct(
27+
public readonly array $config,
28+
) {
29+
}
30+
}

Loader/Config/ParametersConfig.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\Config;
13+
14+
/**
15+
* @psalm-type Parameters = array<string, scalar|\UnitEnum|array<scalar|\UnitEnum|array|null>|null>
16+
*/
17+
class ParametersConfig
18+
{
19+
/**
20+
* @param Parameters $config
21+
*/
22+
public function __construct(
23+
public readonly array $config,
24+
) {
25+
}
26+
}

Loader/Config/ServicesConfig.php

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Config;
1313

1414
use Symfony\Component\DependencyInjection\ContainerInterface;
15-
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1615
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
1716
use Symfony\Component\DependencyInjection\Reference;
1817
use Symfony\Component\ExpressionLanguage\Expression;
@@ -21,16 +20,10 @@
2120

2221
/**
2322
* @psalm-type Arguments = list<mixed>|array<string, mixed>
24-
* @psalm-type Callback = string|array{0:string|Reference|ReferenceConfigurator,1:string}|\Closure|Reference|ReferenceConfigurator|Expression
23+
* @psalm-type Call = array<string, Arguments>|array{0:string, 1?:Arguments, 2?:bool}|array{method:string, arguments?:Arguments, returns_clone?:bool}
2524
* @psalm-type Tags = list<string|array<string, array<string, mixed>>>
25+
* @psalm-type Callback = string|array{0:string|Reference|ReferenceConfigurator,1:string}|\Closure|Reference|ReferenceConfigurator|Expression
2626
* @psalm-type Deprecation = array{package: string, version: string, message?: string}
27-
* @psalm-type Call = array<string, Arguments>|array{0:string, 1?:Arguments, 2?:bool}|array{method:string, arguments?:Arguments, returns_clone?:bool}
28-
* @psalm-type Imports = list<string|array{
29-
* resource: string,
30-
* type?: string|null,
31-
* ignore_errors?: bool,
32-
* }>
33-
* @psalm-type Parameters = array<string, scalar|\UnitEnum|array<scalar|\UnitEnum|array|null>|null>
3427
* @psalm-type Defaults = array{
3528
* public?: bool,
3629
* tags?: Tags,
@@ -111,32 +104,19 @@
111104
* public?: bool,
112105
* deprecated?: Deprecation,
113106
* }
114-
* @psalm-type Services = array<string, Definition|Alias|Prototype|Stack>|array<class-string, Arguments|null>
107+
* @psalm-type Services = array{
108+
* _defaults?: Defaults,
109+
* _instanceof?: Instanceof,
110+
* ...<string, Definition|Alias|Prototype|Stack|Arguments|null>
111+
* }
115112
*/
116113
class ServicesConfig
117114
{
118-
public readonly array $services;
119-
120115
/**
121-
* @param Services $services
122-
* @param Imports $imports
123-
* @param Parameters $parameters
124-
* @param Defaults $defaults
125-
* @param Instanceof $instanceof
116+
* @param Services $config
126117
*/
127118
public function __construct(
128-
array $services = [],
129-
public readonly array $imports = [],
130-
public readonly array $parameters = [],
131-
array $defaults = [],
132-
array $instanceof = [],
119+
public readonly array $config,
133120
) {
134-
if (isset($services['_defaults']) || isset($services['_instanceof'])) {
135-
throw new InvalidArgumentException('The $services argument should not contain "_defaults" or "_instanceof" keys, use the $defaults and $instanceof parameters instead.');
136-
}
137-
138-
$services['_defaults'] = $defaults;
139-
$services['_instanceof'] = $instanceof;
140-
$this->services = $services;
141121
}
142122
}

Loader/PhpFileLoader.php

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
2626
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
2727
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
28+
use Symfony\Config\ImportsConfig;
29+
use Symfony\Config\ParametersConfig;
2830
use Symfony\Config\ServicesConfig;
2931

3032
/**
@@ -85,7 +87,7 @@ public function load(mixed $resource, ?string $type = null): mixed
8587
if (\is_object($result) && \is_callable($result)) {
8688
$result = $this->callConfigurator($result, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $path);
8789
}
88-
if ($result instanceof ConfigBuilderInterface || $result instanceof ServicesConfig) {
90+
if ($result instanceof ServicesConfig || $result instanceof ParametersConfig || $result instanceof ImportsConfig || $result instanceof ConfigBuilderInterface) {
8991
$result = [$result];
9092
} elseif (!is_iterable($result ?? [])) {
9193
throw new InvalidArgumentException(\sprintf('The return value in config file "%s" is invalid: "%s" given.', $path, get_debug_type($result)));
@@ -96,25 +98,33 @@ public function load(mixed $resource, ?string $type = null): mixed
9698
$config = [$key => $config];
9799
} elseif (!$this->env || 'when@'.$this->env !== $key) {
98100
continue;
99-
} elseif ($config instanceof ServicesConfig || $config instanceof ConfigBuilderInterface) {
101+
} elseif ($config instanceof ServicesConfig || $config instanceof ParametersConfig || $config instanceof ImportsConfig || $config instanceof ConfigBuilderInterface) {
100102
$config = [$config];
101103
} elseif (!is_iterable($config)) {
102104
throw new InvalidArgumentException(\sprintf('The "%s" key should contain an array in "%s".', $key, $path));
103105
}
104106

105107
foreach ($config as $key => $config) {
106-
if ($config instanceof ServicesConfig || \in_array($key, ['imports', 'parameters', 'services'], true)) {
107-
if (!$config instanceof ServicesConfig) {
108+
$expectedKey = match (true) {
109+
$config instanceof ServicesConfig => 'services',
110+
$config instanceof ParametersConfig => 'parameters',
111+
$config instanceof ImportsConfig => 'imports',
112+
default => null,
113+
};
114+
if (null !== $expectedKey || \in_array($key, ['services', 'parameters', 'imports'], true)) {
115+
if (null === $expectedKey) {
108116
$config = [$key => $config];
109-
} elseif (\is_string($key) && 'services' !== $key) {
110-
throw new InvalidArgumentException(\sprintf('Invalid key "%s" returned for the "%s" config builder; none or "services" expected in file "%s".', $key, get_debug_type($config), $path));
117+
} elseif (\is_string($key) && $key !== $expectedKey) {
118+
throw new InvalidArgumentException(\sprintf('Invalid key "%s" returned for the "%s" config builder; none or "%s" expected in file "%s".', $key, get_debug_type($config), $expectedKey, $path));
119+
} else {
120+
$config = [$expectedKey => $config->config];
111121
}
112122
$yamlLoader = new YamlFileLoader($this->container, $this->locator, $this->env, $this->prepend);
113123
$yamlLoader->setResolver(new LoaderResolver([$this]));
114124
$loadContent = new \ReflectionMethod(YamlFileLoader::class, 'loadContent');
115125
++$this->importing;
116126
try {
117-
$loadContent->invoke($yamlLoader, ContainerConfigurator::processValue((array) $config), $path);
127+
$loadContent->invoke($yamlLoader, ContainerConfigurator::processValue($config), $path);
118128
} finally {
119129
--$this->importing;
120130
}
@@ -231,6 +241,7 @@ private function callConfigurator(callable $callback, ContainerConfigurator $con
231241
} catch (InvalidArgumentException|\LogicException $e) {
232242
throw new \InvalidArgumentException(\sprintf('Could not resolve argument "%s" for "%s".', $type.' $'.$parameter->getName(), $path), 0, $e);
233243
}
244+
trigger_deprecation('symfony/dependency-injection', '7.4', 'Using fluent builders for semantic configuration is deprecated, instantiate the "%s" class with the config array as argument and return it instead in "%s".', $type, $path);
234245
$configBuilders[] = $configBuilder;
235246
$arguments[] = $configBuilder;
236247
}
@@ -276,7 +287,7 @@ private function configBuilder(string $namespace): ConfigBuilderInterface
276287
throw new InvalidArgumentException(\sprintf('Could not find or generate class "%s".', $namespace));
277288
}
278289

279-
if (is_a($namespace, ServicesConfig::class, true)) {
290+
if (is_a($namespace, ServicesConfig::class, true) || is_a($namespace, ParametersConfig::class, true) || is_a($namespace, ImportsConfig::class, true)) {
280291
throw new \LogicException(\sprintf('You cannot use "%s" as a config builder; create an instance and return it instead.', $namespace));
281292
}
282293

Tests/Fixtures/config/config_builder_env_configurator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
use Symfony\Component\DependencyInjection\Tests\Fixtures\AcmeConfig;
44
use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
55

6-
return function (AcmeConfig $config) {
7-
$config->color(env('COLOR'));
6+
return function () {
7+
return new AcmeConfig(['color' => env('COLOR')]);
88
};

Tests/Fixtures/config/env_param.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
use Symfony\Component\DependencyInjection\Tests\Fixtures\AcmeConfig;
44

5-
return function (AcmeConfig $config, string $env) {
6-
if ('prod' === $env) {
7-
$config->color('blue');
8-
} else {
9-
$config->color('red');
10-
}
5+
return function (string $env) {
6+
return new AcmeConfig(['color' => 'prod' === $env ? 'blue' : 'red']);
117
};

Tests/Fixtures/config/nested_config_builder.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,4 @@
66
return;
77
}
88

9-
return function (AcmeConfig $config) {
10-
$config->color('red');
11-
};
9+
return new AcmeConfig(['color' => 'red']);
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
<?php
22

33
use Symfony\Component\DependencyInjection\Tests\Fixtures\Bar;
4+
use Symfony\Config\ParametersConfig;
45
use Symfony\Config\ServicesConfig;
56

6-
return new ServicesConfig(
7-
parameters: [
7+
return [
8+
new ParametersConfig([
89
'foo' => 'bar',
9-
],
10-
defaults: [
11-
'public' => true,
12-
],
13-
services: [
10+
]),
11+
new ServicesConfig([
12+
'_defaults' => [
13+
'public' => true,
14+
],
1415
Bar::class => null,
1516
'my_service' => [
1617
'class' => Bar::class,
1718
'arguments' => ['%foo%'],
1819
],
19-
]);
20+
]),
21+
];

Tests/Fixtures/config/return_config_builder.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,4 @@
22

33
use Symfony\Component\DependencyInjection\Tests\Fixtures\AcmeConfig;
44

5-
$config = new AcmeConfig();
6-
$config->color('red');
7-
8-
return $config;
5+
return new AcmeConfig(['color' => 'red']);

0 commit comments

Comments
 (0)