[DependencyInjection] Add a mechanism to deprecate public services to private
This commit is contained in:
parent
e9be7418a3
commit
3e80e461a9
@ -34,6 +34,7 @@ class UnusedTagsPass implements CompilerPassInterface
|
|||||||
'container.hot_path',
|
'container.hot_path',
|
||||||
'container.no_preload',
|
'container.no_preload',
|
||||||
'container.preload',
|
'container.preload',
|
||||||
|
'container.private',
|
||||||
'container.reversible',
|
'container.reversible',
|
||||||
'container.service_locator',
|
'container.service_locator',
|
||||||
'container.service_locator_context',
|
'container.service_locator_context',
|
||||||
|
@ -21,6 +21,7 @@ CHANGELOG
|
|||||||
* deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead
|
* deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead
|
||||||
* deprecated PHP-DSL's `inline()` function, use `service()` instead
|
* deprecated PHP-DSL's `inline()` function, use `service()` instead
|
||||||
* added support of PHP8 static return type for withers
|
* added support of PHP8 static return type for withers
|
||||||
|
* added `AliasDeprecatedPublicServicesPass` to deprecate public services to private
|
||||||
|
|
||||||
5.0.0
|
5.0.0
|
||||||
-----
|
-----
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass
|
||||||
|
{
|
||||||
|
private $tagName;
|
||||||
|
|
||||||
|
private $aliases = [];
|
||||||
|
|
||||||
|
public function __construct(string $tagName = 'container.private')
|
||||||
|
{
|
||||||
|
$this->tagName = $tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function processValue($value, bool $isRoot = false)
|
||||||
|
{
|
||||||
|
if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) {
|
||||||
|
return new Reference($this->aliases[$id], $value->getInvalidBehavior());
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::processValue($value, $isRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||||
|
if (null === $package = $tags[0]['package'] ?? null) {
|
||||||
|
throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $version = $tags[0]['version'] ?? null) {
|
||||||
|
throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition = $container->getDefinition($id);
|
||||||
|
if (!$definition->isPublic() || $definition->isPrivate()) {
|
||||||
|
throw new InvalidArgumentException(sprintf('The "%s" service is private: it cannot have the "%s" tag.', $id, $this->tagName));
|
||||||
|
}
|
||||||
|
|
||||||
|
$container
|
||||||
|
->setAlias($id, $aliasId = '.'.$this->tagName.'.'.$id)
|
||||||
|
->setPublic(true)
|
||||||
|
->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.');
|
||||||
|
|
||||||
|
$container->setDefinition($aliasId, $definition);
|
||||||
|
|
||||||
|
$this->aliases[$id] = $aliasId;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::process($container);
|
||||||
|
}
|
||||||
|
}
|
@ -94,6 +94,7 @@ class PassConfig
|
|||||||
new CheckExceptionOnInvalidReferenceBehaviorPass(),
|
new CheckExceptionOnInvalidReferenceBehaviorPass(),
|
||||||
new ResolveHotPathPass(),
|
new ResolveHotPathPass(),
|
||||||
new ResolveNoPreloadPass(),
|
new ResolveNoPreloadPass(),
|
||||||
|
new AliasDeprecatedPublicServicesPass(),
|
||||||
]];
|
]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\AliasDeprecatedPublicServicesPass;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
|
final class AliasDeprecatedPublicServicesPassTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testProcess()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container
|
||||||
|
->register('foo')
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.2']);
|
||||||
|
|
||||||
|
(new AliasDeprecatedPublicServicesPass())->process($container);
|
||||||
|
|
||||||
|
$this->assertTrue($container->hasAlias('foo'));
|
||||||
|
|
||||||
|
$alias = $container->getAlias('foo');
|
||||||
|
|
||||||
|
$this->assertSame('.container.private.foo', (string) $alias);
|
||||||
|
$this->assertTrue($alias->isPublic());
|
||||||
|
$this->assertFalse($alias->isPrivate());
|
||||||
|
$this->assertSame([
|
||||||
|
'package' => 'foo/bar',
|
||||||
|
'version' => '1.2',
|
||||||
|
'message' => 'Accessing the "foo" service directly from the container is deprecated, use dependency injection instead.',
|
||||||
|
], $alias->getDeprecation('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider processWithMissingAttributeProvider
|
||||||
|
*/
|
||||||
|
public function testProcessWithMissingAttribute(string $attribute, array $attributes)
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage(sprintf('The "%s" attribute is mandatory for the "container.private" tag on the "foo" service.', $attribute));
|
||||||
|
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container
|
||||||
|
->register('foo')
|
||||||
|
->addTag('container.private', $attributes);
|
||||||
|
|
||||||
|
(new AliasDeprecatedPublicServicesPass())->process($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function processWithMissingAttributeProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['package', ['version' => '1.2']],
|
||||||
|
['version', ['package' => 'foo/bar']],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testProcessWithNonPublicService()
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('The "foo" service is private: it cannot have the "container.private" tag.');
|
||||||
|
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container
|
||||||
|
->register('foo')
|
||||||
|
->setPublic(false)
|
||||||
|
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.2']);
|
||||||
|
|
||||||
|
(new AliasDeprecatedPublicServicesPass())->process($container);
|
||||||
|
}
|
||||||
|
}
|
@ -1661,6 +1661,44 @@ class ContainerBuilderTest extends TestCase
|
|||||||
|
|
||||||
$this->assertInstanceOf(D::class, $container->get(X::class));
|
$this->assertInstanceOf(D::class, $container->get(X::class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
|
public function testDirectlyAccessingDeprecatedPublicService()
|
||||||
|
{
|
||||||
|
$this->expectDeprecation('Since foo/bar 3.8: Accessing the "Symfony\Component\DependencyInjection\Tests\A" service directly from the container is deprecated, use dependency injection instead.');
|
||||||
|
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container
|
||||||
|
->register(A::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$container->get(A::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReferencingDeprecatedPublicService()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container
|
||||||
|
->register(A::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
|
||||||
|
$container
|
||||||
|
->register(B::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addArgument(new Reference(A::class));
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
// No deprecation should be triggered.
|
||||||
|
$container->get(B::class);
|
||||||
|
|
||||||
|
$this->addToAssertionCount(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FooClass
|
class FooClass
|
||||||
|
@ -1429,6 +1429,54 @@ class PhpDumperTest extends TestCase
|
|||||||
$dumper = new PhpDumper($container);
|
$dumper = new PhpDumper($container);
|
||||||
$dumper->dump();
|
$dumper->dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group legacy
|
||||||
|
*/
|
||||||
|
public function testDirectlyAccessingDeprecatedPublicService()
|
||||||
|
{
|
||||||
|
$this->expectDeprecation('Since foo/bar 3.8: Accessing the "bar" service directly from the container is deprecated, use dependency injection instead.');
|
||||||
|
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container
|
||||||
|
->register('bar', \BarClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$dumper = new PhpDumper($container);
|
||||||
|
eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Directly_Accessing_Deprecated_Public_Service']));
|
||||||
|
|
||||||
|
$container = new \Symfony_DI_PhpDumper_Test_Directly_Accessing_Deprecated_Public_Service();
|
||||||
|
|
||||||
|
$container->get('bar');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testReferencingDeprecatedPublicService()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container
|
||||||
|
->register('bar', \BarClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
|
||||||
|
$container
|
||||||
|
->register('bar_user', \BarUserClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addArgument(new Reference('bar'));
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$dumper = new PhpDumper($container);
|
||||||
|
eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Referencing_Deprecated_Public_Service']));
|
||||||
|
|
||||||
|
$container = new \Symfony_DI_PhpDumper_Test_Referencing_Deprecated_Public_Service();
|
||||||
|
|
||||||
|
// No deprecation should be triggered.
|
||||||
|
$container->get('bar_user');
|
||||||
|
|
||||||
|
$this->addToAssertionCount(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Rot13EnvVarProcessor implements EnvVarProcessorInterface
|
class Rot13EnvVarProcessor implements EnvVarProcessorInterface
|
||||||
|
Reference in New Issue
Block a user