Skip to content

[DependencyInjection] Enable deprecating parameters #47719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CHANGELOG
* Add support for nesting autowiring-related attributes into `#[Autowire(...)]`
* Deprecate undefined and numeric keys with `service_locator` config
* Fail if Target attribute does not exist during compilation
* Enable deprecating parameters with `ContainerBuilder::deprecateParameter()`

6.2
---
Expand Down
6 changes: 5 additions & 1 deletion src/Symfony/Component/DependencyInjection/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Contracts\Service\ResetInterface;

Expand Down Expand Up @@ -82,7 +83,10 @@ public function compile()
{
$this->parameterBag->resolve();

$this->parameterBag = new FrozenParameterBag($this->parameterBag->all());
$this->parameterBag = new FrozenParameterBag(
$this->parameterBag->all(),
$this->parameterBag instanceof ParameterBag ? $this->parameterBag->allDeprecated() : []
);

$this->compiled = true;
}
Expand Down
31 changes: 27 additions & 4 deletions src/Symfony/Component/DependencyInjection/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
Expand Down Expand Up @@ -610,7 +611,15 @@ public function merge(self $container)
}
}
$this->addAliases($container->getAliases());
$this->getParameterBag()->add($container->getParameterBag()->all());
$parameterBag = $this->getParameterBag();
$otherBag = $container->getParameterBag();
$parameterBag->add($otherBag->all());

if ($parameterBag instanceof ParameterBag && $otherBag instanceof ParameterBag) {
foreach ($otherBag->allDeprecated() as $name => $deprecated) {
$parameterBag->deprecate($name, ...$deprecated);
}
}

if ($this->trackResources) {
foreach ($container->getResources() as $resource) {
Expand All @@ -626,9 +635,9 @@ public function merge(self $container)
$this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
}

if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) {
$envPlaceholders = $container->getParameterBag()->getEnvPlaceholders();
$this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag());
if ($parameterBag instanceof EnvPlaceholderParameterBag && $otherBag instanceof EnvPlaceholderParameterBag) {
$envPlaceholders = $otherBag->getEnvPlaceholders();
$parameterBag->mergeEnvPlaceholders($otherBag);
} else {
$envPlaceholders = [];
}
Expand Down Expand Up @@ -689,6 +698,20 @@ public function prependExtensionConfig(string $name, array $config)
array_unshift($this->extensionConfigs[$name], $config);
}

/**
* Deprecates a service container parameter.
*
* @throws ParameterNotFoundException if the parameter is not defined
*/
public function deprecateParameter(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.'): void
{
if (!$this->parameterBag instanceof ParameterBag) {
throw new BadMethodCallException(sprintf('The parameter bag must be an instance of "%s" to call "%s".', ParameterBag::class, __METHOD__));
}

$this->parameterBag->deprecate($name, $package, $version, $message);
}

/**
* Compiles the container.
*
Expand Down
39 changes: 34 additions & 5 deletions src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper;
use Symfony\Component\DependencyInjection\Loader\FileLoader;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator;
use Symfony\Component\DependencyInjection\TypedReference;
Expand Down Expand Up @@ -1234,6 +1235,8 @@ private function startClass(string $class, string $baseClass, bool $hasProxyClas
*/
class $class extends $baseClass
{
private const DEPRECATED_PARAMETERS = [];

protected \$parameters = [];
protected readonly \WeakReference \$ref;

Expand All @@ -1242,11 +1245,9 @@ public function __construct()
\$this->ref = \WeakReference::create(\$this);

EOF;
$code = str_replace(" private const DEPRECATED_PARAMETERS = [];\n\n", $this->addDeprecatedParameters(), $code);
if ($this->asFiles) {
$code = str_replace('$parameters = []', "\$containerDir;\n protected \$parameters = [];\n private \$buildParameters", $code);
$code = str_replace('__construct()', '__construct(array $buildParameters = [], $containerDir = __DIR__)', $code);
$code .= " \$this->buildParameters = \$buildParameters;\n";
$code .= " \$this->containerDir = \$containerDir;\n";
$code = str_replace('__construct()', '__construct(private array $buildParameters = [], protected string $containerDir = __DIR__)', $code);

if (null !== $this->targetDirRegex) {
$code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code);
Expand Down Expand Up @@ -1391,6 +1392,24 @@ public function getRemovedIds(): array
EOF;
}

private function addDeprecatedParameters(): string
{
if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag) {
return '';
}

if (!$deprecated = $bag->allDeprecated()) {
return '';
}
$code = '';
ksort($deprecated);
foreach ($deprecated as $param => $deprecation) {
$code .= ' '.$this->doExport($param).' => ['.implode(', ', array_map($this->doExport(...), $deprecation))."],\n";
}

return " private const DEPRECATED_PARAMETERS = [\n{$code} ];\n\n";
}

private function addMethodMap(): string
{
$code = '';
Expand Down Expand Up @@ -1552,6 +1571,10 @@ private function addDefaultParametersMethod(): string

public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null
{
if (isset(self::DEPRECATED_PARAMETERS[$name])) {
trigger_deprecation(...self::DEPRECATED_PARAMETERS[$name]);
}

if (isset($this->buildParameters[$name])) {
return $this->buildParameters[$name];
}
Expand Down Expand Up @@ -1590,17 +1613,23 @@ public function getParameterBag(): ParameterBagInterface
foreach ($this->buildParameters as $name => $value) {
$parameters[$name] = $value;
}
$this->parameterBag = new FrozenParameterBag($parameters);
$this->parameterBag = new FrozenParameterBag($parameters, self::DEPRECATED_PARAMETERS);
}

return $this->parameterBag;
}

EOF;

if (!$this->asFiles) {
$code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code);
}

if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag || !$bag->allDeprecated()) {
$code = preg_replace("/\n.*DEPRECATED_PARAMETERS.*\n.*\n.*\n/m", '', $code, 1);
$code = str_replace(', self::DEPRECATED_PARAMETERS', '', $code);
}

if ($dynamicPhp) {
$loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8);
$getDynamicParameter = <<<'EOF'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ class FrozenParameterBag extends ParameterBag
* all keys are already lowercased.
*
* This is always the case when used internally.
*
* @param array $parameters An array of parameters
*/
public function __construct(array $parameters = [])
{
public function __construct(
array $parameters = [],
protected array $deprecatedParameters = [],
) {
$this->parameters = $parameters;
$this->resolved = true;
}
Expand All @@ -49,6 +49,11 @@ public function set(string $name, array|bool|string|int|float|\UnitEnum|null $va
throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
}

public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.')
{
throw new LogicException('Impossible to call deprecate() on a frozen ParameterBag.');
}

public function remove(string $name)
{
throw new LogicException('Impossible to call remove() on a frozen ParameterBag.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class ParameterBag implements ParameterBagInterface
{
protected $parameters = [];
protected $resolved = false;
protected array $deprecatedParameters = [];

public function __construct(array $parameters = [])
{
Expand All @@ -47,6 +48,11 @@ public function all(): array
return $this->parameters;
}

public function allDeprecated(): array
{
return $this->deprecatedParameters;
}

public function get(string $name): array|bool|string|int|float|\UnitEnum|null
{
if (!\array_key_exists($name, $this->parameters)) {
Expand Down Expand Up @@ -81,6 +87,10 @@ public function get(string $name): array|bool|string|int|float|\UnitEnum|null
throw new ParameterNotFoundException($name, null, null, null, $alternatives, $nonNestedAlternative);
}

if (isset($this->deprecatedParameters[$name])) {
trigger_deprecation(...$this->deprecatedParameters[$name]);
}

return $this->parameters[$name];
}

Expand All @@ -95,14 +105,28 @@ public function set(string $name, array|bool|string|int|float|\UnitEnum|null $va
$this->parameters[$name] = $value;
}

/**
* Deprecates a service container parameter.
*
* @throws ParameterNotFoundException if the parameter is not defined
*/
public function deprecate(string $name, string $package, string $version, string $message = 'The parameter "%s" is deprecated.')
{
if (!\array_key_exists($name, $this->parameters)) {
throw new ParameterNotFoundException($name);
}

$this->deprecatedParameters[$name] = [$package, $version, $message, $name];
}

public function has(string $name): bool
{
return \array_key_exists($name, $this->parameters);
}

public function remove(string $name)
{
unset($this->parameters[$name]);
unset($this->parameters[$name], $this->deprecatedParameters[$name]);
}

public function resolve()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
Expand All @@ -42,6 +44,7 @@
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
Expand Down Expand Up @@ -100,6 +103,109 @@ public function testDefinitions()
}
}

/**
* The test should be kept in the group as it always expects a deprecation.
*
* @group legacy
*/
public function testDeprecateParameter()
{
$builder = new ContainerBuilder();
$builder->setParameter('foo', 'bar');

$builder->deprecateParameter('foo', 'symfony/test', '6.3');

$this->expectDeprecation('Since symfony/test 6.3: The parameter "foo" is deprecated.');

$builder->getParameter('foo');
}

/**
* The test should be kept in the group as it always expects a deprecation.
*
* @group legacy
*/
public function testParameterDeprecationIsTrgiggeredWhenCompiled()
{
$builder = new ContainerBuilder();
$builder->setParameter('foo', '%bar%');
$builder->setParameter('bar', 'baz');

$builder->deprecateParameter('bar', 'symfony/test', '6.3');

$this->expectDeprecation('Since symfony/test 6.3: The parameter "bar" is deprecated.');

$builder->compile();
}

public function testDeprecateParameterThrowsWhenParameterIsUndefined()
{
$builder = new ContainerBuilder();

$this->expectException(ParameterNotFoundException::class);
$this->expectExceptionMessage('You have requested a non-existent parameter "foo".');

$builder->deprecateParameter('foo', 'symfony/test', '6.3');
}

public function testDeprecateParameterThrowsWhenParameterBagIsNotInternal()
{
$builder = new ContainerBuilder(new class() implements ParameterBagInterface {
public function clear()
{
}

public function add(array $parameters)
{
}

public function all(): array
{
return [];
}

public function get(string $name): array|bool|string|int|float|\UnitEnum|null
{
return null;
}

public function remove(string $name)
{
}

public function set(string $name, \UnitEnum|float|int|bool|array|string|null $value)
{
}

public function has(string $name): bool
{
return false;
}

public function resolve()
{
}

public function resolveValue(mixed $value)
{
}

public function escapeValue(mixed $value): mixed
{
return null;
}

public function unescapeValue(mixed $value): mixed
{
return null;
}
});

$this->expectException(BadMethodCallException::class);

$builder->deprecateParameter('foo', 'symfony/test', '6.3');
}

public function testRegister()
{
$builder = new ContainerBuilder();
Expand Down
Loading