Drupal → Использование сервисов в контроллерах и формах

27.12.2013

Сервис (service, служба) в Drupal 8 это объект, который выполняет какую-то одну глобальную задачу, например сервис работы с базой данных или сервис рассылки писем. Сервисы это часть Symfony 2 и паттерна Service Container (он же Dependency Injection Container), позволяющий сделать код менее связанным а также, как написал @andypost "чтобы можно было заменить контрибом сервис например на более производительный и ничего не поломать".

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

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

namespace Drupal\mymodule\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Guzzle\Http\ClientInterface;

class MymoduleController extends ControllerBase {
  /**
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;
  
  /**
   * @var \Guzzle\Http\ClientInterface
   */
  protected $httpClient;
  
  /**
   * @param \Drupal\Core\Database\Connection $database
   * @param \Guzzle\Http\ClientInterface $http_client
   */
  public function __construct(Connection $database, ClientInterface $http_client) {
    $this->database = $database;
    $this->httpClient = $http_client;
  }
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('database'),
      $container->get('http_client')
    );
  }

  /**
   * Page callback.
   */
  public function myAction() {
    $this->database->query('...');
    $this->httpClient->get('...')->send();
  }
}

Наследование контроллера от ControllerBase позволяет без лишнего кода пользоваться сервисами кэша ($this->cache()), конфига ($this->config()), url-генератора ($this->urlGenerator()) и некоторыми другими.

В формах по аналогии:

namespace Drupal\mymodule\Form;

use Drupal\Core\Form\FormBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Guzzle\Http\ClientInterface;

class MyForm extends FormBase {
  /**
   * @var \Drupal\Core\Database\Connection
   */
  protected $database;
  
  /**
   * @var \Guzzle\Http\ClientInterface
   */
  protected $httpClient;
  
  /**
   * @param \Drupal\Core\Database\Connection $database
   * @param \Guzzle\Http\ClientInterface $http_client
   */
  public function __construct(Connection $database, ClientInterface $http_client) {
    $this->database = $database;
    $this->httpClient = $http_client;
  }
  
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('database'),
      $container->get('http_client')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, array &$form_state) {
    $this->database->query('...');
    $this->httpClient->get('...')->send();
  }
}

Если код не планируется размещать в паблике и писать для него тесты, то можно пользоваться процедурными врапперами из класса \Drupal:

namespace Drupal\mymodule\Controller;

use Drupal\Core\Controller\ControllerBase;

class MymoduleController extends ControllerBase {
  /**
   * Page callback.
   */
  public function myAction() {
    \Drupal::database()->query('...');
    \Drupal::httpClient()->get('...')->send();
  }
}

Но по возможности рекомендуется всегда пользоваться методом выше. Цитата из официальной документации:

The global Drupal class is to be used within global functions, however, Drupal 8's base approach revolves around classes in the form of controllers, plugins, and so on. The best practice for these is not to call out to the global service container and instead pass in the required services as arguments to a constructor or inject the needed services via service setter methods. Once again using Drupal:: in a class method is advised against!

Написанное актуально для
Drupal 8
Похожие записи

Комментарии

Павел
02.02.2016, 20:01

К сожалению такой способ вызова объекта базы данных не работает в Drupal 8.0.2. В официальной документации от 15 июля 2015 г. https://www.drupal.org/node/2133171 приводится способ как раз с использованием враппера

<?php
// Returns a Drupal\Core\Database\Connection object.
$connection = \Drupal::database();
$result = $connection->select('node', 'n')
  ->fields('n', array('nid'))
  ->execute();
?>

Если все делать как в статье $this->database остается пустым при создании экземпляра класса.

Павел
02.02.2016, 20:28

Работает, но только внутри этого класса. А в экземпляре не работает. Есть мысли почему? Если я хочу обработчик формы вынести в отдельный класс.

Павел
03.02.2016, 18:58

В общем действительно все работает! Проблема решается довольно просто, особенно это очевидно тем, кто знает ООП. Решение: при описании службы в module.servises.yml необходимо обязательно указать аргумент - arguments: ['@database']

  site_kadry_courses.test:
    class: Drupal\site_kadry_courses\CoursesTest
    arguments: ['@database']

Именно благодаря этому в конструктор класса будет передана ссылка на активное соединение с базой данных. Вот сюда. И тогда переменная connection не пустая.

  public function __construct(Connection $connection) {
    $this->connection = $connection;
  }

Добавить комментарий