Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3e80e46

Browse filesBrowse files
committed
[DependencyInjection] Add a mechanism to deprecate public services to private
1 parent e9be741 commit 3e80e46
Copy full SHA for 3e80e46

File tree

7 files changed

+234
-0
lines changed
Filter options

7 files changed

+234
-0
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class UnusedTagsPass implements CompilerPassInterface
3434
'container.hot_path',
3535
'container.no_preload',
3636
'container.preload',
37+
'container.private',
3738
'container.reversible',
3839
'container.service_locator',
3940
'container.service_locator_context',

‎src/Symfony/Component/DependencyInjection/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ CHANGELOG
2121
* deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead
2222
* deprecated PHP-DSL's `inline()` function, use `service()` instead
2323
* added support of PHP8 static return type for withers
24+
* added `AliasDeprecatedPublicServicesPass` to deprecate public services to private
2425

2526
5.0.0
2627
-----
+72Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
18+
final class AliasDeprecatedPublicServicesPass extends AbstractRecursivePass
19+
{
20+
private $tagName;
21+
22+
private $aliases = [];
23+
24+
public function __construct(string $tagName = 'container.private')
25+
{
26+
$this->tagName = $tagName;
27+
}
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
protected function processValue($value, bool $isRoot = false)
33+
{
34+
if ($value instanceof Reference && isset($this->aliases[$id = (string) $value])) {
35+
return new Reference($this->aliases[$id], $value->getInvalidBehavior());
36+
}
37+
38+
return parent::processValue($value, $isRoot);
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function process(ContainerBuilder $container)
45+
{
46+
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
47+
if (null === $package = $tags[0]['package'] ?? null) {
48+
throw new InvalidArgumentException(sprintf('The "package" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
49+
}
50+
51+
if (null === $version = $tags[0]['version'] ?? null) {
52+
throw new InvalidArgumentException(sprintf('The "version" attribute is mandatory for the "%s" tag on the "%s" service.', $this->tagName, $id));
53+
}
54+
55+
$definition = $container->getDefinition($id);
56+
if (!$definition->isPublic() || $definition->isPrivate()) {
57+
throw new InvalidArgumentException(sprintf('The "%s" service is private: it cannot have the "%s" tag.', $id, $this->tagName));
58+
}
59+
60+
$container
61+
->setAlias($id, $aliasId = '.'.$this->tagName.'.'.$id)
62+
->setPublic(true)
63+
->setDeprecated($package, $version, 'Accessing the "%alias_id%" service directly from the container is deprecated, use dependency injection instead.');
64+
65+
$container->setDefinition($aliasId, $definition);
66+
67+
$this->aliases[$id] = $aliasId;
68+
}
69+
70+
parent::process($container);
71+
}
72+
}

‎src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public function __construct()
9494
new CheckExceptionOnInvalidReferenceBehaviorPass(),
9595
new ResolveHotPathPass(),
9696
new ResolveNoPreloadPass(),
97+
new AliasDeprecatedPublicServicesPass(),
9798
]];
9899
}
99100

+73Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\DependencyInjection\Compiler\AliasDeprecatedPublicServicesPass;
7+
use Symfony\Component\DependencyInjection\ContainerBuilder;
8+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
9+
10+
final class AliasDeprecatedPublicServicesPassTest extends TestCase
11+
{
12+
public function testProcess()
13+
{
14+
$container = new ContainerBuilder();
15+
$container
16+
->register('foo')
17+
->setPublic(true)
18+
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.2']);
19+
20+
(new AliasDeprecatedPublicServicesPass())->process($container);
21+
22+
$this->assertTrue($container->hasAlias('foo'));
23+
24+
$alias = $container->getAlias('foo');
25+
26+
$this->assertSame('.container.private.foo', (string) $alias);
27+
$this->assertTrue($alias->isPublic());
28+
$this->assertFalse($alias->isPrivate());
29+
$this->assertSame([
30+
'package' => 'foo/bar',
31+
'version' => '1.2',
32+
'message' => 'Accessing the "foo" service directly from the container is deprecated, use dependency injection instead.',
33+
], $alias->getDeprecation('foo'));
34+
}
35+
36+
/**
37+
* @dataProvider processWithMissingAttributeProvider
38+
*/
39+
public function testProcessWithMissingAttribute(string $attribute, array $attributes)
40+
{
41+
$this->expectException(InvalidArgumentException::class);
42+
$this->expectExceptionMessage(sprintf('The "%s" attribute is mandatory for the "container.private" tag on the "foo" service.', $attribute));
43+
44+
$container = new ContainerBuilder();
45+
$container
46+
->register('foo')
47+
->addTag('container.private', $attributes);
48+
49+
(new AliasDeprecatedPublicServicesPass())->process($container);
50+
}
51+
52+
public function processWithMissingAttributeProvider()
53+
{
54+
return [
55+
['package', ['version' => '1.2']],
56+
['version', ['package' => 'foo/bar']],
57+
];
58+
}
59+
60+
public function testProcessWithNonPublicService()
61+
{
62+
$this->expectException(InvalidArgumentException::class);
63+
$this->expectExceptionMessage('The "foo" service is private: it cannot have the "container.private" tag.');
64+
65+
$container = new ContainerBuilder();
66+
$container
67+
->register('foo')
68+
->setPublic(false)
69+
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.2']);
70+
71+
(new AliasDeprecatedPublicServicesPass())->process($container);
72+
}
73+
}

‎src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+38Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,6 +1661,44 @@ public function testAutoAliasing()
16611661

16621662
$this->assertInstanceOf(D::class, $container->get(X::class));
16631663
}
1664+
1665+
/**
1666+
* @group legacy
1667+
*/
1668+
public function testDirectlyAccessingDeprecatedPublicService()
1669+
{
1670+
$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.');
1671+
1672+
$container = new ContainerBuilder();
1673+
$container
1674+
->register(A::class)
1675+
->setPublic(true)
1676+
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
1677+
1678+
$container->compile();
1679+
1680+
$container->get(A::class);
1681+
}
1682+
1683+
public function testReferencingDeprecatedPublicService()
1684+
{
1685+
$container = new ContainerBuilder();
1686+
$container
1687+
->register(A::class)
1688+
->setPublic(true)
1689+
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
1690+
$container
1691+
->register(B::class)
1692+
->setPublic(true)
1693+
->addArgument(new Reference(A::class));
1694+
1695+
$container->compile();
1696+
1697+
// No deprecation should be triggered.
1698+
$container->get(B::class);
1699+
1700+
$this->addToAssertionCount(1);
1701+
}
16641702
}
16651703

16661704
class FooClass

‎src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+48Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,54 @@ public function testDumpServiceWithAbstractArgument()
14291429
$dumper = new PhpDumper($container);
14301430
$dumper->dump();
14311431
}
1432+
1433+
/**
1434+
* @group legacy
1435+
*/
1436+
public function testDirectlyAccessingDeprecatedPublicService()
1437+
{
1438+
$this->expectDeprecation('Since foo/bar 3.8: Accessing the "bar" service directly from the container is deprecated, use dependency injection instead.');
1439+
1440+
$container = new ContainerBuilder();
1441+
$container
1442+
->register('bar', \BarClass::class)
1443+
->setPublic(true)
1444+
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
1445+
1446+
$container->compile();
1447+
1448+
$dumper = new PhpDumper($container);
1449+
eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Directly_Accessing_Deprecated_Public_Service']));
1450+
1451+
$container = new \Symfony_DI_PhpDumper_Test_Directly_Accessing_Deprecated_Public_Service();
1452+
1453+
$container->get('bar');
1454+
}
1455+
1456+
public function testReferencingDeprecatedPublicService()
1457+
{
1458+
$container = new ContainerBuilder();
1459+
$container
1460+
->register('bar', \BarClass::class)
1461+
->setPublic(true)
1462+
->addTag('container.private', ['package' => 'foo/bar', 'version' => '3.8']);
1463+
$container
1464+
->register('bar_user', \BarUserClass::class)
1465+
->setPublic(true)
1466+
->addArgument(new Reference('bar'));
1467+
1468+
$container->compile();
1469+
1470+
$dumper = new PhpDumper($container);
1471+
eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Referencing_Deprecated_Public_Service']));
1472+
1473+
$container = new \Symfony_DI_PhpDumper_Test_Referencing_Deprecated_Public_Service();
1474+
1475+
// No deprecation should be triggered.
1476+
$container->get('bar_user');
1477+
1478+
$this->addToAssertionCount(1);
1479+
}
14321480
}
14331481

14341482
class Rot13EnvVarProcessor implements EnvVarProcessorInterface

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.