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 e080ce2

Browse filesBrowse files
committed
[DependencyInjection] Add a mechanism to deprecate public services to private
1 parent 6dc7d8b commit e080ce2
Copy full SHA for e080ce2

File tree

7 files changed

+240
-0
lines changed
Filter options

7 files changed

+240
-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
@@ -41,6 +41,7 @@ class UnusedTagsPass implements CompilerPassInterface
4141
'controller.argument_value_resolver',
4242
'controller.service_arguments',
4343
'data_collector',
44+
'deprecated_public_service',
4445
'form.type',
4546
'form.type_extension',
4647
'form.type_guesser',

‎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
@@ -19,6 +19,7 @@ CHANGELOG
1919
* deprecated `Definition::getDeprecationMessage()`, use `Definition::getDeprecation()` instead
2020
* deprecated `Alias::getDeprecationMessage()`, use `Alias::getDeprecation()` instead
2121
* deprecated PHP-DSL's `inline()` function, use `service()` instead
22+
* added `AliasDeprecatedPublicServicesPass` to deprecate public services to private
2223

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

‎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
@@ -92,6 +92,7 @@ public function __construct()
9292
$this->afterRemovingPasses = [[
9393
new CheckExceptionOnInvalidReferenceBehaviorPass(),
9494
new ResolveHotPathPass(),
95+
new AliasDeprecatedPublicServicesPass(),
9596
]];
9697
}
9798

+74Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
10+
11+
final class AliasDeprecatedPublicServicesPassTest extends TestCase
12+
{
13+
public function testProcess()
14+
{
15+
$container = new ContainerBuilder();
16+
$container
17+
->register('foo')
18+
->setPublic(true)
19+
->addTag('deprecated_public_service', ['package' => 'foo/bar', 'version' => '1.2']);
20+
21+
(new AliasDeprecatedPublicServicesPass())->process($container);
22+
23+
$this->assertTrue($container->hasAlias('foo'));
24+
25+
$alias = $container->getAlias('foo');
26+
27+
$this->assertSame('.deprecated_public_service.EVZD5I4', (string) $alias);
28+
$this->assertTrue($alias->isPublic());
29+
$this->assertFalse($alias->isPrivate());
30+
$this->assertSame([
31+
'package' => 'foo/bar',
32+
'version' => '1.2',
33+
'message' => 'The public "foo" service will be private in the future. You should not access it directly from the container at runtime.',
34+
], $alias->getDeprecation('foo'));
35+
}
36+
37+
/**
38+
* @dataProvider processWithMissingAttributeProvider
39+
*/
40+
public function testProcessWithMissingAttribute(string $attribute, array $attributes)
41+
{
42+
$this->expectException(InvalidArgumentException::class);
43+
$this->expectExceptionMessage(sprintf('The "%s" attribute is mandatory for the "deprecated_public_service" tag on the "foo" service.', $attribute));
44+
45+
$container = new ContainerBuilder();
46+
$container
47+
->register('foo')
48+
->addTag('deprecated_public_service', $attributes);
49+
50+
(new AliasDeprecatedPublicServicesPass())->process($container);
51+
}
52+
53+
public function processWithMissingAttributeProvider()
54+
{
55+
return [
56+
['package', ['version' => '1.2']],
57+
['version', ['package' => 'foo/bar']],
58+
];
59+
}
60+
61+
public function testProcessWithNonPublicService()
62+
{
63+
$this->expectException(RuntimeException::class);
64+
$this->expectExceptionMessage('The "foo" service is not public. Only public services can be deprecated with the "deprecated_public_service" tag.');
65+
66+
$container = new ContainerBuilder();
67+
$container
68+
->register('foo')
69+
->setPublic(false)
70+
->addTag('deprecated_public_service', ['package' => 'foo/bar', 'version' => '1.2']);
71+
72+
(new AliasDeprecatedPublicServicesPass())->process($container);
73+
}
74+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+42Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
use PHPUnit\Framework\TestCase;
1919
use Psr\Container\ContainerInterface as PsrContainerInterface;
20+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
2021
use Symfony\Component\Config\Resource\ComposerResource;
2122
use Symfony\Component\Config\Resource\DirectoryResource;
2223
use Symfony\Component\Config\Resource\FileResource;
@@ -31,6 +32,7 @@
3132
use Symfony\Component\DependencyInjection\ContainerBuilder;
3233
use Symfony\Component\DependencyInjection\ContainerInterface;
3334
use Symfony\Component\DependencyInjection\Definition;
35+
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
3436
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
3537
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
3638
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
@@ -50,6 +52,8 @@
5052

5153
class ContainerBuilderTest extends TestCase
5254
{
55+
use ExpectDeprecationTrait;
56+
5357
public function testDefaultRegisteredDefinitions()
5458
{
5559
$builder = new ContainerBuilder();
@@ -1633,6 +1637,44 @@ public function testAutoAliasing()
16331637

16341638
$this->assertInstanceOf(D::class, $container->get(X::class));
16351639
}
1640+
1641+
/**
1642+
* @group legacy
1643+
*/
1644+
public function testDirectlyAccessingDeprecatedPublicService()
1645+
{
1646+
$this->expectDeprecation('Since foo/bar 3.8: The public "Symfony\Component\DependencyInjection\Tests\A" service will be private in the future. You should not access it directly from the container at runtime.');
1647+
1648+
$container = new ContainerBuilder();
1649+
$container
1650+
->register(A::class)
1651+
->setPublic(true)
1652+
->addTag('deprecated_public_service', ['package' => 'foo/bar', 'version' => '3.8']);
1653+
1654+
$container->compile();
1655+
1656+
$container->get(A::class);
1657+
}
1658+
1659+
public function testReferencingDeprecatedPublicService()
1660+
{
1661+
$container = new ContainerBuilder();
1662+
$container
1663+
->register(A::class)
1664+
->setPublic(true)
1665+
->addTag('deprecated_public_service', ['package' => 'foo/bar', 'version' => '3.8']);
1666+
$container
1667+
->register(B::class)
1668+
->setPublic(true)
1669+
->addArgument(new Reference(A::class));
1670+
1671+
$container->compile();
1672+
1673+
// No deprecation should be triggered.
1674+
$container->get(B::class);
1675+
1676+
$this->addToAssertionCount(1);
1677+
}
16361678
}
16371679

16381680
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
@@ -1386,6 +1386,54 @@ public function testDumpServiceWithAbstractArgument()
13861386
$dumper = new PhpDumper($container);
13871387
$dumper->dump();
13881388
}
1389+
1390+
/**
1391+
* @group legacy
1392+
*/
1393+
public function testDirectlyAccessingDeprecatedPublicService()
1394+
{
1395+
$this->expectDeprecation('Since foo/bar 3.8: The public "bar" service will be private in the future. You should not access it directly from the container at runtime.');
1396+
1397+
$container = new ContainerBuilder();
1398+
$container
1399+
->register('bar', \BarClass::class)
1400+
->setPublic(true)
1401+
->addTag('deprecated_public_service', ['package' => 'foo/bar', 'version' => '3.8']);
1402+
1403+
$container->compile();
1404+
1405+
$dumper = new PhpDumper($container);
1406+
eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Directly_Accessing_Deprecated_Public_Service']));
1407+
1408+
$container = new \Symfony_DI_PhpDumper_Test_Directly_Accessing_Deprecated_Public_Service();
1409+
1410+
$container->get('bar');
1411+
}
1412+
1413+
public function testReferencingDeprecatedPublicService()
1414+
{
1415+
$container = new ContainerBuilder();
1416+
$container
1417+
->register('bar', \BarClass::class)
1418+
->setPublic(true)
1419+
->addTag('deprecated_public_service', ['package' => 'foo/bar', 'version' => '3.8']);
1420+
$container
1421+
->register('bar_user', \BarUserClass::class)
1422+
->setPublic(true)
1423+
->addArgument(new Reference('bar'));
1424+
1425+
$container->compile();
1426+
1427+
$dumper = new PhpDumper($container);
1428+
eval('?>'.$dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Referencing_Deprecated_Public_Service']));
1429+
1430+
$container = new \Symfony_DI_PhpDumper_Test_Referencing_Deprecated_Public_Service();
1431+
1432+
// No deprecation should be triggered.
1433+
$container->get('bar_user');
1434+
1435+
$this->addToAssertionCount(1);
1436+
}
13891437
}
13901438

13911439
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.