in Профессиональное

Symfony2: доступ к одной службе из другой

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

Сегодняшний сниппет посвящается службам (сервисам), а именно – как получить доступ из пользовательской службы к другой службе в рамках приложения.

Положим у нас в нашем пакете есть служба Company\SuperBundle\Service\CoolService описываемая следующей конфигурацией:

# app/config/config.yml
services:
    giveMeCool:
        class: Company\SuperBundle\Service\CoolService
        arguments: [%arg1%, %arg2%]

Все идет хорошо, пока нам не требуется другая служба. Например мне понадобился logger. Вообще говоря это есть в документации, но пришлось повозиться пока я это нашел: другую службу можно указать в аргументах своего сервиса через @svcname:

# app/config/config.yml
services:
    giveMeCool:
        class: Company\SuperBundle\Service\CoolService
        arguments: [%arg1%, %arg2%, @logger]

Соответственно код конструктора службы надо немного модифицировать:

# src/Company/SuperBundle/Service/CoolService.php
  //...
  // services
  protected $logger = null;
 
  public function __construct( $arg1, $arg2, \Symfony\Bundle\ZendBundle\Logger\Logger $logger)
  {
    $this->logger = $logger;
    //...

Тип сервиса можно указать явно (как в примере), тогда мы сможем как минимум пользоваться подсказками нашей IDE (что в Symfony2 получается не часто).

Update

Читатель Костег в комментариях предложил более общее решение для данного кейса: можно передавать в пользовательскую службу не отдельные службы, а весь service container разом (мало ли когда и какая из служб вам потребуется):

# app/config/config.yml
services:
    giveMeCool:
        class: Company\SuperBundle\Service\CoolService
        arguments: [%arg1%, %arg2%, @service_container]

Теперь в классе вашей службы вы сможете получить доступ к любой из служб, определенных в рамках вашего приложения: templating, logger, request, routing etc…

# src/Company/SuperBundle/Service/CoolService.php
  //...
  // services
  protected $serviceContainer = null;
 
  public function __construct( $arg1, $arg2, \Symfony\Component\DependencyInjection\Container $container)
  {
    $this->serviceContainer = $container;
    $this->serviceContainer->get('routing');
    //...

По-моему это уже выглядит достаточно удобно – во всяком случае к основным критическим данным служба сможет получить доступ без лишних извращений над кодом 🙂

Write a Comment

Comment

*

20 Comments

  1. смотря что там за сервисы и сколько их там нужно. Возможно стоит вообще передавать весь ContainerInterface?)

    Расскрой плизз тему Controllers as a services & Interface injection. А то я что-то не до конца понимаю что там как и зачем

    • Меня и перечисление атрибутов в таком виде не устраивает, не говоря уж о службах. Вот представь, 5 параметров для инициализации facebook, плюс логгер мне понадобился… уже 6. И это я еще не разошелся )))

      Но весь контейнер я не знаю как туда передать разумным образом.

        • Что-то не понял, если я определяю класс как сервис, то service container там всегда доступен (примерно $this->get(‘service_container’))?

          Что касается facebook то я имел в виду следующее:

          services:
              facebook:
                  arguments: [%app_id%, %api_key%, %app_secret%, %canvas%, %site%, @logger]

          Аргументов много. В контроллере же $this->get(‘facebook’) – это удобно.

          • Не, там он не будет доступен через $this->get(), этого метода вообще может и не быть в сервисе. Смотри конструктор в классе Container. Через конфигурацию инъектить можно


            # app/config/config.yml
            services:
            giveMeCool:
            class: Company\SuperBundle\Service\CoolService
            arguments: [@service_container]

            Facebook – да, я это и имел в виду.

        • Нет, фейсбук мне кажется как раз в виде сервиса и хорош. Я с тем же успехом new Facebook() мог делать в контроллере каждый раз – но это ж не айс )

          За хинт спасибо ) В принципе это общий случай @сервис. Интересно, а список всех служб есть?

          P.S. полезно вести блог, что-то новое узнаешь… ))))

    • Костя, спасибо, написал update к посту. Если дашь ссылку на тебя, прилинкую к твоему нику в тексте.

      • Да незачто. Я без сайта, линковать нечего).

        Вот только похоже трабл есть:

        Теперь в классе вашей службы вы сможете получить доступ к любой из служб, определенных в рамках вашего приложения: templating, logger, request, response, routing etc…

        Насколько я знаю, Response удалили из ServiceContainer.

        зы: неплохо было б превью к комменариям добвить

        • Да, ты прав, response нет. Это я раньше из статьи про контроллер почерпнул. Надо бы погуглить насчет актуального списка служб.

  2. @service_container – можно подробнее про эту конструкцию, как фрэйм понимает что и где искать?

  3. Это я знаю, вопрос был немного в другом. Мы в конфиге можем ссылаться на сервисы которые уже объявляли , а тут этот сервис уже объявлен где то в др. месте, наверно в конфеге бандла к которому он относится. Если я правильно понимаю, то конфиги бандлов на этапе инициализации приложения собираются и тогда мы получаем доступ к сервису любого бандла.

    P.S. Кстати было бы не плохо написать статью по архитектуре симфони и процессу инициализации/запуска приложения, диспетчеризации и т.д.

    • Вообще говоря я свои сервисы определял в общем конфиге (app/config) – как в примерах описано. Возможно чтобы инициировалась служба, определенная в пакете, его конфигурацию нужно импортировать, как к примеру конфигурацию маршрутизатора. Надо попробовать.

  4. Попробую это вариант, книгу еще до этого момента не дочитал.
    Решить задачу получилось

    <argument type="service" id="service_container"></argument>
  5. Игорь, у меня получается вставить ) Пришли код на мыло, я вставлю в твой коммент