четверг, 11 июля 2013 г.

Контроллеры маршрутизации в Drupal8 - Using Drupal 8's new route controllers

Частично Web Services и Context в ядре инициатив традиционные процедуры обратного вызова страниц были сконвертированы в новые обьективные ориентированные контроллеры маршрутов.
Зачем нам понадобился новый компонент Routing?

В предыдущих версиях Drupal существовал только hook_menu. Ловушка меню управляла только несколькими вещами. Обрабатывая входные запросы предостовляла: ссылки меню, контроль доступа, ссылки действия и другие особенности, которые были плотно связаны друг с другом. Используя компонент маршрутизации Symfony2 стало возможным разделить аспекты обработки маршрута, улучшив и обогатив данное решение. Система маршрутизации Symfony2 работает с маршрутами, а не с путями. К примеру, теперь, мы можем получив ответ в различных форматах( JSON, XML or HTML) запрашивая один и тот же путь.

Определение маршрута.

Простой пример hook_menu() в Drupal 7 реализации:
function trousers_menu() {
  $items['trousers'] = array(
    'title' => 'Trousers',
    'page callback' => 'trousers_page', \\ calls a trousers_page function that returns the content
 );
 return $items;
} 

Что же изменилось в Drupal 8?

Сначало необходимо определить маршрут в файле маршрутизации YAML формата, который находится в директории модуля modulename.routing.yml. Если вы посмотрите директории других модулей, найдёте много хороших примеров. Создадим пример модуля trousers. Модуль называется trousers, файл конфигурирования маршрутизации выглядит так.
trousers_list:
  pattern: '/trousers'
  defaults:
  _content: '\Drupal\trousers\Controller\TrouserController::list'
  requirements:
  _permission: 'view content'

В первой строке мы определяем уникальное название маршрута - trousers_list. Позже будем ссылаться на него для определения элемента меню.

Определим дополнительную информацию маршрута. Информация схожа на массив $items Drupal 7 в реализации ловушки hook_menu().

В секции defaults(поумолчанию) устанавливаем метод - назначение маршрута в особенную переменную _content. Обработчик будет вызван при соответствующем пути. В нашем случае метод list() класса TrouserController. _content специальное название ключа, которое указывает то, что контент это основная часть ответа. Далее контент может быть отослан сразу клиенту, или обработан системой размещения блоков в разметке страниц.
_permissions - указывает необходимые требования прав на доступ к маршруту.

Создание класса контроллера маршрута.
Контроллер маршрута - это простой PHP класс, наименование которого соответсвует PSR-0 в Drupal 8. Класс должен существовать в подпространстве имен контроллера и находиться в определенном месте файловой системы:

lib/Drupal/trousers/Controller/TrouserController.php

namespace Drupal\trousers\Controller;

/**
* Route controller for trousers.
*/
class TrouserController {
 /**
  * Displays a list of trousers.
  */
  public function list() {
    // return trouser list here.
  }

}

Просто возращаем обработанное HTML содержимое.

Создание элемента меню.
Как упоминалось выше ,компонент маршрутизации в Symfony2 отвечает только за маршруты. Теперь нам необходимо определить элемент меню в системе меню Drupal 8. Система меню была упрощенна.
/**
 * Implements hook_menu().
 */
function trousers_menu() {
  $items['trousers'] = array(
    'title' => 'Trousers',
    'route_name' => 'trousers_list',
  );
  return $items;
}

Видите, что route_name указывает на маршрут, который объявлен в rousers.routing.yml.

Более продвинутый контроллер маршрутизации.
Пример выше - это простой пример. Обычно нам необходимо получить контент от куда-нибудь ещё. Может быть с какого-нибудь сервиса, другой фунции, базы данных. Это случай когда сила контейнера зависимотей встраивания(DIC) вступает в игру.

Зависимости встраивания - Dependency Injection.
Кратко. Если вы вызываете класс(контейнер) у которого в параметрах есть зависмости от других классов(контейнеров, тогда DIC инициализирует всё, что необходимо в соответсвии зависимотей контейнеров. В нашем примере необходимо запросить у базы данных trousers, давайте модифицируем наш контроллер добавив объект Connection.
namespace Drupal\trousers\Controller;

use Drupal\Core\Controller\ControllerInterface;
use Drupal\Core\Database\Connection;
use Symfony\Component\DependencyInjection\ContainerInterface; 
/**
* Route controller for trousers.
*/
class TrouserController implements ControllerInterface {

 /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection;
   */
  protected $database; 

  /**
   * Constructs a \Drupal\trousers\Controller\TrouserController object.
  *
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   */
 public function __construct(Connection $database) {
   $this->database = $database;
 } 
 /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
   return new static($container->get('database'));
 } 
 /**
  * Displays a list of trousers
  */
  public function list() {
   // query the database
   $this->database->query('SELECT * from {trousers}');
   // handle the db results as usual
    // return trouser content here.
  }

}

Необходимо выполнить некоторую работу для того, чтобы встроить зависимости. Сперва, объявим защищенное свойство $database, которое будет хранить объект соеденения к базе данных, после, установим его в конструкторе. Так как контроллер сам по себе не является сервисом(компонентом) DIC, у нас метод фабрики create(ContainerInterface $container) объявленный в интерфейсе ControllerInterface, который мы реализуем, что позволяет получить с DIC то, что нам необходимо, соединение с базой данных. Выглядит магически, но если вы хотите использовать встраиваемые зависимости, тогда вам необходимо реализовать интерфейс ControllerInterface, так как мы делали выше.

Зачем нам нужны встраиваемые зависимости, если возможно просто вызвать функцию db_query?
Задача встраиваемых зависимостей заключается в разделении различных компонет и логики, что нам позволит писать тесты используя PHPUnit к примеру. Таким образом мы можем подставить тестовые реализации зависимых компонент отделив данную реализацию от остальной системы. В процедурном стиле кода нет возможности подмены тестовых данных.

Оригинал статьи

1 комментарий: