Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ac1e429

Browse files
[DI] add ReverseContainer: a locator that turns services back to their ids
1 parent 59e6380 commit ac1e429

File tree

7 files changed

+239
-0
lines changed

7 files changed

+239
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class UnusedTagsPass implements CompilerPassInterface
2626
'cache.pool.clearer',
2727
'console.command',
2828
'container.hot_path',
29+
'container.reversible',
2930
'container.service_locator',
3031
'container.service_subscriber',
3132
'controller.service_arguments',

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
3232
use Symfony\Component\Debug\ErrorHandler;
3333
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
34+
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
3435
use Symfony\Component\DependencyInjection\ContainerBuilder;
3536
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
3637
use Symfony\Component\Form\DependencyInjection\FormPass;
@@ -123,6 +124,8 @@ public function build(ContainerBuilder $container)
123124
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
124125
$this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class);
125126
$this->addCompilerPassIfExists($container, MessengerPass::class);
127+
$container->addCompilerPass(new RegisterReverseContainerPass(true));
128+
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
126129

127130
if ($container->getParameter('kernel.debug')) {
128131
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);

src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,11 @@
7373
</service>
7474

7575
<service id="services_resetter" class="Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter" public="true" />
76+
77+
<service id="reverse_container" class="Symfony\Component\DependencyInjection\ReverseContainer">
78+
<argument type="service" id="service_container" />
79+
<argument type="service_locator" />
80+
</service>
81+
<service id="Symfony\Component\DependencyInjection\ReverseContainer" alias="reverse_container" />
7682
</services>
7783
</container>

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%`
99
* added support for deprecating aliases
1010
* made `ContainerParametersResource` final and not implement `Serializable` anymore
11+
* added `ReverseContainer`: a container that turns services back to their ids
1112
* added ability to define an index for a tagged collection
1213

1314
4.2.0
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Argument\ServiceClosureArgument;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\ContainerInterface;
17+
use Symfony\Component\DependencyInjection\Definition;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
20+
/**
21+
* @author Nicolas Grekas <[email protected]>
22+
*/
23+
class RegisterReverseContainerPass implements CompilerPassInterface
24+
{
25+
private $beforeRemoving;
26+
private $serviceId;
27+
private $tagName;
28+
29+
public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible')
30+
{
31+
$this->beforeRemoving = $beforeRemoving;
32+
$this->serviceId = $serviceId;
33+
$this->tagName = $tagName;
34+
}
35+
36+
public function process(ContainerBuilder $container)
37+
{
38+
if (!$container->hasDefinition($this->serviceId)) {
39+
return;
40+
}
41+
42+
$refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
43+
$services = [];
44+
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
45+
$services[$id] = new Reference($id, $refType);
46+
}
47+
48+
if ($this->beforeRemoving) {
49+
// prevent inlining of the reverse container
50+
$services[$this->serviceId] = new Reference($this->serviceId, $refType);
51+
}
52+
$locator = $container->getDefinition($this->serviceId)->getArgument(1);
53+
54+
if ($locator instanceof Reference) {
55+
$locator = $container->getDefinition((string) $locator);
56+
}
57+
if ($locator instanceof Definition) {
58+
foreach ($services as $id => $ref) {
59+
$services[$id] = new ServiceClosureArgument($ref);
60+
}
61+
$locator->replaceArgument(0, $services);
62+
} else {
63+
$locator->setValues($services);
64+
}
65+
}
66+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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;
13+
14+
use Psr\Container\ContainerInterface;
15+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
16+
17+
/**
18+
* Turns public and "container.reversible" services back to their ids.
19+
*
20+
* @author Nicolas Grekas <[email protected]>
21+
*/
22+
final class ReverseContainer
23+
{
24+
private $serviceContainer;
25+
private $reversibleLocator;
26+
private $tagName;
27+
private $getServiceId;
28+
29+
public function __construct(Container $serviceContainer, ContainerInterface $reversibleLocator, string $tagName = 'container.reversible')
30+
{
31+
$this->serviceContainer = $serviceContainer;
32+
$this->reversibleLocator = $reversibleLocator;
33+
$this->tagName = $tagName;
34+
$this->getServiceId = \Closure::bind(function ($service): ?string {
35+
return array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null;
36+
}, $serviceContainer, Container::class);
37+
}
38+
39+
/**
40+
* Returns the id of the passed object when it exists as a service.
41+
*
42+
* To be reversible, services need to be either public or be tagged with "container.reversible".
43+
*
44+
* @param object $service
45+
*/
46+
public function getId($service): ?string
47+
{
48+
if ($this->serviceContainer === $service) {
49+
return 'service_container';
50+
}
51+
52+
if (null === $id = ($this->getServiceId)($service)) {
53+
return null;
54+
}
55+
56+
if ($this->serviceContainer->has($id) || $this->reversibleLocator->has($id)) {
57+
return $id;
58+
}
59+
60+
return null;
61+
}
62+
63+
/**
64+
* @return object
65+
*
66+
* @throws ServiceNotFoundException When the service is not reversible
67+
*/
68+
public function getService(string $id)
69+
{
70+
if ($this->serviceContainer->has($id)) {
71+
return $this->serviceContainer->get($id);
72+
}
73+
74+
if ($this->reversibleLocator->has($id)) {
75+
return $this->reversibleLocator->get($id);
76+
}
77+
78+
if (isset($this->serviceContainer->getRemovedIds()[$id])) {
79+
throw new ServiceNotFoundException($id, null, null, [], sprintf('The "%s" service is private and cannot be accessed by reference. You should either make it public, or tag it as "%s".', $id, $this->tagName));
80+
}
81+
82+
// will throw a ServiceNotFoundException
83+
$this->serviceContainer->get($id);
84+
}
85+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Compiler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
16+
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
17+
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\DependencyInjection\ReverseContainer;
21+
22+
class RegisterReverseContainerPassTest extends TestCase
23+
{
24+
public function testCompileRemovesUnusedServices()
25+
{
26+
$container = new ContainerBuilder();
27+
$container->register('foo', 'stdClass');
28+
$container->register('reverse_container', ReverseContainer::class)
29+
->addArgument(new Reference('service_container'))
30+
->addArgument(new ServiceLocatorArgument([]))
31+
->setPublic(true);
32+
33+
$container->addCompilerPass(new RegisterReverseContainerPass(true));
34+
$container->compile();
35+
36+
$this->assertFalse($container->has('foo'));
37+
}
38+
39+
public function testPublicServices()
40+
{
41+
$container = new ContainerBuilder();
42+
$container->register('foo', 'stdClass')->setPublic(true);
43+
$container->register('reverse_container', ReverseContainer::class)
44+
->addArgument(new Reference('service_container'))
45+
->addArgument(new ServiceLocatorArgument([]))
46+
->setPublic(true);
47+
48+
$container->addCompilerPass(new RegisterReverseContainerPass(true));
49+
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
50+
$container->compile();
51+
52+
$foo = $container->get('foo');
53+
54+
$this->assertSame('foo', $container->get('reverse_container')->getId($foo));
55+
$this->assertSame($foo, $container->get('reverse_container')->getService('foo'));
56+
}
57+
58+
public function testReversibleServices()
59+
{
60+
$container = new ContainerBuilder();
61+
$container->register('bar', 'stdClass')->setProperty('foo', new Reference('foo'))->setPublic(true);
62+
$container->register('foo', 'stdClass')->addTag('container.reversible');
63+
$container->register('reverse_container', ReverseContainer::class)
64+
->addArgument(new Reference('service_container'))
65+
->addArgument(new ServiceLocatorArgument([]))
66+
->setPublic(true);
67+
68+
$container->addCompilerPass(new RegisterReverseContainerPass(true));
69+
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
70+
$container->compile();
71+
72+
$foo = $container->get('bar')->foo;
73+
74+
$this->assertSame('foo', $container->get('reverse_container')->getId($foo));
75+
$this->assertSame($foo, $container->get('reverse_container')->getService('foo'));
76+
}
77+
}

0 commit comments

Comments
 (0)