Как декорировать сервисы

Как декорировать сервисы

При переопределении существующего определения (например, при применении Декоратора (шаблона проектирования)), первоначальный сервис теряется:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    services:
        app.mailer:
            class: AppBundle\Mailer
    
        # это заменяет старое определение app.mailer новым,
        # а старое определение теряется
        app.mailer:
            class: AppBundle\DecoratingMailer
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance"
        xsd:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="app.mailer" class="AppBundle\Mailer" />
    
            <!-- это заменяет старое определение app.mailer новым,
                 а старое определение теряется -->
            <service id="app.mailer" class="AppBundle\DecoratingMailer" />
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    use AppBundle\Mailer;
    use AppBundle\DecoratingMailer;
    
    $container->register('app.mailer', Mailer::class);
    
    // это заменяет старое определение app.mailer новым,
    // а старое определение теряется
    $container->register('app.mailer', DecoratingMailer::class);
    

В большинстве случаев это будет именно то, что вы будете хотеть сделать. Но иногда, вы можете захотеть вместо этого декорировать старый сервис, и оставить его, чтобы ссылаться а него:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    services:
        app.mailer:
            class: AppBundle\Mailer
    
        app.decorating_mailer:
            class:     AppBundle\DecoratingMailer
            # переопределяет сервис app.mailer
            # но этот сервис всё ещё доступен как app.decorating_mailer.inner
            decorates: app.mailer
    
            # передать старый сервис, как аргумент
            arguments: ['@app.decorating_mailer.inner']
    
            # приватный, так как обычно вам не нужно вызывать app.decorating_mailer напрямую
            public:    false
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance"
        xsd:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <service id="app.mailer" class="AppBundle\Mailer" />
    
            <service id="app.decorating_mailer"
                class="AppBundle\DecoratingMailer"
                decorates="app.mailer"
                public="false"
            >
                <argument type="service" id="app.decorating_mailer.inner" />
            </service>
    
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    use AppBundle\DecoratingMailer;
    use AppBundle\Mailer;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->register('app.mailer', Mailer::class);
    
    $container->register('app.decorating_mailer', DecoratingMailer::class)
        ->setDecoratedService('app.mailer')
        ->addArgument(new Reference('app.decorating_mailer.inner'))
        ->setPublic(false)
    ;
    

Опция decorates сообщает контейнеру, что сервис app.decorating_mailer заменяет сервис app.mailer. Старый сервис app.mailer``переименовывается на ``app.decorating_mailer.inner, чтобы вы могли внедрить его в ваш новый сервис.

Tip

Видимость (публичная) декорированного сервиса app.mailer (который явялется дополнительным именем нового сервиса), будет такой же, как и видимость первоначального app.mailer.

Note

Сгенерированный внутрений id осовывается на is сервиса декоратора (тут app.decorating_mailer), а не декорированном сервисе (тут app.mailer). Вы можете контролировать внутренний сервис через опцию decoration_inner_name:

  • YAML
    1
    2
    3
    4
    5
    services:
        app.decorating_mailer:
            # ...
            decoration_inner_name: app.decorating_mailer.wooz
            arguments: ['@app.decorating_mailer.wooz']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema-instance"
        xsd:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <services>
            <!-- ... -->
    
            <service
                id="app.decorating_mailer"
                class="AppBundle\DecoratingMailer"
                decorates="app.mailer"
                decoration-inner-name="app.decorating_mailer.wooz"
                public="false"
            >
                <argument type="service" id="app.decorating_mailer.wooz" />
            </service>
    
        </services>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    use AppBundle\DecoratingMailer;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->register('app.decorating_mailer', DecoratingMailer::class)
        ->setDecoratedService('app.mailer', 'app.decorating_mailer.wooz')
        ->addArgument(new Reference('app.decorating_mailer.wooz'))
        // ...
    ;
    

Приоритет декорирования

Если вы хотите применить к сервису более одного декоратора, вы можете контролировать их порядок, сконфигурировав приоритет декораций, это может быть любое целое число (декораторы с более высокими приоритетами будут применяться в первую очередь).

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    foo:
        class: Foo
    
    bar:
        class: Bar
        public: false
        decorates: foo
        decoration_priority: 5
        arguments: ['@bar.inner']
    
    baz:
        class: Baz
        public: false
        decorates: foo
        decoration_priority: 1
        arguments: ['@baz.inner']
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?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="foo" class="Foo" />
    
            <service id="bar" class="Bar" decorates="foo" decoration-priority="5" public="false">
                <argument type="service" id="bar.inner" />
            </service>
    
            <service id="baz" class="Baz" decorates="foo" decoration-priority="1" public="false">
                <argument type="service" id="baz.inner" />
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->register('foo', 'Foo')
    
    $container->register('bar', 'Bar')
        ->addArgument(new Reference('bar.inner'))
        ->setPublic(false)
        ->setDecoratedService('foo', null, 5);
    
    $container->register('baz', 'Baz')
        ->addArgument(new Reference('baz.inner'))
        ->setPublic(false)
        ->setDecoratedService('foo', null, 1);
    

Сгенерированный код будет следующим:

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.