Урок 3. Requests и responses, роутинг и контроллеры

Requests, responses, роутинг и контроллеры

Содержание

Requests и responses

Запросы и ответы в Drupal 8 представлены компонентом Symfony HttpFoundation (был рассмотрен в Уроке 2) и классами Request и Response соответственно.

Request

Начнем с запроса[1]. Простой способ создания запроса из глобальных переменных выглядит так

1
2
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();

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

  • request — эквивалент $_POST;
  • query — эквивалент $_GET ($request->query->get('name'));
  • cookies — эквивалент $_COOKIE;
  • attributes — эквивалента нет, используется для хранения дополнительных данных;
  • files — эквивалент $_FILES;
  • server — эквивалент $_SERVER;
  • headers — в основном эквивалент подгруппе $_SERVER ($request->headers->get('User-Agent')).

Каждое свойство — это экземпляр класса ParameterBag (класс, который представляет собой контейнер для хранения пары ключ/значение). Соответственно все экземпляры данного класса имеют методы для получения/обновления данных, а также фильтрации входных значений.
Чтобы получить информацию о запрашиваемом пути в классе Request существует метод getPathInfo().
Для примера обратимся к странице example.com/example

1
2
3
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
$path = $request->getPathInfo();

В переменной $path получим значение /example.
Пример имитации запроса.

1
2
3
4
5
6
use Symfony\Component\HttpFoundation\Request;
$request = Request::create(
 'https://www.google.com/',
 'GET',
 array('q' => 'drupal%208')
);

Примеры неполного списка команд для получения тех или иных значений от запроса

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$request = Request::createFromGlobals();
// Возвращает путь, по которому был сделан запрос. Для example.com/example вернет /example.
$request->getPathInfo();
// Получение GET и POST параметра в запросе.
$request->query->get('foo');
$request->request->get('bar', 'значение по умолчанию если первого параметра не обнаружено');
// Получение SERVER переменных.
$request->server->get('HTTP_HOST');
// Получение экземпляра UploadedFile идентифицируемого по foo.
$request->files->get('foo');
// Получение значения COOKIE.
$request->cookies->get('PHPSESSID');
// Получение заголовка HTTP запроса, с нормализованными ключами в нижнем регистре.
$request->headers->get('host');
$request->headers->get('content_type');
$request->getMethod();      // GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // массив клиентских языков.

Значение которые пришли по урлу /example?foo=123
Requests

Response

Response[2] объект хранит всю необходимую информацию для построения ответа клиенту. Конструктор класса Response принимает три аргумента:

  • контент ответа;
  • статус код;
  • HTTP заголовки;

Пример создания респонса.

1
2
3
4
5
6
7
use Symfony\Component\HttpFoundation\Response;
 
$response = new Response(
 'This is response',
 Response::HTTP_OK,
 array('content-type' => 'text/html')
);

Ответ также можно модифицировать после его создания

1
$response->setStatusCode(Response::HTTP_NOT_FOUND);

При отправке запроса используют методы prepare() и send(). Метод prepare() приводит response в соответствие со спецификацией RFC 2616 протокола HTTP[3].

1
2
$response->prepare($request);
$response->send();

Класс Response содержит довольно большое количество методов для управления HTTP заголовками (setPublic(), setPrivate(), expire() и т.д.).
Для редиректа пользователя на другой урл компонент HttpFoundation имеет в своем арсенале класс RedirectResponse, наследуемый от базового класса Response. Пример редиректа

1
2
3
4
5
6
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
$request  = Request::createFromGlobals();
$response = new RedirectResponse('/');
$response->prepare($request);
$response->send();

Более подробную информацию о работе с Response объектом можно получить по ссылке[2].

Роутинг и контроллеры

Друпаловская роутинговая система работает на основе компонента Симфони HttpKernel. Роут — это путь, определенный в Друпале, при обращении по которому возвращается определенный контент.
Как правило, большинство фреймворков или систем умеют управлять повторяющимися задачами (например роутинг, проверки доступа и т.д.), поэтому разработчики могут довольно просто создавать страницы приложения. HttpKernel компонент предоставляет интерфейс, который формализует процесс запроса и создание соответствующего ответа. Т.е. таким образом компонент является “сердцем” любого приложения или системы и не имеет значения насколько они отличаются по внутренней архитектуре.
Структура интерфейса HttpKernel приведена ниже.

1
2
3
4
5
6
7
8
9
10
11
12
namespace Symfony\Component\HttpKernel;
use Symfony\Component\HttpFoundation\Request;
 
interface HttpKernelInterface
{
  // ...
  /**
   * @return Response A Response instance
   */
  public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
 
}

Метод HttpKernel:handle() — это конкретная реализация обработки запроса и возврата ответа.
Drupal на базе интерфейса HttpKernalInterface реализует собственный интерфейс HttpDrupalKernelInterface. Класс, который непосредственно содержит всю логику метода handle() называется DrupalKernel и реализуется от двух интерфейсов: HttpDrupalKernelInterface и TerminableInterface[4].
Рассмотрим, что происходит когда пользователь обращается по урлу, например /admin/config.
При запросе Drupal создает экземпляр автозагрузчика \Composer\Autoload\ClassLoader. Затем создает экземпляр класса DrupalKernel с передачей ему двух параметров: строки ‘prod’ и объекта автозагрузчика. Строка “prod” - обозначает энвайронмент (вариации: prod и dev). Энвайронмент далее участвует в построении кеш ключа для сервис контейнера. Далее получаем объект Request из глобальных переменных и передаем его в метод handle() экземпляра класса DrupalKernel. После обработки происходит отправка ответа и вызов метода terminate(), который отвечает за прерывание цикла запрос/ответ.
Как это выглядит схематически приведено ниже (упрощенная версия). Ознакомиться с полным вариантом можно по ссылке[5].
Роутинг в Drupal 8
Нумерация на схеме отображает последовательность выполнения тех или иных блоков. Некоторые блоки могут не участвовать в создании ответа. Например, если контроллер возвращает объект Response, то блоки 5 и 6 (MainContentViewSubscriber и Main content renders) участвовать не будут.
Контроллер — это класс, который содержит метод, обрабатывающий запрос и возвращающий ответ. В Drupal 7 роль контроллеров выполняли page callbacks. Для указания соответствия пути (урла) и связанного с ним контроллера существует файл *.routing.yml.
Итак, что же нужно для объявления роутинга кастомного урла:

  1. создать файл MYMODULE.routing.yml
  2. Добавить в него урл, указать метод контроллера, который будет возвращать содержимое страницы, пермишены и т.д. Пример:
    1
    2
    3
    4
    5
    6
    
    MYMODULE.example:
     path: '/example'
     defaults:
       _controller: '\Drupal\MYMODULE\Controller\MymoduleController::getOutput'
     requirements:
       _permission: 'access content'
  3. Создать контроллер, описать указанный метод (getOutput) в файле контроллера MymoduleController.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    class MymoduleController {
     
     /**
      * Outputs the example page.
      */
     public function getOutput() {
       return t('This is example page.');
     }
    }

Создание кастомного модуля

В данном пункте рассмотрим создание кастомного модуля. Для этого перво-наперво создадим папку нашего модуля в директории modules/custom. Данный модуль по мере написания статей и изучения новых материалов мы будет постоянно расширять и дополнять. Модуль не будет тривиальным (наподобие Hello world), а будет выполнять достаточно конкретную задачу: тянуть ленту новостей с какого-либо новостного портала. Имя модулю дадим соответствующее — news.
Итак в папке modules/custom создаем папку news.
В ней создаем файл news.info.yml. Содержимое данного файла приведено ниже

1
2
3
4
5
6
name: News
description: 'Provides news feeds.'
type: module
core: 8.x
package: Other
version: '8.x-1.0'

Все, модуль готов:) Этого вполне достаточно, чтобы модуль был отображен в панели управления и мы могли его включить.
модуль news

Домашнее задание

Создать модуль news, добавить на страницу admin/config под блоком Web Services ссылку News с описанием. Урл ссылки News — admin/config/services/news. По этому урлу отображать пустую страницу с заголовком News.
Для выполнения данного задания потребуется создать свой роутинг, также необходимо разрешить доступ к данной странице только группе "администраторы".

Дополнительная информация по статье

  1. http://symfony.com/doc/current/components/http_foundation.html#request - о Request из документации симфони.
  2. http://symfony.com/doc/current/components/http_foundation.html#response - о Response из документации симфони.
  3. https://tools.ietf.org/html/rfc2616 - спецификация RFC 2616 протокола HTTP.
  4. http://api.symfony.com/3.2/search.html?search=TerminableInterface - TerminableInterface из документации симфони.
  5. https://www.drupal.org/files/d8_render_pipeline.pdf - полный вариант схемы роутинга.
  6. Версии программных продуктов, используемых в статье: Drupal 8.2.5

4 Комментария