feature #15416 [DependencyInjection] Added a way to define the priority of service decoration (dosten)

This PR was merged into the 2.8 branch.

Discussion
----------

[DependencyInjection] Added a way to define the priority of service decoration

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #10634
| License       | MIT
| Doc PR        | symfony/symfony-docs#5600

This PR adds a way to define the priority of service decoration, so, the service with the highest priority will be applied first (the default priority is zero).

```yaml
services:
    foo:
        class: Foo

    bar:
        class: Bar
        arguments: ['@bar.inner']
        decorates: foo
        public: false

    foobar:
        class: Foobar
        arguments: ['@foobar.inner']
        decorates: foo
        decoration_priority: 1
        public: false
```

This will result in this code:

```php
$this->services['foo'] = new Bar(new Foobar(new Foo)));
```

Commits
-------

75c98cb Added a way to define the priority of service decoration
This commit is contained in:
Fabien Potencier 2015-08-05 17:50:26 +02:00
commit a00687f43a
16 changed files with 94 additions and 17 deletions

View File

@ -8,6 +8,7 @@ CHANGELOG
* deprecated the concept of scopes
* added `Definition::setShared()` and `Definition::isShared()`
* added ResettableContainerInterface to be able to reset the container to release memory on shutdown
* added a way to define the priority of service decoration
2.7.0
-----

View File

@ -19,18 +19,28 @@ use Symfony\Component\DependencyInjection\Alias;
*
* @author Christophe Coevoet <stof@notk.org>
* @author Fabien Potencier <fabien@symfony.com>
* @author Diego Saint Esteben <diego@saintesteben.me>
*/
class DecoratorServicePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definitions = new \SplPriorityQueue();
$order = PHP_INT_MAX;
foreach ($container->getDefinitions() as $id => $definition) {
if (!$decorated = $definition->getDecoratedService()) {
continue;
}
$definitions->insert(array($id, $definition), array($decorated[2], --$order));
}
foreach ($definitions as $arr) {
list($id, $definition) = $arr;
list($inner, $renamedId) = $definition->getDecoratedService();
$definition->setDecoratedService(null);
list($inner, $renamedId) = $decorated;
if (!$renamedId) {
$renamedId = $id.'.inner';
}

View File

@ -150,12 +150,13 @@ class Definition
*
* @param null|string $id The decorated service id, use null to remove decoration
* @param null|string $renamedId The new decorated service id
* @param int $priority The priority of decoration
*
* @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)
public function setDecoratedService($id, $renamedId = null, $priority = 0)
{
if ($renamedId && $id == $renamedId) {
throw new \InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id));
@ -164,7 +165,7 @@ class Definition
if (null === $id) {
$this->decoratedService = null;
} else {
$this->decoratedService = array($id, $renamedId);
$this->decoratedService = array($id, $renamedId, (int) $priority);
}
return $this;
@ -173,7 +174,7 @@ class Definition
/**
* 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
* @return null|array An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated
*/
public function getDecoratedService()
{

View File

@ -173,11 +173,11 @@ class DefinitionDecorator extends Definition
/**
* {@inheritdoc}
*/
public function setDecoratedService($id, $renamedId = null)
public function setDecoratedService($id, $renamedId = null, $priority = 0)
{
$this->changes['decorated_service'] = true;
return parent::setDecoratedService($id, $renamedId);
return parent::setDecoratedService($id, $renamedId, $priority);
}
/**

View File

@ -149,11 +149,14 @@ class XmlDumper extends Dumper
$service->setAttribute('lazy', 'true');
}
if (null !== $decorated = $definition->getDecoratedService()) {
list($decorated, $renamedId) = $decorated;
list($decorated, $renamedId, $priority) = $decorated;
$service->setAttribute('decorates', $decorated);
if (null !== $renamedId) {
$service->setAttribute('decoration-inner-name', $renamedId);
}
if (0 !== $priority) {
$service->setAttribute('decoration-priority', $priority);
}
}
foreach ($definition->getTags() as $name => $tags) {

View File

@ -141,11 +141,14 @@ class YamlDumper extends Dumper
}
if (null !== $decorated = $definition->getDecoratedService()) {
list($decorated, $renamedId) = $decorated;
list($decorated, $renamedId, $priority) = $decorated;
$code .= sprintf(" decorates: %s\n", $decorated);
if (null !== $renamedId) {
$code .= sprintf(" decoration_inner_name: %s\n", $renamedId);
}
if (0 !== $priority) {
$code .= sprintf(" decoration_priority: %s\n", $priority);
}
}
if ($callable = $definition->getFactory()) {

View File

@ -245,7 +245,8 @@ class XmlFileLoader extends FileLoader
if ($value = $service->getAttribute('decorates')) {
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
$definition->setDecoratedService($value, $renameId);
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
$definition->setDecoratedService($value, $renameId, $priority);
}
return $definition;

View File

@ -290,7 +290,8 @@ class YamlFileLoader extends FileLoader
if (isset($service['decorates'])) {
$renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
$definition->setDecoratedService($service['decorates'], $renameId);
$priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0;
$definition->setDecoratedService($service['decorates'], $renameId, $priority);
}
$this->container->setDefinition($id, $definition);

View File

@ -101,6 +101,7 @@
<xsd:attribute name="parent" type="xsd:string" />
<xsd:attribute name="decorates" type="xsd:string" />
<xsd:attribute name="decoration-inner-name" type="xsd:string" />
<xsd:attribute name="decoration-priority" type="xsd:integer" />
</xsd:complexType>
<xsd:complexType name="tag">

View File

@ -73,6 +73,48 @@ class DecoratorServicePassTest extends \PHPUnit_Framework_TestCase
$this->assertNull($fooExtendedDefinition->getDecoratedService());
}
public function testProcessWithPriority()
{
$container = new ContainerBuilder();
$fooDefinition = $container
->register('foo')
->setPublic(false)
;
$barDefinition = $container
->register('bar')
->setPublic(true)
->setDecoratedService('foo')
;
$bazDefinition = $container
->register('baz')
->setPublic(true)
->setDecoratedService('foo', null, 5)
;
$quxDefinition = $container
->register('qux')
->setPublic(true)
->setDecoratedService('foo', null, 3)
;
$this->process($container);
$this->assertEquals('bar', $container->getAlias('foo'));
$this->assertFalse($container->getAlias('foo')->isPublic());
$this->assertSame($fooDefinition, $container->getDefinition('baz.inner'));
$this->assertFalse($container->getDefinition('baz.inner')->isPublic());
$this->assertEquals('qux', $container->getAlias('bar.inner'));
$this->assertFalse($container->getAlias('bar.inner')->isPublic());
$this->assertEquals('baz', $container->getAlias('qux.inner'));
$this->assertFalse($container->getAlias('qux.inner')->isPublic());
$this->assertNull($barDefinition->getDecoratedService());
$this->assertNull($bazDefinition->getDecoratedService());
$this->assertNull($quxDefinition->getDecoratedService());
}
protected function process(ContainerBuilder $container)
{
$repeatedPass = new DecoratorServicePass();

View File

@ -241,7 +241,7 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase
->setDecoratedService('foo', 'foo_inner')
;
$this->assertEquals(array('foo', 'foo_inner'), $container->getDefinition('child1')->getDecoratedService());
$this->assertEquals(array('foo', 'foo_inner', 0), $container->getDefinition('child1')->getDecoratedService());
}
protected function process(ContainerBuilder $container)

View File

@ -56,16 +56,23 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
public function testSetGetDecoratedService()
{
$def = new Definition('stdClass');
$this->assertNull($def->getDecoratedService());
$def->setDecoratedService('foo', 'foo.renamed', 5);
$this->assertEquals(array('foo', 'foo.renamed', 5), $def->getDecoratedService());
$def->setDecoratedService(null);
$this->assertNull($def->getDecoratedService());
$def = new Definition('stdClass');
$this->assertNull($def->getDecoratedService());
$def->setDecoratedService('foo', 'foo.renamed');
$this->assertEquals(array('foo', 'foo.renamed'), $def->getDecoratedService());
$this->assertEquals(array('foo', 'foo.renamed', 0), $def->getDecoratedService());
$def->setDecoratedService(null);
$this->assertNull($def->getDecoratedService());
$def = new Definition('stdClass');
$def->setDecoratedService('foo');
$this->assertEquals(array('foo', null), $def->getDecoratedService());
$this->assertEquals(array('foo', null, 0), $def->getDecoratedService());
$def->setDecoratedService(null);
$this->assertNull($def->getDecoratedService());

View File

@ -47,6 +47,7 @@
<service id="another_alias_for_foo" alias="foo" public="false" />
<service id="decorator_service" decorates="decorated" />
<service id="decorator_service_with_name" decorates="decorated" decoration-inner-name="decorated.pif-pouf"/>
<service id="decorator_service_with_name_and_priority" decorates="decorated" decoration-inner-name="decorated.pif-pouf" decoration-priority="5"/>
<service id="new_factory1" class="FooBarClass">
<factory function="factory" />
</service>

View File

@ -26,6 +26,10 @@ services:
decorator_service_with_name:
decorates: decorated
decoration_inner_name: decorated.pif-pouf
decorator_service_with_name_and_priority:
decorates: decorated
decoration_inner_name: decorated.pif-pouf
decoration_priority: 5
new_factory1: { class: FooBarClass, factory: factory}
new_factory2: { class: FooBarClass, factory: [@baz, getClass]}
new_factory3: { class: FooBarClass, factory: [BazClass, getInstance]}

View File

@ -242,8 +242,9 @@ class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foo', (string) $aliases['another_alias_for_foo']);
$this->assertFalse($aliases['another_alias_for_foo']->isPublic());
$this->assertEquals(array('decorated', null), $services['decorator_service']->getDecoratedService());
$this->assertEquals(array('decorated', 'decorated.pif-pouf'), $services['decorator_service_with_name']->getDecoratedService());
$this->assertEquals(array('decorated', null, 0), $services['decorator_service']->getDecoratedService());
$this->assertEquals(array('decorated', 'decorated.pif-pouf', 0), $services['decorator_service_with_name']->getDecoratedService());
$this->assertEquals(array('decorated', 'decorated.pif-pouf', 5), $services['decorator_service_with_name_and_priority']->getDecoratedService());
}
public function testParsesTags()

View File

@ -171,8 +171,9 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foo', (string) $aliases['another_alias_for_foo']);
$this->assertFalse($aliases['another_alias_for_foo']->isPublic());
$this->assertEquals(array('decorated', null), $services['decorator_service']->getDecoratedService());
$this->assertEquals(array('decorated', 'decorated.pif-pouf'), $services['decorator_service_with_name']->getDecoratedService());
$this->assertEquals(array('decorated', null, 0), $services['decorator_service']->getDecoratedService());
$this->assertEquals(array('decorated', 'decorated.pif-pouf', 0), $services['decorator_service_with_name']->getDecoratedService());
$this->assertEquals(array('decorated', 'decorated.pif-pouf', 5), $services['decorator_service_with_name_and_priority']->getDecoratedService());
}
public function testLoadFactoryShortSyntax()