Skip to content

Commit 8ced461

Browse files
committed
feature #10600 [DependencyInjection] added a simple way to replace a service by keeping a reference to the old one (romainneutron)
This PR was merged into the 2.5-dev branch. Discussion ---------- [DependencyInjection] added a simple way to replace a service by keeping a reference to the old one | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #5920 | License | MIT | Doc PR | symfony/symfony-docs#3745 This PR replaces #9003. Here's the todo list: - [x] add a cookbook entry for this new feature - [x] add support in all loaders/dumpers - [x] add unit tests - [x] see if there are use cases in Symfony that would benefit from this new feature - [x] find the best name for this feature I've implemented YAML and XML Loader / Dumper. From what I see, PhpDumper, PhpLoader, IniLoader, GraphvizDumper do not require an update, am I wrong? Commits ------- 140f807 [DependencyInjection] Update dumpers and loaders, add unit tests 1eb1f4d [DependencyInjection] added a simple way to replace a service by keeping a reference to the old one
2 parents 58bed5d + 140f807 commit 8ced461

24 files changed

+391
-2
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
2.5.0
5+
-----
6+
7+
* added DecoratorServicePass and a way to override a service definition (Definition::setDecoratedService())
8+
49
2.4.0
510
-----
611

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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\Alias;
16+
17+
/**
18+
* Overwrites a service but keeps the overridden one.
19+
*
20+
* @author Christophe Coevoet <stof@notk.org>
21+
* @author Fabien Potencier <fabien@symfony.com>
22+
*/
23+
class DecoratorServicePass implements CompilerPassInterface
24+
{
25+
public function process(ContainerBuilder $container)
26+
{
27+
foreach ($container->getDefinitions() as $id => $definition) {
28+
if (!$decorated = $definition->getDecoratedService()) {
29+
continue;
30+
}
31+
$definition->setDecoratedService(null);
32+
33+
list ($inner, $renamedId) = $decorated;
34+
if (!$renamedId) {
35+
$renamedId = $id.'.inner';
36+
}
37+
38+
// we create a new alias/service for the service we are replacing
39+
// to be able to reference it in the new one
40+
if ($container->hasAlias($inner)) {
41+
$alias = $container->getAlias($inner);
42+
$public = $alias->isPublic();
43+
$container->setAlias($renamedId, new Alias((string) $alias, false));
44+
} else {
45+
$definition = $container->getDefinition($inner);
46+
$public = $definition->isPublic();
47+
$definition->setPublic(false);
48+
$container->setDefinition($renamedId, $definition);
49+
}
50+
51+
$container->setAlias($inner, new Alias($id, $public));
52+
}
53+
}
54+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public function __construct()
4646

4747
$this->optimizationPasses = array(
4848
new ResolveDefinitionTemplatesPass(),
49+
new DecoratorServicePass(),
4950
new ResolveParameterPlaceHoldersPass(),
5051
new CheckDefinitionValidityPass(),
5152
new ResolveReferencesToAliasesPass(),

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Definition
3838
private $abstract = false;
3939
private $synchronized = false;
4040
private $lazy = false;
41+
private $decoratedService;
4142

4243
protected $arguments;
4344

@@ -100,6 +101,41 @@ public function setFactoryMethod($factoryMethod)
100101
return $this;
101102
}
102103

104+
/**
105+
* Sets the service that this service is decorating.
106+
*
107+
* @param null|string $id The decorated service id, use null to remove decoration
108+
* @param null|string $renamedId The new decorated service id
109+
*
110+
* @return Definition The current instance
111+
*
112+
* @throws InvalidArgumentException In case the decorated service id and the new decorated service id are equals.
113+
*/
114+
public function setDecoratedService($id, $renamedId = null)
115+
{
116+
if ($renamedId && $id == $renamedId) {
117+
throw new \InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id));
118+
}
119+
120+
if (null === $id) {
121+
$this->decoratedService = null;
122+
} else {
123+
$this->decoratedService = array($id, $renamedId);
124+
}
125+
126+
return $this;
127+
}
128+
129+
/**
130+
* Gets the service that decorates this service.
131+
*
132+
* @return null|array An array composed of the decorated service id and the new id for it, null if no service is decorated
133+
*/
134+
public function getDecoratedService()
135+
{
136+
return $this->decoratedService;
137+
}
138+
103139
/**
104140
* Gets the factory method.
105141
*

src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ private function addService($definition, $id, \DOMElement $parent)
138138
if ($definition->isLazy()) {
139139
$service->setAttribute('lazy', 'true');
140140
}
141+
if (null !== $decorated = $definition->getDecoratedService()) {
142+
list ($decorated, $renamedId) = $decorated;
143+
$service->setAttribute('decorates', $decorated);
144+
if (null !== $renamedId) {
145+
$service->setAttribute('decoration-inner-name', $renamedId);
146+
}
147+
}
141148

142149
foreach ($definition->getTags() as $name => $tags) {
143150
foreach ($tags as $attributes) {

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ private function addService($id, $definition)
139139
$code .= sprintf(" scope: %s\n", $scope);
140140
}
141141

142+
if (null !== $decorated = $definition->getDecoratedService()) {
143+
list ($decorated, $renamedId) = $decorated;
144+
$code .= sprintf(" decorates: %s\n", $decorated);
145+
if (null !== $renamedId) {
146+
$code .= sprintf(" decoration-inner-name: %s\n", $renamedId);
147+
}
148+
}
149+
142150
if ($callable = $definition->getConfigurator()) {
143151
if (is_array($callable)) {
144152
if ($callable[0] instanceof Reference) {

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ private function parseDefinition($id, $service, $file)
197197
$definition->addTag((string) $tag['name'], $parameters);
198198
}
199199

200+
if (isset($service['decorates'])) {
201+
$renameId = isset($service['decoration-inner-name']) ? (string) $service['decoration-inner-name'] : null;
202+
$definition->setDecoratedService((string) $service['decorates'], $renameId);
203+
}
204+
200205
$this->container->setDefinition($id, $definition);
201206
}
202207

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ private function parseDefinition($id, $service, $file)
234234
}
235235
}
236236

237+
if (isset($service['decorates'])) {
238+
$renameId = isset($service['decoration-inner-name']) ? $service['decoration-inner-name'] : null;
239+
$definition->setDecoratedService($service['decorates'], $renameId);
240+
}
241+
237242
$this->container->setDefinition($id, $definition);
238243
}
239244

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@
9494
<xsd:attribute name="factory-service" type="xsd:string" />
9595
<xsd:attribute name="alias" type="xsd:string" />
9696
<xsd:attribute name="parent" type="xsd:string" />
97+
<xsd:attribute name="decorates" type="xsd:string" />
98+
<xsd:attribute name="decoration-inner-name" type="xsd:string" />
9799
</xsd:complexType>
98100

99101
<xsd:complexType name="tag">
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Compiler;
4+
5+
use Symfony\Component\DependencyInjection\Alias;
6+
use Symfony\Component\DependencyInjection\ContainerBuilder;
7+
use Symfony\Component\DependencyInjection\Compiler\DecoratorServicePass;
8+
9+
class DecoratorServicePassTest extends \PHPUnit_Framework_TestCase
10+
{
11+
public function testProcessWithoutAlias()
12+
{
13+
$container = new ContainerBuilder();
14+
$fooDefinition = $container
15+
->register('foo')
16+
->setPublic(false)
17+
;
18+
$fooExtendedDefinition = $container
19+
->register('foo.extended')
20+
->setPublic(true)
21+
->setDecoratedService('foo')
22+
;
23+
$barDefinition = $container
24+
->register('bar')
25+
->setPublic(true)
26+
;
27+
$barExtendedDefinition = $container
28+
->register('bar.extended')
29+
->setPublic(true)
30+
->setDecoratedService('bar', 'bar.yoo')
31+
;
32+
33+
$this->process($container);
34+
35+
$this->assertEquals('foo.extended', $container->getAlias('foo'));
36+
$this->assertFalse($container->getAlias('foo')->isPublic());
37+
38+
$this->assertEquals('bar.extended', $container->getAlias('bar'));
39+
$this->assertTrue($container->getAlias('bar')->isPublic());
40+
41+
$this->assertSame($fooDefinition, $container->getDefinition('foo.extended.inner'));
42+
$this->assertFalse($container->getDefinition('foo.extended.inner')->isPublic());
43+
44+
$this->assertSame($barDefinition, $container->getDefinition('bar.yoo'));
45+
$this->assertFalse($container->getDefinition('bar.yoo')->isPublic());
46+
47+
$this->assertNull($fooExtendedDefinition->getDecoratedService());
48+
$this->assertNull($barExtendedDefinition->getDecoratedService());
49+
}
50+
51+
public function testProcessWithAlias()
52+
{
53+
$container = new ContainerBuilder();
54+
$container
55+
->register('foo')
56+
->setPublic(true)
57+
;
58+
$container->setAlias('foo.alias', new Alias('foo', false));
59+
$fooExtendedDefinition = $container
60+
->register('foo.extended')
61+
->setPublic(true)
62+
->setDecoratedService('foo.alias')
63+
;
64+
65+
$this->process($container);
66+
67+
$this->assertEquals('foo.extended', $container->getAlias('foo.alias'));
68+
$this->assertFalse($container->getAlias('foo.alias')->isPublic());
69+
70+
$this->assertEquals('foo', $container->getAlias('foo.extended.inner'));
71+
$this->assertFalse($container->getAlias('foo.extended.inner')->isPublic());
72+
73+
$this->assertNull($fooExtendedDefinition->getDecoratedService());
74+
}
75+
76+
protected function process(ContainerBuilder $container)
77+
{
78+
$repeatedPass = new DecoratorServicePass();
79+
$repeatedPass->process($container);
80+
}
81+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy