Skip to content

[DependencyInjection] Document stack definitions #17295

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

Merged
merged 1 commit into from
Oct 2, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 235 additions & 14 deletions service_container/service_decoration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,18 @@ the ``decoration_priority`` option. Its value is an integer that defaults to
.. code-block:: yaml

# config/services.yaml
Foo: ~
services:
Foo: ~

Bar:
decorates: Foo
decoration_priority: 5
arguments: ['@.inner']
Bar:
decorates: Foo
decoration_priority: 5
arguments: ['@.inner']

Baz:
decorates: Foo
decoration_priority: 1
arguments: ['@.inner']
Baz:
decorates: Foo
decoration_priority: 1
arguments: ['@.inner']

.. code-block:: xml

Expand Down Expand Up @@ -300,14 +301,14 @@ the ``decoration_priority`` option. Its value is an integer that defaults to
return function(ContainerConfigurator $configurator) {
$services = $configurator->services();

$services->set(Foo::class);
$services->set(\Foo::class);

$services->set(Bar::class)
->decorate(Foo::class, null, 5)
$services->set(\Bar::class)
->decorate(\Foo::class, null, 5)
->args([service('.inner')]);

$services->set(Baz::class)
->decorate(Foo::class, null, 1)
$services->set(\Baz::class)
->decorate(\Foo::class, null, 1)
->args([service('.inner')]);
};

Expand All @@ -316,6 +317,226 @@ The generated code will be the following::

$this->services[Foo::class] = new Baz(new Bar(new Foo()));

Stacking Decorators
-------------------

An alternative to using decoration priorities is to create a ``stack`` of
ordered services, each one decorating the next:

.. configuration-block::

.. code-block:: yaml

# config/services.yaml
services:
decorated_foo_stack:
stack:
- class: Baz
arguments: ['@.inner']
- class: Bar
arguments: ['@.inner']
- class: Foo

# using the short syntax:
decorated_foo_stack:
stack:
- Baz: ['@.inner']
- Bar: ['@.inner']
- Foo: ~

# can be simplified when autowiring is enabled:
decorated_foo_stack:
stack:
- Baz: ~
- Bar: ~
- Foo: ~

.. code-block:: xml

<!-- config/services.xml -->
<?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
https://symfony.com/schema/dic/services/services-1.0.xsd"
>
<services>
<stack id="decorated_foo_stack">
<service class="Baz">
<argument type="service" id=".inner"/>
</service>
<service class="Bar">
<argument type="service" id=".inner"/>
</service>
<service class="Foo"/>
</stack>

<!-- can be simplified when autowiring is enabled: -->
<stack id="decorated_foo_stack">
<service class="Baz"/>
<service class="Bar"/>
<service class="Foo"/>
</stack>
</services>
</container>

.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return function(ContainerConfigurator $container) {
$container->services()
->stack('decorated_foo_stack', [
inline_service(\Baz::class)->args([service('.inner')]),
inline_service(\Bar::class)->args([service('.inner')]),
inline_service(\Foo::class),
])

// can be simplified when autowiring is enabled:
->stack('decorated_foo_stack', [
inline_service(\Baz::class),
inline_service(\Bar::class),
inline_service(\Foo::class),
])
;
};

The result will be the same as in the previous section::

$this->services['decorated_foo_stack'] = new Baz(new Bar(new Foo()));

Like aliases, a ``stack`` can only use ``public`` and ``deprecated`` attributes.

Each frame of the ``stack`` can be either an inlined service, a reference or a
child definition.
The latter allows embedding ``stack`` definitions into each others, here's an
advanced example of composition:

.. configuration-block::

.. code-block:: yaml

# config/services.yaml
services:
some_decorator:
class: App\Decorator

embedded_stack:
stack:
- alias: some_decorator
- App\Decorated: ~

decorated_foo_stack:
stack:
- parent: embedded_stack
- Baz: ~
- Bar: ~
- Foo: ~

.. code-block:: xml

<!-- config/services.xml -->
<?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
https://symfony.com/schema/dic/services/services-1.0.xsd"
>
<services>
<service id="some_decorator" class="App\Decorator"/>

<stack id="embedded_stack">
<service alias="some_decorator"/>
<service class="App\Decorated"/>
</stack>

<stack id="decorated_foo_stack">
<service parent="embedded_stack"/>
<service class="Baz"/>
<service class="Bar"/>
<service class="Foo"/>
</stack>
</services>
</container>

.. code-block:: php

// config/services.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\Decorated;
use App\Decorator;

return function(ContainerConfigurator $container) {
$container->services()
->set('some_decorator', Decorator::class)

->stack('embedded_stack', [
service('some_decorator'),
inline_service(Decorated::class),
])

->stack('decorated_foo_stack', [
inline_service()->parent('embedded_stack'),
inline_service(\Baz::class),
inline_service(\Bar::class),
inline_service(\Foo::class),
])
;
};

The result will be::

$this->services['decorated_foo_stack'] = new App\Decorator(new App\Decorated(new Baz(new Bar(new Foo()))));

.. note::

To change existing stacks (i.e. from a compiler pass), you can access each
frame by its generated id with the following structure:
``.stack_id.frame_key``.
From the example above, ``.decorated_foo_stack.1`` would be a reference to
the inlined ``Baz`` service and ``.decorated_foo_stack.0`` to the embedded
stack.
To get more explicit ids, you can give a name to each frame:

.. configuration-block::

.. code-block:: yaml

# ...
decorated_foo_stack:
stack:
first:
parent: embedded_stack
second:
Baz: ~
# ...

.. code-block:: xml

<!-- ... -->
<stack id="decorated_foo_stack">
<service id="first" parent="embedded_stack"/>
<service id="second" class="Baz"/>
<!-- ... -->
</stack>

.. code-block:: php

// ...
->stack('decorated_foo_stack', [
'first' => inline_service()->parent('embedded_stack'),
'second' => inline_service(\Baz::class),
// ...
])

The ``Baz`` frame id will now be ``.decorated_foo_stack.second``.

.. versionadded:: 5.1

The ability to define ``stack`` was introduced in Symfony 5.1.

Control the Behavior When the Decorated Service Does Not Exist
--------------------------------------------------------------

Expand Down