[DI] add ReverseContainer: a locator that turns services back to their ids
This commit is contained in:
parent
59e63805a3
commit
ac1e4291e8
@ -26,6 +26,7 @@ class UnusedTagsPass implements CompilerPassInterface
|
||||
'cache.pool.clearer',
|
||||
'console.command',
|
||||
'container.hot_path',
|
||||
'container.reversible',
|
||||
'container.service_locator',
|
||||
'container.service_subscriber',
|
||||
'controller.service_arguments',
|
||||
|
@ -31,6 +31,7 @@ use Symfony\Component\Config\Resource\ClassExistenceResource;
|
||||
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
|
||||
use Symfony\Component\Debug\ErrorHandler;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass;
|
||||
use Symfony\Component\Form\DependencyInjection\FormPass;
|
||||
@ -123,6 +124,8 @@ class FrameworkBundle extends Bundle
|
||||
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$this->addCompilerPassIfExists($container, AddMimeTypeGuesserPass::class);
|
||||
$this->addCompilerPassIfExists($container, MessengerPass::class);
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(true));
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
|
||||
|
||||
if ($container->getParameter('kernel.debug')) {
|
||||
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
||||
|
@ -73,5 +73,11 @@
|
||||
</service>
|
||||
|
||||
<service id="services_resetter" class="Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter" public="true" />
|
||||
|
||||
<service id="reverse_container" class="Symfony\Component\DependencyInjection\ReverseContainer">
|
||||
<argument type="service" id="service_container" />
|
||||
<argument type="service_locator" />
|
||||
</service>
|
||||
<service id="Symfony\Component\DependencyInjection\ReverseContainer" alias="reverse_container" />
|
||||
</services>
|
||||
</container>
|
||||
|
@ -8,6 +8,7 @@ CHANGELOG
|
||||
* added `%env(default:param_name:...)%` processor to fallback to a parameter or to null when using `%env(default::...)%`
|
||||
* added support for deprecating aliases
|
||||
* made `ContainerParametersResource` final and not implement `Serializable` anymore
|
||||
* added `ReverseContainer`: a container that turns services back to their ids
|
||||
* added ability to define an index for a tagged collection
|
||||
|
||||
4.2.0
|
||||
|
@ -0,0 +1,66 @@
|
||||
<?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\Argument\ServiceClosureArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class RegisterReverseContainerPass implements CompilerPassInterface
|
||||
{
|
||||
private $beforeRemoving;
|
||||
private $serviceId;
|
||||
private $tagName;
|
||||
|
||||
public function __construct(bool $beforeRemoving, string $serviceId = 'reverse_container', string $tagName = 'container.reversible')
|
||||
{
|
||||
$this->beforeRemoving = $beforeRemoving;
|
||||
$this->serviceId = $serviceId;
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition($this->serviceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$refType = $this->beforeRemoving ? ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
|
||||
$services = [];
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
$services[$id] = new Reference($id, $refType);
|
||||
}
|
||||
|
||||
if ($this->beforeRemoving) {
|
||||
// prevent inlining of the reverse container
|
||||
$services[$this->serviceId] = new Reference($this->serviceId, $refType);
|
||||
}
|
||||
$locator = $container->getDefinition($this->serviceId)->getArgument(1);
|
||||
|
||||
if ($locator instanceof Reference) {
|
||||
$locator = $container->getDefinition((string) $locator);
|
||||
}
|
||||
if ($locator instanceof Definition) {
|
||||
foreach ($services as $id => $ref) {
|
||||
$services[$id] = new ServiceClosureArgument($ref);
|
||||
}
|
||||
$locator->replaceArgument(0, $services);
|
||||
} else {
|
||||
$locator->setValues($services);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
<?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;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
|
||||
|
||||
/**
|
||||
* Turns public and "container.reversible" services back to their ids.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class ReverseContainer
|
||||
{
|
||||
private $serviceContainer;
|
||||
private $reversibleLocator;
|
||||
private $tagName;
|
||||
private $getServiceId;
|
||||
|
||||
public function __construct(Container $serviceContainer, ContainerInterface $reversibleLocator, string $tagName = 'container.reversible')
|
||||
{
|
||||
$this->serviceContainer = $serviceContainer;
|
||||
$this->reversibleLocator = $reversibleLocator;
|
||||
$this->tagName = $tagName;
|
||||
$this->getServiceId = \Closure::bind(function ($service): ?string {
|
||||
return array_search($service, $this->services, true) ?: array_search($service, $this->privates, true) ?: null;
|
||||
}, $serviceContainer, Container::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the passed object when it exists as a service.
|
||||
*
|
||||
* To be reversible, services need to be either public or be tagged with "container.reversible".
|
||||
*
|
||||
* @param object $service
|
||||
*/
|
||||
public function getId($service): ?string
|
||||
{
|
||||
if ($this->serviceContainer === $service) {
|
||||
return 'service_container';
|
||||
}
|
||||
|
||||
if (null === $id = ($this->getServiceId)($service)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->serviceContainer->has($id) || $this->reversibleLocator->has($id)) {
|
||||
return $id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
*
|
||||
* @throws ServiceNotFoundException When the service is not reversible
|
||||
*/
|
||||
public function getService(string $id)
|
||||
{
|
||||
if ($this->serviceContainer->has($id)) {
|
||||
return $this->serviceContainer->get($id);
|
||||
}
|
||||
|
||||
if ($this->reversibleLocator->has($id)) {
|
||||
return $this->reversibleLocator->get($id);
|
||||
}
|
||||
|
||||
if (isset($this->serviceContainer->getRemovedIds()[$id])) {
|
||||
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));
|
||||
}
|
||||
|
||||
// will throw a ServiceNotFoundException
|
||||
$this->serviceContainer->get($id);
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
<?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\Tests\Compiler;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ReverseContainer;
|
||||
|
||||
class RegisterReverseContainerPassTest extends TestCase
|
||||
{
|
||||
public function testCompileRemovesUnusedServices()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'stdClass');
|
||||
$container->register('reverse_container', ReverseContainer::class)
|
||||
->addArgument(new Reference('service_container'))
|
||||
->addArgument(new ServiceLocatorArgument([]))
|
||||
->setPublic(true);
|
||||
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(true));
|
||||
$container->compile();
|
||||
|
||||
$this->assertFalse($container->has('foo'));
|
||||
}
|
||||
|
||||
public function testPublicServices()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'stdClass')->setPublic(true);
|
||||
$container->register('reverse_container', ReverseContainer::class)
|
||||
->addArgument(new Reference('service_container'))
|
||||
->addArgument(new ServiceLocatorArgument([]))
|
||||
->setPublic(true);
|
||||
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(true));
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$container->compile();
|
||||
|
||||
$foo = $container->get('foo');
|
||||
|
||||
$this->assertSame('foo', $container->get('reverse_container')->getId($foo));
|
||||
$this->assertSame($foo, $container->get('reverse_container')->getService('foo'));
|
||||
}
|
||||
|
||||
public function testReversibleServices()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('bar', 'stdClass')->setProperty('foo', new Reference('foo'))->setPublic(true);
|
||||
$container->register('foo', 'stdClass')->addTag('container.reversible');
|
||||
$container->register('reverse_container', ReverseContainer::class)
|
||||
->addArgument(new Reference('service_container'))
|
||||
->addArgument(new ServiceLocatorArgument([]))
|
||||
->setPublic(true);
|
||||
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(true));
|
||||
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
|
||||
$container->compile();
|
||||
|
||||
$foo = $container->get('bar')->foo;
|
||||
|
||||
$this->assertSame('foo', $container->get('reverse_container')->getId($foo));
|
||||
$this->assertSame($foo, $container->get('reverse_container')->getService('foo'));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user