-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DependencyInjection] added a simple way to replace a service by keeping a reference to the old one #10600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DependencyInjection] added a simple way to replace a service by keeping a reference to the old one #10600
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?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\Alias; | ||
|
||
/** | ||
* Overwrites a service but keeps the overridden one. | ||
* | ||
* @author Christophe Coevoet <stof@notk.org> | ||
* @author Fabien Potencier <fabien@symfony.com> | ||
*/ | ||
class DecoratorServicePass implements CompilerPassInterface | ||
{ | ||
public function process(ContainerBuilder $container) | ||
{ | ||
foreach ($container->getDefinitions() as $id => $definition) { | ||
if (!$decorated = $definition->getDecoratedService()) { | ||
continue; | ||
} | ||
$definition->setDecoratedService(null); | ||
|
||
list ($inner, $renamedId) = $decorated; | ||
if (!$renamedId) { | ||
$renamedId = $id.'.inner'; | ||
} | ||
|
||
// we create a new alias/service for the service we are replacing | ||
// to be able to reference it in the new one | ||
if ($container->hasAlias($inner)) { | ||
$alias = $container->getAlias($inner); | ||
$public = $alias->isPublic(); | ||
$container->setAlias($renamedId, new Alias((string) $alias, false)); | ||
} else { | ||
$definition = $container->getDefinition($inner); | ||
$public = $definition->isPublic(); | ||
$definition->setPublic(false); | ||
$container->setDefinition($renamedId, $definition); | ||
} | ||
|
||
$container->setAlias($inner, new Alias($id, $public)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,7 @@ class Definition | |
private $abstract = false; | ||
private $synchronized = false; | ||
private $lazy = false; | ||
private $decoratedService; | ||
|
||
protected $arguments; | ||
|
||
|
@@ -100,6 +101,41 @@ public function setFactoryMethod($factoryMethod) | |
return $this; | ||
} | ||
|
||
/** | ||
* Sets the service that this service is decorating. | ||
* | ||
* @param null|string $id The decorated service id, use null to remove decoration | ||
* @param null|string $renamedId The new decorated service id | ||
* | ||
* @return Definition The current instance | ||
* | ||
* @throws InvalidArgumentException In case the decorated service id and the new decorated service id are equals. | ||
*/ | ||
public function setDecoratedService($id, $renamedId = null) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this method currently does not allow to reset it. We should find a way to do it (maybe passing |
||
{ | ||
if ($renamedId && $id == $renamedId) { | ||
throw new \InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); | ||
} | ||
|
||
if (null === $id) { | ||
$this->decoratedService = null; | ||
} else { | ||
$this->decoratedService = array($id, $renamedId); | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Gets the service that decorates this service. | ||
* | ||
* @return null|array An array composed of the decorated service id and the new id for it, null if no service is decorated | ||
*/ | ||
public function getDecoratedService() | ||
{ | ||
return $this->decoratedService; | ||
} | ||
|
||
/** | ||
* Gets the factory method. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -138,6 +138,13 @@ private function addService($definition, $id, \DOMElement $parent) | |
if ($definition->isLazy()) { | ||
$service->setAttribute('lazy', 'true'); | ||
} | ||
if (null !== $decorated = $definition->getDecoratedService()) { | ||
list ($decorated, $renamedId) = $decorated; | ||
$service->setAttribute('decorates', $decorated); | ||
if (null !== $renamedId) { | ||
$service->setAttribute('decoration-inner-name', $renamedId); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see an issue here: given that the compiler pass does not remove the decoration settings after resolving the decorators, dumping and reloading will break everything (circular reference as the service will decorate itself). I think the only way to make it work is to alter the definition to remove the decoration setting once it has been processed, to allow dumping a compiled container without issue There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, this should be done in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. humm, actually I don't get the issue: I'm trying this: require __DIR__ . '/vendor/autoload.php';
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Dumper\XmlDumper;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Config\FileLocator;
$file = __DIR__ . '/generated.xml';
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');
$container->compile();
$dumper = new XmlDumper($container);
file_put_contents($file, $dumper->dump());
$container2 = new ContainerBuilder();
$loader = new XmlFileLoader($container2, new FileLocator(__DIR__));
$loader->load($file);
$container2->compile(); with sevices.xml: <?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="decorated" class="stdClass" />
<service id="decorator_service" class="stdClass" decorates="decorated" />
<service id="decorator_service_with_name" class="stdClass" decorates="decorated" decoration-inner-name="decorated.pif-pouf"/>
</services>
</container> that generates: <?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="decorator_service" class="stdClass" decorates="decorated"/>
<service id="decorator_service_with_name" class="stdClass" decorates="decorated" decoration-inner-name="decorated.pif-pouf"/>
<service id="decorated" alias="decorator_service_with_name"/>
</services>
</container> There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use services which injects the inner service as argument (otherwise, using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. <?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="decorated" class="stdClass" />
<service id="decorator_service" class="stdClass" decorates="decorated">
<argument type="service" id="decorator_service.inner" />
</service>
<service id="decorator_service_with_name" class="stdClass" decorates="decorated" decoration-inner-name="decorated.pif-pouf">
<argument type="service" id="decorated.pif-pouf" />
</service>
</services>
</container> |
||
|
||
foreach ($definition->getTags() as $name => $tags) { | ||
foreach ($tags as $attributes) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<?php | ||
|
||
namespace Symfony\Component\DependencyInjection\Tests\Compiler; | ||
|
||
use Symfony\Component\DependencyInjection\Alias; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass; | ||
|
||
class DecoratorServicePassTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
public function testProcessWithoutAlias() | ||
{ | ||
$container = new ContainerBuilder(); | ||
$fooDefinition = $container | ||
->register('foo') | ||
->setPublic(false) | ||
; | ||
$fooExtendedDefinition = $container | ||
->register('foo.extended') | ||
->setPublic(true) | ||
->setDecoratedService('foo') | ||
; | ||
$barDefinition = $container | ||
->register('bar') | ||
->setPublic(true) | ||
; | ||
$barExtendedDefinition = $container | ||
->register('bar.extended') | ||
->setPublic(true) | ||
->setDecoratedService('bar', 'bar.yoo') | ||
; | ||
|
||
$this->process($container); | ||
|
||
$this->assertEquals('foo.extended', $container->getAlias('foo')); | ||
$this->assertFalse($container->getAlias('foo')->isPublic()); | ||
|
||
$this->assertEquals('bar.extended', $container->getAlias('bar')); | ||
$this->assertTrue($container->getAlias('bar')->isPublic()); | ||
|
||
$this->assertSame($fooDefinition, $container->getDefinition('foo.extended.inner')); | ||
$this->assertFalse($container->getDefinition('foo.extended.inner')->isPublic()); | ||
|
||
$this->assertSame($barDefinition, $container->getDefinition('bar.yoo')); | ||
$this->assertFalse($container->getDefinition('bar.yoo')->isPublic()); | ||
|
||
$this->assertNull($fooExtendedDefinition->getDecoratedService()); | ||
$this->assertNull($barExtendedDefinition->getDecoratedService()); | ||
} | ||
|
||
public function testProcessWithAlias() | ||
{ | ||
$container = new ContainerBuilder(); | ||
$container | ||
->register('foo') | ||
->setPublic(true) | ||
; | ||
$container->setAlias('foo.alias', new Alias('foo', false)); | ||
$fooExtendedDefinition = $container | ||
->register('foo.extended') | ||
->setPublic(true) | ||
->setDecoratedService('foo.alias') | ||
; | ||
|
||
$this->process($container); | ||
|
||
$this->assertEquals('foo.extended', $container->getAlias('foo.alias')); | ||
$this->assertFalse($container->getAlias('foo.alias')->isPublic()); | ||
|
||
$this->assertEquals('foo', $container->getAlias('foo.extended.inner')); | ||
$this->assertFalse($container->getAlias('foo.extended.inner')->isPublic()); | ||
|
||
$this->assertNull($fooExtendedDefinition->getDecoratedService()); | ||
} | ||
|
||
protected function process(ContainerBuilder $container) | ||
{ | ||
$repeatedPass = new DecoratorServicePass(); | ||
$repeatedPass->process($container); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
||
$container = new ContainerBuilder(); | ||
$container | ||
->register('foo', 'FooClass\\Foo') | ||
->setDecoratedService('bar', 'bar.woozy') | ||
; | ||
|
||
return $container; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
|
||
$container = new ContainerBuilder(); | ||
$container | ||
->register('foo', 'FooClass\\Foo') | ||
->setDecoratedService('bar') | ||
; | ||
|
||
return $container; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/symfony/symfony/pull/10600/files#r11152517 has been addressed here