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

Создание сервиса состоит из двух этапов — создание 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.

shared

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

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

synthetic

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

tags

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

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

Ссылки

🌱 Помогите нам сделать документацию лучше!

Вся документация Druki с отрытым исходным кодом. Нашли ошибку или неточность? Создайте pull request.

Редактировать текущий документ Обсудить улучшение

Или узнайте как контрибутить.

🤔 По-прежнему нужна помощь?

Не нашли ответа на свой вопрос? Попросите помощи у сообщества!

Задайте вопрос на GitHub Смотрите другие ресурсы сообщества