Symfony 2 Сервис-Контейнер | Service Container (RUS) Symfony 2 | МБ Блог

:

В современном PHP приложении полно объектов. Один объект может облегчить доставку email сообщений, в то время как другой может позволить Вам сохранять информацию в базе данных. В своём приложении Вы можете создать объект, управляющий каталогом товаров, или другой объект, который обрабатывает данные из внешнего API. Смысл в том, что современное приложение выполняет множество задач и подразделяется на множество объектов, выполняющих каждое задание.

В этой главе мы поговорим об особом PHP объекте в Symfony2, который помогает Вам подтверждать, создавать и извлекать многие другие объекты Вашего приложения. Объект этот, называемый сервис-контейнером, позволит Вам стандартизировать и централизировать логическую структуру приложения. Контейнер облегчит Вашу жизнь: он невероятно быстр и подчёркивает структуру, предлагающую раздельную и универсальную систему кодирования. А так как все основные Symfony2 классы используют этот контейнер, Вы научитесь создавать, настраивать и использовать любые объекты в Symfony2. По сути, сервис-контейнер оказывает большое влияние на быстродействие и расширяемость Symfony2.

И наконец, контейнер достаточно прост в настройке и использовании. К окончанию этой главы Вы сможете с лёгкостью создавать собственные объекты с помощью контейнера, а также настравивать объекты из любого внешнего пакета. Вы легко научитесь писать универсальный, удобнотестируемый и раздельный код, поскольку сервис-контейнер упрощает процедуру написания полноценного кода.

Что такое сервис?

Проще говоря, сервис – это какой-либо PHP объект, выполняющий, своего рода, “универсальную” задачу. Это конкретно-общее название используется в компьютерной науке для описания объекта, созданного с определённой целью (например, доставка электронной почты). Каждый сервис используется Вашим приложением по мере необходимости выполняемых им функций. Создание сервиса не представляет сложности: просто пропишите PHP класс с определённым кодом, выполняющим какую-либо определённую задачу. Поздравляем, Вы только что создали сервис!

Как правило, PHP объект является сервисом в том случае, если он используется повсеместно в Вашем приложении. Единый сервис Mailer используется универсально для рассылки электронных сообщений, в то время как множественные Message объекты, им рассылаемые, сервисами не являются. Аналогично, объект Product не является сервисом, однако объект, сохраняющий Product объекты в базе данных, является сервисом.

Преимущество размышлений о “сервисах” заключается в том, что Вы начинаете думать о разделении каждой функциональной части Вашего приложения на комплекс сервисов. Поскольку каждый сервис выполняет только одну задачу, Вы легко сможете получить доступ к каждому из них и использовать в случае необходимости. Ещё проще протестировать и настроить сервис, поскольку он отделён от от остальных функциональных возможностей Вашего приложения. Такой подход называется сервис-ориентированной архитектурой и не является уникальным для Symfony2 или PHP. Построение приложения, основываясь на комплексе независимых сервис-классов, является хорошо известной и проверенной объектно-ориентированной практики. Данные навыки являются ключевыми для хорошего разработчика практически на любом языке.

Что такое сервис-контейнер?

Сервис-контейнер (или контейнер инжекции зависимости) — это PHP объект, управляющий реализацией сервисов (т.е. объектов). Предположим, у нас есть простой PHP класс, отправляющий электронные сообщения. Без сервис-контейнера нам необходимо будет вручную создавать объект в случае надобности:

use Acme\HelloBundle\Mailer;

$mailer = new Mailer('sendmail');
$mailer->send('ryan@foobar.net', ... );

Это достаточно просто. Фиктивный класс Mailer позволяет нам сформировать метод доставки электронной почты (sendmail, smtp и т.д.). Но что если мы захотим использовать почтовый сервис где-нибудь ещё? Конечно же, мы не хотим повторять настройку “почтовика” каждый раз, когда нужно использовать объект Mailer. Что если везде в приложении нам понадобится изменить transport из sendmail на smtp? Тогда нам потребовалось бы отследить каждую позицию, в которой мы создали сервис Mailer и изменить её.

Создание/Настройка сервисов в контейнере

Лучший способ – это позволить сервис-контейнеру создать для Вас объект Mailer. Чтобы это сработало, нам нужно научить контейнер создавать сервис Mailer. Это делается с помощью настройки, которую можно задать в YAML, XML или PHP:

YAML:

# app/config/config.yml
services:
    my_mailer:
        class:        Acme\HelloBundle\Mailer
        arguments:    [sendmail]

XML:

<!-- app/config/config.xml -->
<services>
    <service id="my_mailer">
        <argument>sendmail</argument>
    </service>
</services>

PHP:

// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;

$container->setDefinition('my_mailer', new Definition(
    'Acme\HelloBundle\Mailer',
    array('sendmail')
));

Во время инициализации Symfony2 создаёт сервис-контейнер с помощью конфигурацию приложения (по умолчанию — app/config/config.yml). Точный загружаемый файл диктуется функцией AppKernel::registerContainerConfiguration(), которая загружает файл настройки для определённой среды (например, config_dev.yml для среды dev, или config_prod.yml для prod).

Теперь экземпляр Acme\HelloBundle\Mailer объекта доступен через сервис-контейнер. Доступ к самому контейнеру можно получить в любом традиционном Symfony2 контроллере, в котором Вы можете воспользоваться сервисами с помощью ускоренного метода get():

class HelloController extends Controller
{
    // ...

    public function sendEmailAction()
    {
        // ...
        $mailer = $this->get('my_mailer');
        $mailer->send('ryan@foobar.net', ... );
    }
}

Когда мы запрашиваем сервис my_mailer в контейнере, последний создаёт и выдаёт объект. Это ещё одно важное преимущество использования сервис-контейнера. А именно, сервис никогда не создаётся без надобности. Сервис, который Вы определяете сервис и никогда не используете в запросе, не создаётся. Это экономит объём памяти и увеличивает быстродействие Вашего приложения. Это также означает, что для определения множества сервисов существует очень мало хитов (либо их нет вообще).

В качестве добавочного бонуса, сервис Mailer создаётся однажды и этот же экземпляр выдаётся каждый раз при вызове сервиса. Это почти всегда то поведение, которое Вам и нужно (оно более гибкое и мощное), однако позднее мы научимся настраивать сервис с несколькими экземплярами.

Параметры сервиса

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

YAML:

# app/config/config.yml
parameters:
    my_mailer.class:      Acme\HelloBundle\Mailer
    my_mailer.transport:  sendmail

services:
    my_mailer:
        class:        %my_mailer.class%
        arguments:    [%my_mailer.transport%]

XML:

<!-- app/config/config.xml -->
<parameters>
    <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
    <parameter key="my_mailer.transport">sendmail</parameter>
</parameters>

<services>
    <service id="my_mailer">
        <argument>%my_mailer.transport%</argument>
    </service>
</services>

PHP:

// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;

$container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
$container->setParameter('my_mailer.transport', 'sendmail');

$container->setDefinition('my_mailer', new Definition(
    '%my_mailer.class%',
    array('%my_mailer.transport%')
));

Конечный результат такой же, как и раньше. Разница заключается только в способе определения сервиса. При заключении строк my_mailer.class и my_mailer.transport между процентными символами (%), контейнер узнаёт как искать параметры с данными именами. Когда контейнер создан, он выискивает значение каждого параметра и использует его в определении сервиса.

Цель параметров – передавать информацию сервисам. Конечно, определить сервис можно и без всяких параметров. Последние, однако, имеют ряд преимуществ:

  • разделение и упорядочивание всех “опций” сервиса в пределах одного ключа parameters;
  • значения параметра могут быть использованы для нескольких определений сервиса;
  • при создании сервиса в пакете (вскоре мы покажем как), параметры позволяют легко настроить сервис в приложении.

Использовать или не использовать параметры – решать Вам. Высококачественные внешние пакеты всегда будут использовать параметры, поскольку они позволяют сервисам храниться в контейнере более реконфигурируемыми. Хотя, для сервисов в Вашем приложении может и не понадобится гибкость параметров.

Импортирование других ресурсов настройки контейнера

В этом разделе мы будем считать файлы настройки сервиса ресурсами. Это для того, чтобы подчеркнуть тот факт, что, хотя большинство источников настройки будут файлами (например, YAML, XML, PHP), Symfony2 настолько гибка, что позволяет загружать настройки откуда угодно (например, база данных или даже посредством внешнего веб-сервиса).

Сервис-контейнер создан с использованием единственного источника настройки (по умолчанию: app/config/config.yml). Все остальные настройки сервиса (включая основные настройки Symfony2, а также настройки внешнего пакета) необходимо импортировать из этого файла тем или иным способом. Это даёт Вам абсолютную гибкость сервисов в приложении.

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

Импортирование настроек с помощью imports

Ранее мы разместили наше определение my_mailer непосредственно в настроечном файле приложения (например, app/config/config.yml). Естественно, поскольку класс Mailer расположен в AcmeHelloBundle, будет более логично поместить определение my_mailer в указанный пакет.

Во-первых, переместите определение my_mailer в новый ресурс-файл контейнера внутри AcmeHelloBundle. Если директории Resources или Resources/config не существуют, создайте их.

YAML:

# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
    my_mailer.class:      Acme\HelloBundle\Mailer
    my_mailer.transport:  sendmail

services:
    my_mailer:
        class:        %my_mailer.class%
        arguments:    [%my_mailer.transport%]

XML:

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<parameters>
    <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
    <parameter key="my_mailer.transport">sendmail</parameter>
</parameters>

<services>
    <service id="my_mailer">
        <argument>%my_mailer.transport%</argument>
    </service>
</services>

PHP:

// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;

$container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
$container->setParameter('my_mailer.transport', 'sendmail');

$container->setDefinition('my_mailer', new Definition(
    '%my_mailer.class%',
    array('%my_mailer.transport%')
));

Изменилось только расположение, но не само определение. Конечно же, сервис-контейнер не знает о новом ресурс-файле. К счастью, мы легко можем импортировать этот файл с помощью ключа imports в настройках приложения.

YAML:

# app/config/config.yml
imports:
    hello_bundle:
        resource: @AcmeHelloBundle/Resources/config/services.yml

XML:

<!-- app/config/config.xml -->
<imports>
    <import resource="@AcmeHelloBundle/Resources/config/services.xml"/>
</imports>

PHP:

// app/config/config.php
$this->import('@AcmeHelloBundle/Resources/config/services.php');

Директива imports позволяет Вашему приложению подключать конфигурационные ресурсы сервис-контейнера из других локаций (наиболее часто из пакетов). Для файлов локация resource является абсолютным путём к ресурс-файлу. Особый синтаксис @AcmeHello открывает прямой путь пакета AcmeHelloBundle. Это помогает Вам указать путь к ресурсу, не заботясь далее о перемещении AcmeHelloBundle в другую директорию.

Импортирование настроек с помощью дополнений контейнера

В процессе разработки с помощью Symfony2 наиболее часто Вы будете использовать директиву imports для импорта настроек контейнера onfiguration from the bundles you’ve created specifically for your application. Настройки из внешних пакетов, включая основные Symfony2 сервисы, обычно загружаются другим способом, более гибким и удобным для настройки в Вашем приложении.

Вот как это работает. Внутренне, каждый пакет проводит выраженное разделение сервисов. А именно, пакет использует один или более конфигурационный ресурс-файл (обычно XML) для определения параметров и сервисов для данного пакета. Однако, вместо импортирования каждого из этих ресурсов непосредственно из конфигурации приложения, используя директиву imports, Вы можете просто вызвать дополнение сервис-контейнера внутри пакета. Дополнение сервис-контейнера — это PHP класс, созданный автором пакета для выполнения двух функций:

  • импортирования всех ресурсов сервис-контейнера, необходимых для настройки сервиса для пакета;
  • обеспечения семантической, прямой конфигурации, чтобы пакет мог быть настроен без взаимодействия с линейными параметрами конфигурации сервис-контейнера.

Иными словами, дополнение сервис-контейнера настраивает сервисы для пакета от Вашего имени. И, как мы вскоре убедимся, дополнение предоставляет удобный, высокоуровневый интерфейс для настройки пакета.

Возьмём для примера основную оболочку Symfony2 – FrameworkBundle. Наличие следующего кода в конфигурации Вашего приложения вызывает дополнение сервис-контейнера внутри FrameworkBundle:

YAML:

# app/config/config.yml
framework:
    secret:          xxxxxxxxxx
    charset:         UTF-8
    form:            true
    csrf_protection: true
    router:        { resource: "%kernel.root_dir%/config/routing.yml" }
    # ...

XML:

<!-- app/config/config.xml -->
<parameters>
    <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
    <parameter key="my_mailer.transport">sendmail</parameter>
</parameters>

<services>
    <service id="my_mailer">
        <argument>%my_mailer.transport%</argument>
    </service>
</services>

PHP:

// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;

$container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
$container->setParameter('my_mailer.transport', 'sendmail');

$container->setDefinition('my_mailer', new Definition(
    '%my_mailer.class%',
    array('%my_mailer.transport%')
));

Во время обработки конфигурации контейнер ищет дополнение, способное выполнить директиву framework. Дополнение, о котором идёт речь, и которое находится в FrameworkBundle, запускается и загружается конфигурация сервиса для FrameworkBundle. Если Вы полностью удалите ключ framework из файла настройки Вашего приложения, основные сервисы Symfony2 не будут загружены. Дело в том, что всё находится по Вашим контролем. Оболочка Symfony2 не обладает волшебными свойствами и не выполняет действий, которые Вы не контролируете.

Конечно, Вы можете сделать гораздо больше, чем просто “активизировать” дополнение сервис-контейнера FrameworkBundle. Каждое дополнение позволяет Вам легко настроить пакет, не заботясь об определении внутренних сервисов.

В этом случае , дополнение позволяет Вам настроить charset, error_handler, csrf_protection, router и др. Внутренне FrameworkBundle использует опции, указанные здесь для определения и настройки сервисов, характерных для данной оболочки. Пакет создаёт все необходимые параметры и сервисы для сервис-контейнера, хотя и позволяет легко изменить многие настройки. В качестве добавочного бонуса, большинство дополнений сервис-контейнера также могут выполить проверку, уведомляя об отсутствующих опциях или о неправильных типах данных.

Во время установки или настройки пакета руководствуйтесь соответствующей документацией, объясняющей, как именно следует устанавливать или настраивать сервисы для пакета.

Изначально сервис-контейнер распознаёт только директивы parameters, services и imports. Другие директивы обрабатываются дополнениями сервис-контейнера.

Ссылки (инжектинг) на сервисы

До сих пор наш оригинальный сервис my_mailer был достаточно прост и понятен: он содержит только один легко настраиваемый параметр. Как Вы убедитесь далее, настоящий потенциал контейнера реализовывается тогда, когда Вам необходимо создать сервис, зависящий от одного или более других сервисов в контейнере.

Начнём с примера. Предположим, у нас есть новый сервис – NewsletterManager, который помогает управлять подготовкой и отправкой электронной почты на определённое количество адресов. Конечно же, сервис my_mailer хорошо зарекомендовал себя при доставке электронной почты, поэтому мы используем его в NewsletterManager для выполнения, собственно, доставки сообщений. Такой предполагаемый класс мог бы выглядеть следующим образом:

namespace Acme\HelloBundle\Newsletter;

use Acme\HelloBundle\Mailer;

class NewsletterManager
{
    protected $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    // ...
}

Не используя сервис-контейнер, мы достаточно легко можем создать NewsletterManager в контроллере:

public function sendNewsletterAction()
{
    $mailer = $this->get('my_mailer');
    $newsletter = new Acme\HelloBundle\Newsletter\NewsletterManager($mailer);
    // ...
}

Данный подход хорош, но что если мы решим добавить в класс NewsletterManager второй или третий параметр? Что если мы решили сделать рефакторинг кода или переименовать класс? В обоих случаях Вам бы понадобилось отыскивать и править все упоминания о NewsletterManager. Конечно же, сервис-контейнер предлагает нам гораздо больше заманчивых опций:

YAML:

# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
    # ...
    newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager

services:
    my_mailer:
        # ...
    newsletter_manager:
        class:     %newsletter_manager.class%
        arguments: [@my_mailer]

XML:

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<parameters>
    <!-- ... -->
    <parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</parameter>
</parameters>

<services>
    <service id="my_mailer" ... >
      <!-- ... -->
    </service>
    <service id="newsletter_manager">
        <argument type="service" id="my_mailer"/>
    </service>
</services>

PHP:

// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

// ...
$container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager');

$container->setDefinition('my_mailer', ... );
$container->setDefinition('newsletter_manager', new Definition(
    '%newsletter_manager.class%',
    array(new Reference('my_mailer'))
));

В YAML специальный синтаксис @my_mailer даёт указание контейнеру искать сервис под названием my_mailer и вставить указанный объект в конструкцию NewsletterManager. Однако, в данном случае должен существовать этот самый сервис my_mailer. Если он не создан, будет выдано сообщение об ошибке. Вы можете пометить соотношение как выборочное (об этом мы поговорим в следующем разделе).

Использование ссылок является очень мощным средством, позволяющим создавать независимые классы сервисов хорошо обозначенными соотношениями. В данном примере сервису newsletter_manager для полноценного функционирования необходим сервис my_mailer. Вам нужно определить взаимосвязь в сервис-контейнере, а он позаботится об инсталляции объектов.

Опциональные соотношения: инжекция механизма включения

Инжекция соотношений в конструкцию подобным образом – это отличный способ убедиться в том, что соотношение доступно для использования. Если для Вашего класса существуют опциональные соотношения, то “инжекция механизма включения” может быть лучшим решением. Это означает инжекцию соотношения с использованием метода вызова, а не посредством конструктора. Класс мог бы выглядеть следующим образом:

namespace Acme\HelloBundle\Newsletter;

use Acme\HelloBundle\Mailer;

class NewsletterManager
{
    protected $mailer;

    public function setMailer(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    // ...
}

Для инжекции соотношения путём включения необходимо всего лишь изменить синтаксис:

YAML:

# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
    # ...
    newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager

services:
    my_mailer:
        # ...
    newsletter_manager:
        class:     %newsletter_manager.class%
        calls:
            - [ setMailer, [ @my_mailer ] ]
XML:
<!-- src/Acme/HelloBundle/Resources/config/services official site.xml -->
<parameters>
    <!-- ... -->
    <parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</parameter>
</parameters>

<services>
    <service id="my_mailer" ... >
      <!-- ... -->
    </service>
    <service id="newsletter_manager">
        <call method="setMailer">
             <argument type="service" id="my_mailer" />
        </call>
    </service>
</services>
PHP:
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;

// ...
$container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager');

$container->setDefinition('my_mailer', ... );
$container->setDefinition('newsletter_manager', new Definition(
    '%newsletter_manager.class%'
))->addMethodCall('setMailer', array(
    new Reference('my_mailer')
));

Подходы, представленные в данном разделе, называют “конструкторная инжекция” и “инжекция ввода”. Сервис-контейнер Symfony2 также поддерживает “инжекцию свойств”.

Назначение опционального приоритета для ссылок

Иногда один из Ваших сервисов может иметь опциональную взаимосвязь, что означает: данная взаимосвязь не является обязательной для корректной работы сервиса. В приведённом ранее примере сервис my_mailer должен существовать, иначе будет выдано сообщение об ошибке. Изменив определение сервиса newsletter_manager, Вы можете сделать данную ссылку опциональной. В таком случае контейнер введёт её, если она существует и не будет производить действий, если она отсутствует:

YAML:

# src/Acme/HelloBundle/Resources/config/services.yml
parameters:
    # ...

services:
    newsletter_manager:
        class:     %newsletter_manager.class%
        arguments: [@?my_mailer]

XML:

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<services>
    <service id="my_mailer" ... >
      <!-- ... -->
    </service>
    <service id="newsletter_manager">
        <argument type="service" id="my_mailer" on-invalid="ignore" />
    </service>
</services>

PHP:

// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerInterface;

// ...
$container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager');

$container->setDefinition('my_mailer', ... );
$container->setDefinition('newsletter_manager', new Definition(
    '%newsletter_manager.class%',
    array(new Reference('my_mailer', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))
));

В YAML особый синтакс @? указывает сервис-контейнеру на то, взаимосвязь опциональная. Естественно, NewsletterManager должен быть прописан для этой взаимосвязи:

public function __construct(Mailer $mailer = null)
{
    // ...
}

Основные сервисы Symfony и внешних пакетов

Поскольку Symfony2, а также все внешние пакеты настраивают и получают сервисы с помощью контейнера, Вы легко можете получить к ним доступ или же использовать их в своих собственных сервисах. Из соображений простоты, Symfony2 не требует, чтобы контроллеры определялись сервисами по умолчанию. Более того, Symfony2 вводит целый сервис-контейнер в Ваш контроллер. Например, чтобы обеспечить хранение информации в сессии пользователя, Symfony2 предоставляет сервис session, к которому Вы можете получить доступ в стандартном контроллере следующим образом:

public function indexAction($bar)
{
    $session = $this->get('session');
    $session->set('foo', $bar);

    // ...
}

В Symfony2 Вы постоянно используете сервисы, предоставленные ядром Symfony или другими внешними пакетами для выполнения таких задач, как визуализация шаблонов (templating), отправка электронной почты (mailer), или доступ к информации по запросу (request).

Мы можем шагнуть дальше – использовать эти сервисы внутри сервисов, созданных для Вашего приложения. Давайте модифицируем NewsletterManager для использования настоящего Symfony2 сервиса mailer (вместо предполагаемого my_mailer). Давайте также пропустим сервис визуализации шаблонов в NewsletterManager, чтобы он мог генерировать содержание электронного письма по шаблону:

namespace Acme\HelloBundle\Newsletter;
use Symfony\Component\Templating\EngineInterface;

class NewsletterManager
{
    protected $mailer;

    protected $templating;

    public function __construct(\Swift_Mailer $mailer, EngineInterface $templating)
    {
        $this->mailer = $mailer;
        $this->templating = $templating;
    }

    // ...
}

Настроить сервис-контейнер просто:

YAML:

services:
    newsletter_manager:
        class:     %newsletter_manager.class%
        arguments: [@mailer, @templating]

XML:

<service id="newsletter_manager">
    <argument type="service" id="mailer"/>
    <argument type="service" id="templating"/>
</service>

PHP:

$container->setDefinition('newsletter_manager', new Definition(
    '%newsletter_manager.class%',
    array(
        new Reference('mailer'),
        new Reference('templating')
    )
));

Теперь сервис The newsletter_manager имеет доступ к ядру mailer и templating сервисов. Это обычный способ создания сервисов непосредственно для Вашего приложения, который использует различные сервисы в оболочке.

Убедитесь, что компонент swiftmailer появился в настройке Вашего приложения. Как уже упоминалось ранее, ключ swiftmailer вызывает объект сервиса из SwiftmailerBundle, который регистрирует сервис mailer.

Расширенные настройки контейнера

Как мы уже знаем, можно довольно просто определить сервисы внутри контейнера, используя конфигурационный ключ service и несколько параметров. Тем не менее, контейнер некоторыми другими доступными средствами, помогающими маркировать сервисы для определённых функций, создавать более сложные сервисы, а также выполнять действия после создания контейнера.

Сервисы public/private

При определении сервиса Вы, обычно, желаете получить доступ к этим определениям внутри кода приложения. Такие сервисы называются public. Например, сервис doctrine, зарегистрированный при использовании DoctrineBundle, является public сервисом, поскольку Вы можете получить к нему доступ с помощью:

$doctrine = $container->get('doctrine');

Однако, бывают случаи, когда Вы не желаете делать сервис общедоступным. Обычно, это происходит тогда, когда сервис определяется только во время использования его в качестве параметра для другого сервиса.

Проще говоря, сервис будет приватным в том случае, когда Вы не хотите получить доступ к нему напрямую из кода.

Пример:

YAML:

services:
   foo:
     class: Acme\HelloBundle\Foo
     public: false

XML:

<service id="foo" public="false" />

PHP:

$definition = new Definition('Acme\HelloBundle\Foo');
$definition->setPublic(false);
$container->setDefinition('foo', $definition);

Теперь, когда сервис стал приватным, Вы не можете вызвать:

$container->get('foo');

Однако, когда сервис становится приватным, Вы всё ещё можете переименовать его, чтобы получить доступ к нему.

Использование псевдонимов

Используя основные или внешние пакеты всвоём приложении, Вы можете использовать ярлыки для доступа к некоторым сервисам. Для этого Вы можете назначить альтернативные имена для этих сервисов. Кроме того, Вы даже можете назначить альтернативные имена для закрытых сервисов.

YAML:

services:
   foo:
     class: Acme\HelloBundle\Foo
   bar:
     alias: foo

XML:

<service id="foo"/>

<service id="bar" alias="foo" />

PHP:

$definition = new Definition('Acme\HelloBundle\Foo');
$container->setDefinition('foo', $definition);

$containerBuilder->setAlias('bar', 'foo');

Это значит, что, используя контейнер напрямую, Вы можете получить доступ к сервису foo, запрашивая сервис bar:

$container->get('bar'); // Would return the foo service

Запрашивание файлов

В некоторых случаях Вам может потребоваться ввести файл непосредственно перед загрузкой сервиса. Для этого Вы можете использовать директиву file.

YAML:

services:
   foo:
     class: Acme\HelloBundle\Foo\Bar
     file: %kernel.root_dir%/src/path/to/file/foo.php

XML:

<service id="foo">
    <file>%kernel.root_dir%/src/path/to/file/foo.php</file>
</service>

PHP:

$definition = new Definition('Acme\HelloBundle\Foo\Bar');
$definition->setFile('%kernel.root_dir%/src/path/to/file/foo.php');
$container->setDefinition('foo', $definition);

Обратите внимание, Symfony будет производить внутренний вызов PHP функции require_once, а это означает, что Ваш файл будет включён только один раз для каждого запроса.

Тэги (tags)

В сервис-контейнере тэг подразумевает, что сервис предназначен для определённых целей. Рассмотрим следующий пример:

YAML:

services:
    foo.twig.extension:
        class: Acme\HelloBundle\Extension\FooExtension
        tags:
            -  { name: twig.extension }

XML:

<service id="foo.twig.extension">
    <tag name="twig.extension" />
</service>

PHP:

$definition = new Definition('Acme\HelloBundle\Extension\FooExtension');
$definition->addTag('twig.extension');
$container->setDefinition('foo.twig.extension', $definition);

twig.extension – это специальный тэг, который используется в TwigBundle во время настройки. Если пометить сервис twig.extension тэгом, пакет зарегистрирует foo.twig.extension сервис как дополнение Twig. Иными словами, Twig находит все сервисы, помеченные тэгом twig.extension и автоматически регистрирует их в качестве дополнений.

Кроме того, тэги дают указание Symfony2 или другому внешнему пакету на то, что Ваш сервис должен быть зарегистрирован или использован каким-либо особым способом.

Следующие тэги доступны в основных пакетах Symfony2. Каждый из них оказывает определённое воздействие на Ваш сервис, а для многих тэгов требуются дополнительные параметры (сразу после параметра name).

  • assetic.templating.php
  • data_collector
  • form.field_factory.guesser
  • kernel.cache_warmer
  • kernel.event_listener
  • monolog.logger
  • routing.loader
  • security.listener.factory
  • security.voter
  • templating.helper
  • twig.extension
  • translation.loader
  • validator.constraint_validator

Ссылка на оригинальную статью: Service Container