Перейти к основному содержанию
menu

В предыдущих версиях Drupal весь рабочий процесс обработки входящего HTTP-запроса, выполнения проверок доступа, генерации тематического вывода и в конечном итоге возврата HTTP-ответа клиенту обрабатывался основными модулями и библиотеками, созданными специально для Drupal. В Drupal 8 эти общие задачи обрабатываются несколькими общими компонентами, предоставляемыми современной гибкой средой PHP, встроенной в любой PHP-проект под названием Symfony. 

Вот некоторые из важных тем, которые мы рассмотрим в этой главе:

HTTP-запрос - процесс ответа
Основы Symfony
Компоненты Symfony в Друпал 8
Хотя я не намерен вдаваться в подробности, но думаю, что вы должны получить общее представление о компонентах Symfony и о том, как они используются в Drupal 8.

Что такое Symfony?
Этот раздел призван дать вам полное представление о PHP-фреймворке Symfony. Мы рассмотрим общую концепцию диалога запрос-ответ в Интернете и продемонстрируем, как Symfony обрабатывает этот процесс, используя свои классы ООП.

 

От запроса к ответу


Общение в сети начинается с запроса. Запрос представляет собой простое сообщение, отправляемое клиентом (например, веб-браузером, приложением для смартфона и т. Д.) В специальном формате, известном как HTTP. Запрос отправляется на сервер, а затем клиент ожидает ответа.

Этот HTTP-запрос может выглядеть примерно так:

GET / HTTP/1.1
Host: example.com
Accept: text/html
User-Agent: Mozilla/5.0

Это простое текстовое сообщение сообщает серверу информацию о ресурсе, запрашиваемом клиентом, путем метода HTTP (например, GET ) и URI (например, / или / blog и т. д.). 

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

В HTTP-мире это ответное сообщение будет выглядеть примерно так:

HTTP/1.1 200 OK
Date: Mon, 13 Nov 2018 15:05:05 GMT
Server: Apache/2.2.14 (Ubuntu)
Content-Type: text/html

<html>
  <!-- ... HTML for the requested page -->
</html>

Ответ содержит ресурс, запрошенный клиентом (в данном случае HTML запрашиваемой страницы), а также другую важную информацию. Наиболее важной информацией в ответе является код состояния ( в данном случае 200 ). Это определяет общий результат запроса и сообщает клиенту.

Наряду с кодом состояния, в ответе, называемом заголовками HTTP, сервер возвращает некоторые дополнительные данные . В приведенном выше примере сервер возвратил заголовок Content-type, чтобы сообщить клиенту, что Internet Media Type возвращаемого ресурса - text / html . Тело запрошенного ресурса может быть возвращено в других форматах, таких как JSON или XML.

 

Запрос и ответ в Symfony


Теперь, когда мы понимаем основную концепцию диалога запрос-ответ, давайте посмотрим, как Symfony справляется с этим. Наша цель - продемонстрировать классы Request и Response, предоставляемые компонентом Symfony HttpFoundation .

Класс Request в Symfony - это сложный класс ООП, разработанный для того, чтобы помочь вам взаимодействовать с сообщением HTTP-запроса от клиента. Он предоставляет всю информацию запроса в очень простой форме:

$request = Request::createFromGlobals();

// Retrieve the HTTP method of the request
$request->getMethod();          

// Retrieve SERVER variables
$request->server->get('HTTP_HOST');

// The URI requested (e.g. /contact) without query parameters
$request->getPathInfo();

// GET variables 
$request->query->get('name');

// POST variables
$request->request->get('name');

// Retrieve a COOKIE value
$request->cookies->get('PHPSESSID');

// Retrieve an HTTP request header with lowercase keys
$request->headers->get('host');
$request->headers->get('content_type');

Класс Response в Symfony обеспечивает аналогичный способ работы с ответным сообщением HTTP, предоставляя возможность любому приложению использовать интерфейс OOP для генерации ответа клиенту:

<?php

use Symfony\Component\HttpFoundation\Response;

$response = new Response();

$response->setContent('<html><body><h1>Hello Symfony!</h1></body></html>');
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/html');

// Outputs the HTTP headers and the content of our document
$response->send();

Теперь, когда мы увидели, как легко работать с сообщениями о запросах и ответах, давайте создадим следующее утверждение, которое облегчит вам понимание того, для чего предназначен Symfony: цель любого приложения - обработать входящий запрос и генерировать соответствующий ответ на основе логики приложения. Иными словами, вы можете использовать компоненты Symfony для обработки запроса и генерации ответа. Между ними находится само приложение. В нашем случае это приложение Drupal 8.

 

Фронт контроллер


Вместо создания отдельных файлов для каждой страницы нашего веб-сайта (например, blog.php , contact.php , about.php и т. Д.) Мы можем использовать фронт-контроллер, который выполняется с каждым запросом.symfony

Это означает, что каждый ресурс на нашем сайте определен в файле конфигурации маршрутизации, который сопоставляет URL-адреса с конкретными функциями PHP, называемыми контроллерами . Эти контроллеры передают информацию запроса - через класс Request - и их задача состоит в том, чтобы создать экземпляр и вернуть объект Response . 

 

Компоненты Symfony в Друпал 8


До сих пор мы узнали, что такое Symfony и для чего его можно использовать. Мы также установили, что Symfony предоставляет оболочку для нашего приложения через его компоненты, и наша задача - отвечать на входящий запрос и генерировать ответ. Давайте внимательнее посмотрим на эти компоненты и посмотрим, как их использует Drupal 8.

 

Маршрутизация


Раньше в Drupal была система пользовательских меню, которая обрабатывала диалог запрос - ответ. Хотя он работал правильно, он пытался сделать слишком много вещей одновременно: отображение путей и функций обратного вызова, проверка доступа, загрузка сущностей, предоставление пунктов меню в разных меню, а также вкладок, контекстных ссылок и так далее. В Drupal 8 система меню была разделена на различные системы, и теперь для отображения контроллера используется компонент маршрутизации Symfony . 

Самый простой способ определить маршрут в Drupal 8 - это создать файл YAML, названный в честь нашего модуля. Давайте представим, что мы хотим разработать очень простой модуль mymodule, который создает страницу, к которой можно получить доступ в / hello-world . Для этого нам нужно создать файл mymodule.routing.yml со следующим содержимым: 

mymodule.content:
  path: '/hello-world'
  defaults:
    _content: '\Drupal\mymodule\Controller\MyController::content'
    _title: 'Hello world!'
  requirements:
    _permission: 'access content'

Это в основном говорит Drupal, что:

  • в системе существует новый маршрут с именем mymodule.content
  • маршрут связан с путем / привет-мир
  • содержимое страницы генерируется методом content () в нашем классе MyController
  • заголовок страницы - Привет, мир!
  • только пользователи с правами доступа должны видеть эту страницу

Теперь, когда мы закончили с частью маршрутизации, давайте создадим фактический вывод страницы. Для этого нам нужно создать класс контроллера, на который мы ссылаемся в первой части, а также добавить метод content () , который фактически сгенерирует вывод:

<?php

  /**
   * @file
   * Contains \Drupal\mymodule\Controller\ExampleController.
   */

  namespace Drupal\mymodule\Controller;

  /**
   * Example page controller.
   */
  class MyController {
    /**
     * Generates an example page.
     */
    public function content() {
      return array(
        '#markup' => t('Hello world!'),
      );
    }
  }

?>

Мы определили полностью функциональный маршрут в Drupal 8, к которому можно получить доступ, используя путь / hello-world, и он сгенерирует HTML-страницу с заголовком Hello world! и контент Привет, мир!

Этот пример только поверхностно коснулся системы меню в Drupal 8, в следующей главе мы рассмотрим его более подробно при разработке нашего первого пользовательского интерфейса администратора.

 

Сервисы


Важное примечание: когда мы ссылаемся на Сервисы здесь, мы не говорим о Веб-сервисах или модуле Сервисов для Drupal. В этом контексте Сервис - это любой объект PHP, который выполняет глобальную задачу в нашем приложении. Думайте об этом как об общем объекте, который создан для конкретной задачи, например, получения сведений о текущем пользователе. 

Преимущество создания сервисов в нашем приложении состоит в том, что мы можем разделить части функциональности для многократного использования в их собственную, автономную область. Поскольку каждый сервис выполняет только один конкретный элемент функциональности, мы можем получить к ним доступ из любого места и использовать их, не беспокоясь о том, что еще они могут вызвать в нашем приложении. Сервисы также обеспечивают лучшую тестируемость, поскольку они отделены от других частей нашего приложения.

 

Сервисные контейнеры


Service Container является PHP объект , который управляет созданием экземпляров служб. Используя Контейнер, мы можем указать в нашем приложении, как сервисы должны создаваться системой, вместо того, чтобы создавать их вручную. Конфигурирование Сервисов в Контейнере осуществляется через файл YAML в Drupal 8.

Давайте представим, что у нас есть служба экспорта, которая экспортирует указанный набор данных в заданном формате. Чтобы использовать этот Сервис, нам необходимо сделать следующее:

<?php
    
  $export = new Export('csv');
  $export->export($data);

?>

Мы попросили нашу Службу экспортировать данные в формате CSV в приведенном выше примере, но мы также знаем, что наша Служба позволяет нам экспортировать данные в другие форматы (например, XML или JSON). Так что же произойдет, если нам нужно изменить формат экспорта? Нужно ли искать каждую реализацию этого Сервиса и вручную изменять настройки экспорта? Ответ - нет, мы не хотим этого делать. Вместо этого мы научим наше приложение, как создать для нас объект Export в файле modulename.services.yml в папке нашего модуля:

services:
  my_export:
    class:        Drupal\MyModule\Export
    arguments:    [csv]

Теперь мы вставили наш Сервис в Контейнер сервисов и определили отображение классов и выходной формат (например, CSV). Теперь, если мы хотим изменить настройку позже и использовать XML в качестве выходного формата, нам нужно только изменить конфигурацию нашего Сервиса.

Наш новый Сервис будет доступен с любого контроллера Symfony, используя метод get () :

<?php

  class MyController extends Controller {
    
    public function ExportAction($data) {      
      $export = $this->get('my_export');
      $export->export($data);
    }
  }

?>

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

На сайте Symfony имеется исчерпывающая документация о сервисах. Чтобы получить больше информации посетите http://symfony.com/doc/master/service_container.html


Инъекция сервисов и зависимостей в Drupal 8


В попытке создать более гибкую и разделенную систему, Drupal 8 следует вышеупомянутому шаблону и заменяет статические глобальные функции на Services. Это приводит к подключаемой системе, где данные службы могут быть заменены на другие при необходимости.

В Drupal 8 определено много основных Сервисов. Например, давайте посмотрим, как получить информацию о текущем действующем пользователе на нашем сайте Drupal. В предыдущих версиях Drupal это было сделано с помощью глобальной переменной, называемой $ user . В Drupal 8 эта глобальная переменная была удалена, и сведения о пользователе можно получить с помощью сервиса current_user в основном контейнере Drupal:

<?php

  $account = \Drupal::currentUser();

?>

Сервисный контейнер Drupal содержит все Сервисы, определенные ядром Drupal 8, от Сервисов кэширования до Сервисов трансляции и пути. Глобальный класс Drupal предоставляет удобные методы для доступа к некоторым из этих Сервисов, например, request () (для доступа к Сервису запроса) или database () (для доступа к Сервису базы данных). Однако в Drupal 8 определено множество других сервисов, для которых такие методы не были созданы. Чтобы получить к ним доступ, мы можем использовать метод Drupal :: service () . Например, чтобы получить сервис router_listener , мы использовали бы следующий код:

<?php

  $router_listener = \Drupal::service('router_listener');

?>

Полный список сервисов, определенных ядром Drupal 8, можно найти по адресу https://api.drupal.org/api/drupal/core%21core.services.yml/8.


Сервисы, определенные в системе, могут передаваться в качестве аргументов другим Сервисам. Например, служба link_generator зависит от служб url_generator , module_handler и renderer . Указание зависимости выполняется путем добавления знака @ к имени Сервиса при определении аргументов Сервиса в данном файле YAML services.yml . В свою очередь, эти аргументы будут переданы в функцию конструктора класса нашего Сервиса.

В этом примере контейнер служб используется для загрузки (или «внедрения») зависимых служб для службы link_generator . Давайте посмотрим, как создается экземпляр объекта LinkGenerator, проверив его конструктор класса:

<?php

public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, RendererInterface $renderer) {
  $this->urlGenerator = $url_generator;
  $this->moduleHandler = $module_handler;
  $this->renderer = $renderer;
}

Здесь мы видим, что конструктор класса требует, чтобы параметры были определенного типа (например, экземпляр данного класса), чтобы их можно было использовать для создания экземпляра объекта LinkGenerator . Поскольку сами эти параметры являются Сервисами, настроенными в Контейнере Сервисов, система знает, из какого класса создавать их экземпляры (читая конфигурацию в файле YAML). Эта модель называется инъекцией зависимостей .

 

EventDispatcher


Еще один компонент Symfony, который теперь используется в Drupal 8, - EventDispatcher . Компонент EventDispatcher позволяет создавать различные системные события, на которые могут подписаться слушатели. Когда это конкретное событие происходит в системе, диспетчер  отправляет уведомление всем слушателям, подписавшимся на это событие.

Представьте, что у нас есть класс Mailer, который доставляет различные электронные письма в нашей системе. Мы хотим создать наше приложение таким образом, чтобы другие компоненты нашей системы могли получать уведомления при отправке электронного письма. Для этого мы создаем объект EventDispatcher в конструкторе нашего класса:

<?php

  use Symfony\Component\EventDispatcher\EventDispatcherInterface;

  class Mailer {
    
    static $dispatcher = NULL;
    
    public function __construct(EventDispatcherInterface $dispatcher) {
      $this->dispatcher = $dispatcher;
    }   
  }

?>

Теперь, когда наш класс Mailer получил доступ к сервису EventDispatcher , давайте создадим новое событие :

<?php

  use Symfony\Component\EventDispatcher\EventDispatcherInterface;

  class Mailer {
    
    static $dispatcher = NULL;
    
    public function __construct(EventDispatcherInterface $dispatcher) {
      $this->dispatcher = $dispatcher;
    }
    
    public function deliverEmail() {
      $this->dispatcher->dispatch('mailer.email_delivered', new Event());
    }
  }

?>

Таким образом, наше новое событие регистрируется в центральном служебном контейнере, и всякий раз, когда оно происходит в системе, служба EventDispatcher уведомляет об этом всех слушателей.

 

EventSubscriber


Теперь, когда мы создали наше событие, давайте создадим несколько слушателей, которые будут слушать это событие. Представьте, что у нас есть класс Reporting, который хранит отчеты о различных событиях, происходящих в системе, таких как отправка электронных писем. Мы хотим, чтобы этот класс прослушивал ранее созданное Событие, поэтому мы реализуем EventSubscriberInterface и его статический метод getSubscribeedEvents () :

<?php 

  use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  
  class ReportingSubscriber implements EventSubscriberInterface {
    
    static function getSubscribedEvents() {

      $events['mailer.email_delivered'] = 'onEmailDelivered';
      return $events;
    }
    
    static function onEmailDelivered(Event $event) {
      // Save report to the database
    }
  }

?>

В методе getSubscribeedEvents () мы сообщаем службе EventDispatcher, что хотим прослушать событие mailer.email_delivered, и запускаем метод onEmailDelivered () при возникновении события. Наш метод обратного вызова автоматически получает весь объект Event в качестве аргумента, чтобы мы могли получить доступ к данным события.

Осталось сделать только одно: нам нужно зарегистрировать наш класс ReportingSubscriber как сервис в контейнере центрального сервиса. Чтобы сделать это, мы создаем modulename.services.yaml файл со следующим содержимым (заменяя MODULENAME действительным именем нашего модуля):

services:
  reporting.subscriber:
    class: Drupal\my_reporting\EventSubscriber\ReportingSubscriber
    tags:
      - { name: event_subscriber }

И последнее, но не менее важное: в Drupal 8 появился новый базовый движок шаблонов, который называется Twig. Twig - это шаблонизатор для PHP, разработанный создателем проекта Symfony. Он разработан, чтобы быть быстрым, безопасным и гибким, а также более простым в использовании для разработчиков, поскольку это настоящий шаблонизатор. Предыдущие версии Drupal в основном использовали PHPtemplate в качестве базового движка шаблонов, и это вызывало некоторый дискомфорт среди разработчиков Drupal-приложений и конечных пользователей, в основном из-за того, что разработчикам интерфейсов приходилось знакомиться с PHP и логикой в ​​шаблонах. 

Редизайн системы тем и использование Twig в качестве основного движка шаблонов решили эти проблемы. Мы более подробно рассмотрим тематический слой Twig и Drupal в следующем посте.

Резюме


В этой части серии мы познакомились с Symfony и получили общее представление о его концепциях и компонентах. Мы изучили процесс HTTP-запроса и ответа и выяснили, как Symfony обрабатывает их. Наконец, мы взглянули на то, как некоторые компоненты Symfony используются в Drupal 8. В следующем посте мы применим наши знания на практике и создадим наш первый модуль Drupal 8!

assistant Теги

keyboard_arrow_up