Создание сервиса

Создание сервиса состоит из двух этапов — создание PHP объекта, а также, объявление данного объекта в *.services.yml файле модуля, который данный объект и предоставляет.

Создание сервиса

Для примера возьмем следующий объект (modules/custom/mymodule/src/MyObject.php).

<?php

namespace Drupal\mymodule;

/**
 * Provides my special service.
 */
class MyObject {

  /**
   * Gets hello world message.
   *
   * @return string
   *   The message.
   */
  protected function helloWorld() {
    return 'Hello World!';
  }

}

Данный объект не имеет никаких зависимостей, это просто PHP объект, который можно вызывать и напрямую.

Для того чтобы превратить данный объект в сервис, необходимо добавить информацию о нём (зарегистрировать) в mymodule.services.yml.

services:
  mymodule.awesome_service:
    class: Drupal\mymodule\MyObject

Таким образом, мы создали сервис mymodule.awesome_service, при обращении, к которому, мы получим экземпляр объекта MyObject и сможем вызывать его методы.

Структура *.services.yml

Пример example.services.yml:

# Раздел, в котором можно зарегистрировать статичные параметры, которые можно
# использовать для передачи в качестве аргументов сервису.
parameters:
  example.parameter: 'Hello World!'

# Раздел, в котором объявляются сервисы.
services:
  # Простой пример, регистрирующий объект в качестве сервиса.
  example.simple:
    class: Drupal\example\Simple

  # Пример регистрации объекта в качестве сервиса, с передачей в качестве
  # аргументов другого сервиса и параметра.
  example.complex:
    class: Drupal\example\Complex
    arguments: ['@example.simple', '%example.parameter%']

Файл *.services.yml состоит из двух разделов, parameters и services. Оба раздела являются опциональными, вы сами решаете что вам нужно и описывать в соответствии со структурой.

Раздел parameters отвечает за объявление статичных переменных, которые могут быть переданы в сервисы в качестве аргументов. Это необходимо в тех случаях, когда ваш сервис требует какое-то значение по умолчанию, но вы хотите предусмотреть возможность переопределения данного значения сторонними разработчиками. Так как при вызове сервиса, разработчики не могут передавать никаких аргументов и входных данных, это позволяет решить данный недостаток.

Раздел services отвечает за непосредственное объявление сервисов. В данном разделе вы описываете название сервиса, а также его свойства.

Свойства сервисов

abstract

Позволяет объявить сервис в качестве абстрактного сервиса. Это означает, что данные сервис должен использовать как parent у других сервисов. При значении true, сервис станет абстрактным, при значение false (по умолчанию), сервис будет инициализирован.

Все свойства данного сервиса, будут применены на сервисы, указавшие текущий в качестве parent.

alias

Позволяет задать синоним сервису.

Например:

services:
  example.simple:
    class: Drupal\example\Simple
    alias: foo_bar

Из примера выше, сервис можно будет вызвать как по example.simple, так и по foo_bar.

Объявление сервиса также можно сделать в другом формате.

services:
  example.simple:
    class: Drupal\example\Simple

  foo_bar: '@example.simple'

Это аналогичная запись первому варианту, отличие лишь в том, что второй способ позволяет задать алиас для стороннего сервиса, тогда как первый, задается непосредственно при объявлении сервиса.

arguments

Аргументы — массив аргументов, которые будут переданы либо непосредственно в класс class сервиса, либо в его фабрику factory.

Аргументы могут быть:

  • Другим сервисом. Для этого название сервиса, который необходимо передать как аргумент, записывается с префиксом @. Например @service.to.inject.
  • Параметром из parameters раздела. Переменные объявленные в данном разделе, являются глобальным, таким образом, вам доступны параметры объявленные в других *.services.yml файлах. Для передачи параметра в качестве аргумента, его название оборачивается в %. Например %example.parameter%.
  • Примитивным типом данных поддерживаемым YAML: строкой, числом, логическим значением. Например 'just string'.

calls

Позволяет задать метод сервиса, который будет вызван после успешной инициализации объекта данного сервиса. Вы также можете передавать аргументы для данного метода.

<?php

namespace Drupal\mymodule;

/**
 * Provides my special service.
 */
class MyObject {

  /**
   * The foo object.
   *
   * @var Drupal\foo\FooInterface
   */
  protected $foo;

  /**
   * Sets foo object.
   *
   * @param Drupal\foo\FooInterface $foo
   *   The foo object.
   */
  protected function setFoo(FooInterface $foo) {
    $this->foo = $foo;
  }

}
services:
  my_service:
    class: Drupal\mymodule\MyObject
    calls:
      - [setFoo, ['@foo.service']]

Вы можете указывать сколько угодно вызовов.

class

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

configurator

configuratior - это сервис с методом, которые будут вызваны после успешной инициализации объекта.

services:
  my_service:
    class: Drupal\mymodule\MyObject
    configurator: ['@my_service_configurator', configure]

В примере выше, будет вызван метод ::configure() у сервиса my_service_configurator.

То что возвращает данные метод, будет передано в качестве конструктора текущего сервиса.

decorates, decoration_priority и decoration_inner_name

Свойство decorated позволяет заменить существующий сервис своим собственным. Таким образом, вы можете переопределять сервисы, не хакая их исходный код.

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

Замененные сервисы получают название [origina_service_name].inner по умолчанию. Вы можете влиять на название при помощи decoration_inner_name.

services:
  example.simple:
    class: Drupal\example\Simple

  example.complex:
    class: Drupal\example\Complex
    decorates: example.simple
    arguments: ['@example.simple.inner']

Таким образом, при вызове сервиса example.simple будет вызван example.complex, без необходимости менять код, где использовался старый сервис.

Если для одного сервиса объявляется несколько декораторов, то в данном случае может помочь свойство decoration_priority, которое позволяет контролировать, в каком порядке будут вызваны декораторы. Декораторы вызываются в порядке убывания приоритета.

Например:

services:
  example.first:
    class: Drupal\example\First

  example.second:
    class: Drupal\example\Second
    decorates: example.first
    decoration_priority: 1
    arguments: ['@example.simple.inner']

  example.third:
    class: Drupal\example\Third
    decorates: example.first
    decoration_priority: 5
    arguments: ['@example.simple.inner']

Таким образом, примерный вызов будет выглядеть следующим образом:

$service = new Second(new Third(new First()));

factory

В factory указывается объект, который будет вызван для инициализации объекта сервиса. Иными словами, в нём описывается то, как именно будет создаваться экземпляр объекта сервиса с полным контролем и поведением.

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

parent

parent позволяет объявить сервис, в качестве наследника от другого сервиса со свойством abstract. Данный сервис унаследует всей свойства своего родителя, кроме shared, abstract и tags.

services:
  example.first:
    abstract: true
    arguments: ['@foo', '@bar']

  
  example.second:
    parent: example.first

  example.third:
    parent: example.first
    # Данный аргумент будет добавлен в качестве третьего.
    arguments: ['@baz']

properties

Позволяет внедрять зависимости непосредственно в свойства объекта.

<?php

namespace Drupal\mymodule;

/**
 * Provides my special service.
 */
class MyObject {

  /**
   * The foo object.
   *
   * @var Drupal\foo\FooInterface
   */
  protected $foo;

}
services:
  my_service:
    class: Drupal\mymodule\MyObject
    properties:
      foo: '@foo'

public

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

Публичные сервисы можно вызывать из контейнера, как это обычно и происходит. В случае с приватными сервисами (public: false), они могут быть вызваны только в качестве Dependency Injection.

Заметка

В Symfony считается хорошей практикой, помечать как можно больше сервисов приватными. В Drupal, на данный момент, не везде есть возможность применять Dependency Injection как положено, в связи с этим, используйте данную особенность только когда вы уверены, что данный сервис никогда не будет вызван из контейнера напрямую.

shared

Позволяет определить, как долго будет жить экземпляр объекта данного сервиса:

  • true: (по умолчанию) Экземпляр объекта создаётся только при первом обращении к сервису. Все последующие обращения возвращают уже инициализированный экземпляр объекта.
  • false: Каждое обращение к сервису, будет инициализировать новый экземпляр объекта.

synthetic

При помощи свойства synthetic со значением true, вы можете указать, что данный сервис внедряется в сам Service Container, а не создается им.

tags

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

Подобные сервисы собираются так называемыми сервис коллекторами (сборщиками), они обнаруживают все сервисы с необходимыми им метками и инициализируют их с какой-то конкретной целью.

Ссылки

Помощь и обратная связь

Если вы обнаружили ошибку или хотите внести улучшения, и желаете внести изменения самостоятельно при помощи Pull Request
Если вы желаете предложить улучшение для этого документа

Обратиться за помощью

Если вы не нашли то что искали, воспользуйтесь поиском.

Если вам нужна помощь с чем-то конкретным, обратитесь к сообществу.