Урок 6. Menu API

Урок 6. Menu API

Menu API, на самом деле, представляет собой API для создания различных типов ссылок: ссылок меню, табов, action и контекстных ссылок[1].

Ссылки Menu

Ссылки меню размещаются в файлах *.links.menu.yml[2].
Структура меню:

  • title — имя ссылки. Будет отображено в UI.
  • description — описание ссылки. На отдельной странице будет показано текстом, например
    описание ссылки
    В боковом меню описание будет отображено как title ссылки
    заголовок ссылки
  • route_name — имя роута, определенного в файле *.routing.yml. Именно данное свойство непосредственно влияет на href параметр ссылки. С определением своего роутинга можно ознакомиться по материалам статьи Урок 3. Requests, responses, роутинг и контроллеры
  • route_parameters — содержит параметры, необходимые для построения пути. Данные будут использоваться в определении роута в файле *.routing.yml. Например, вы указали это свойство в определении своей ссылки, т.е. в файле MYMODULE.links.menu.yml
    1
    2
    3
    4
    5
    
    MYMODULE.example_link:
      title: 'MYMODULE link'
      description: 'MYMODULE link description'
      route_name: MYMODULE.some_route
      route_parameters: { key: 'value' }

    Для того чтобы воспользоваться параметром key, необходимо подставить его в определении роута (файл MYMODULE.routing.yml), т.е.

    1
    2
    3
    4
    5
    6
    7
    
    MYMODULE.some_route:
      path: '/admin/config/services/MYMODULE/{key}'
      defaults:
        _controller: '\Drupal\MYMODULE\Controller\MYMODULEController::getPage'
        _title: 'MYMODULE'
      requirements:
        _role: 'administrator'

    Финальный адрес страницы будет — /admin/config/services/MYMODULE/value.
    Более того, параметр key придет также в метод getPage() класса MYMODULEController в качестве аргумента.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    namespace Drupal\MYMODULE\Controller;
     
    /**
     * Class MYMODULEController.
     *
     * @package Drupal\MYMODULE\Controller
     */
    class MYMODULEController {
     
      /**
       * Returns page.
       */
      public function getPage($key = '') {
        ...
      }
     
    }
  • url — свойство, которое необходимо использовать вместо указания route_name, если ссылка ведет на сторонний ресурс (внешняя). Ссылку необходимо указывать в правильном формате, включая протокол HTTP(s), например:
    1
    2
    3
    4
    
    MYMODULE.example_link:
      #...
      url: 'https://www.google.com/'
      #...
  • parent — указывает родительское меню, т.е. меню, в котором будет расположена данная ссылка. Указать нужно имя меню, определенное в файле *.routing.yml.
  • weight — задает вес ссылки в меню. По умолчанию 0.
  • menu_name — машинное имя меню, в котором будет размещена определяемая ссылка. Обратите внимание, что если указано свойство parent, то menu_name работать не будет.
  • enabled — включает или выключает ссылку из UI. Доступные значения: 0/1. По умолчанию 1.
  • class — с помощью данного свойства можно указать собственный класс для создания своего плагина ссылки.
    Пример:

    1
    2
    3
    4
    5
    6
    
    MYMODULE.example_link:
      title: 'MYMODULE link'
      description: 'MYMODULE link description'
      route_name: MYMMODULE.some_route
      parent: system.admin_config_services
      class: \Drupal\MYMODULE\Menu\MymoduleMenuLink

    Файл MymoduleMenuLink.php, содержащий класс, размещаем в папке MYMODULE/src/Plugin/Menu и наследуемся от дефолтного класса создания ссылки меню MenuLinkDefault. Пусть кастомный класс будет добавлять суффикс “Example” к тайтлу ссылки.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    namespace Drupal\MYMODULE\Menu;
     
    use Drupal\Core\Menu\MenuLinkDefault;
     
    /**
    * Class MymoduleMenuLink.
    *
    * @package Drupal\MYMODULE\Menu
    */
    class MymoduleMenuLink extends MenuLinkDefault {
     
     /**
      * {@inheritdoc}
      */
     public function getTitle() {
       return parent::getTitle() . ' Example';
     }
     
    }

    Получаем следующий результат
    кастомный класс для ссылки меню

  • form_class — класс, который отвечает за создание формы для добавления/редактирования ссылок menu. Можно наследоваться от базового класса MenuLinkDefaultForm или полностью создать свой, реализовав два интерфейса MenuLinkFormInterface, ContainerInjectionInterface.
    Пример записи в yml файле:

    1
    2
    3
    4
    
    MYMODULE.example_link:
      #...
      form_class: \Drupal\MYMODULE\Form\MymoduleMenuLinkContentForm
      #...
  • deriver — позволяет динамически создавать дополнительные определения плагинов. Для создания собственного класса необходимо наследоваться от класса DeriverBase, либо наследоваться от уже готового deriver класса, внеся свои коррективы.
    Пример записи в yml файле:

    1
    2
    
    MYMODULE.example_link:
      deriver: \Drupal\MYMODULE\Plugin\Derivative\MymoduleMenuLinkDeriver

    Пример класса MymoduleMenuLinkDeriver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    namespace Drupal\MYMODULE\Plugin\Derivative;
     
    use Drupal\Component\Plugin\Derivative\DeriverBase;
     
    /**
     * Class MymoduleMenuLinkDeriver.
     *
     * @package Drupal\news\Plugin\Derivative
     */
    class MymoduleMenuLinkDeriver extends DeriverBase {
     
      /**
       * {@inheritdoc}
       */
      public function getDerivativeDefinitions($base_plugin_definition) {
        $this->derivatives['MYMODULE.menu_link'] = $base_plugin_definition;
        $this->derivatives['MYMODULE.menu_link']['title'] = 'MYMODULE Dynamic link';
        $this->derivatives['MYMODULE.menu_link']['description'] = 'MYMODULE Dynamic link description';
        $this->derivatives['MYMODULE.menu_link']['route_name'] = 'system.modules_list';
        $this->derivatives['MYMODULE.menu_link']['parent'] = 'system.admin_config_services';
     
        return parent::getDerivativeDefinitions($base_plugin_definition);
      }
     
    }

    Получаем вот такую ссылку
    динамически созданная ссылка

  • expanded — указывает на то, будет ли меню с данной ссылкой всегда развернуто. Аналогично включению галочки Show as expanded (Показывать развернутым) в настройках меню.
    показывать ссылку развернутой
  • options — массив параметров, который может быть передан для генерации ссылки, на основе данного пункта меню. Например
    • attributes — ассоциативный массив HTML атрибутов, который будет использоваться при формировании ссылки. Некоторые атрибуты:

      • target: _blank — открытие ссылки в новой вкладке
      • title: some title — указание тайтла ссылки
      • class: [‘one’, ‘two’] — добавление css классов 'one', ‘two’
    • language — используется для внутренних ссылок с целью сделать их активными в случаях, когда данный параметр совпадает с языком страницы.
    • set_active_class — отвечает за то, будет ли ссылка отображаться как активная (будет добавлен css класс active). Значение по умолчанию — FALSE.

Все параметры, кроме title опциональны[3].
Пример использования множества свойств

1
2
3
4
5
6
7
8
9
10
11
MYMODULE.example_link:
  title: 'MYMODULE link'
  description: 'MYMODULE link description'
  route_name: MYMODULE.some_route
  weight: 100
  enabled: 1
  rout_parameters: { key: 'value' }
  menu_name: main
  options:
    attributes:
      target: _blank

Ссылки меню могут быть изменены с помощью хука hook_menu_links_discovered_alter()[3].

1
2
3
4
5
6
/**
* Implements hook_menu_links_discovered_alter().
*/
function MYMODULE_menu_links_discovered_alter(&$links) {
  $links['MYMODULE.example_link']['parent'] = 'system.logging_settings';
}

Хуки размещаются, как и в 7-ке , в файле MYMODULE.module.

Табы

Определение ссылок для табов находится файле *.links.task.yml[4].
Структура меню

  • title — заголовок таба.
  • route_name — имя роутинга из файла *.routing.yml. Непосредственно влияет на href атрибут таба.
  • base_route — имя базового роутера из файла *.routing.yml. Определяет по какому пути (или на какой странице) будет отображен таб. Если по этому адресу уже есть табы с таким же базовым роутом, то текущий будет размещен среди них согласно весу.
  • weight — отвечает за вес табов.
  • parent_id — свойство, позволяющее указать родительский таб для создания табов второго уровня и т.д. В parent_id необходимо указать имя определения таба (из файла *.links.task.yml), под которым необходимо разместить текущие.
    Рассмотрим свойство на примере. Есть два таба, которые размещены на странице admin/content.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    MYMODULE.example_parent_tab1:
      title: 'MYMODULE tab 1'
      route_name: MYMODULE.some_route_tab1
      base_route: system.admin_content
     
    MYMODULE.example_parent_tab2:
      title: 'MYMODULE tab 2'
      route_name: MYMODULE.some_route_tab2
      base_route: system.admin_content

    Необходимо создать для tab1 дочерние (второго уровня) табы. Для этого добавляем еще парочку определений.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    MYMODULE.example_child_tab1:
      route_name: MYMODULE.some_route_child_tab1
      title: 'MYMODULE subtab 1'
      base_route: MYMODULE.example_child_tab1
      parent_id: MYMODULE.example_parent_tab1
     
    MYMODULE.example_child_tab2:
      route_name: MYMODULE.some_route_child_tab2
      title: 'MYMODULE subtab 2'
      base_route: MYMODULE.example_child_tab1
      parent_id: MYMODULE.example_parent_tab1

    Результат рендера табов.
    табы дочернего уровня

  • options, deriver и class — выполняют аналогичные функции, что и для ссылок меню.

Чтобы сделать дефолтный таб, необходимо чтобы base_route совпадал с именем таба в определении *.links.task.yml файле. Пример записи дефолтного таба ("My example tab 1"):

1
2
3
4
5
6
7
8
9
MYMODULE.example_tab1:
  route_name: MYMODULE.some_route_tab1
  title: 'My example tab 1'
  base_route: MYMODULE.example_tab1
 
MYMODULE.example_tab2:
  route_name: MYMODULE.some_route_tab2
  title: 'My example tab 2'
  base_route: MYMODULE.example_tab1

Также не стоит забывать, что табы будут отображены если их количество не менее двух.
дефолтный таб
Для изменения существующих табов есть специальный хук — hook_menu_local_tasks_alter()[5].

1
2
3
4
5
6
/**
* Implements hook_menu_local_tasks_alter().
*/
function MYMODULE_menu_local_tasks_alter(&$data, $route_name) {
  $data['tabs'][0]['system.admin_content']['#link']['title'] = 'Some title';
}

Actions ссылки

Определения ссылок[6] находится в файле *.links.action.yml.
Структура меню

  • title — заголовок ссылки.
  • route_name — имя роутинга.
  • weight — вес ссылки.
  • appears_on — имя роута из файла *.routing.yml, на странице которого будет выведена ссылка.
  • options, deriver и class — выполняют аналогичные функции, что и для ссылок меню.

Пример action ссылки:

1
2
3
4
5
6
7
MYMODULE.example_action_link:
  route_name: MYMODULE.some_route
  title: 'Example action link'
  weight: 10
  appears_on:
    - MYMODULE.some_another_route
    - system.site_information_settings

Action ссылка на странице настроек сайта.
action ссылка
Actions ссылки также можно альтерить с помощью хука hook_menu_local_actions_alter()[7].

1
2
3
4
5
6
7
8
/**
* Implements hook_menu_local_actions_alter().
*/
function MYMODULE_menu_local_actions_alter(&$local_actions) {
  $local_actions['MYMODULE.example_action_link']['options']['attributes'] = [
    'target' => '_blank',
  ];
}

Контекстные ссылки

Контекстных ссылки определяются в соответствующем файле *.links.contextual.yml[8].
Структура свойств следующая:

  • title — заголовок таба.
  • route_name — имя роутинга.
  • route_parameters — свойство аналогично route_parameters для ссылок меню. Параметры задаются/изменяются при помощи хука hook_contextual_links_alter()[9].
  • localized_options — массив URL параметров. Массив задается/изменяется также через хук hook_contextual_links_alter()[9].
  • group — группа контекстных ссылок, к которой будет отнесена текущая. Например, если группа block, то текущая ссылка будет отображаться в списке контекстных ссылок блока согласно весу.
  • weight — вес ссылки.
  • options, deriver и class — выполняют аналогичные функции, что и для ссылок меню.

Пример контекстной ссылки:

1
2
3
4
MYMODULE.contextual_link:
  title: 'MYMODULE contextual link'
  route_name: dblog.overview
  group: 'block'

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

  1. hook_contextual_links_alter() — позволяет вносить изменения в ссылки до их рендеринга[9].
  2. hook_contextual_links_view_alter() — позволяет вносить изменения в элемент списка ссылок до рендеринга[10].

Пример изменения тайтла ссылки.

1
2
3
4
5
6
/**
* Implements hook_contextual_links_alter().
*/
function MYMODULE_contextual_links_alter(array &$links, $group, array $route_parameters) {
  $links['MYMODULE.example_contextual_link']['title'] = 'Some title';
}

Добавление css класса к списку ссылок ul.

1
2
3
4
5
6
/**
* Implements hook_contextual_links_view_alter().
*/
function MYMODULE_contextual_links_view_alter(&$element, $items) {
  $element['#attributes']['class'][] = 'one';
}

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

Ответ по прошлому заданию

Для получения всех источников новостей, как указано из документации[11], необходимо отправить запрос вида https://newsapi.org/v1/sources. На данном этапе нам не требуется использовать api key, поэтому пока можно не регистрироваться для его получения.
Далее нужно создать файл news.services.yml и разместить его в корне модуля news. Содержимое файла следующее:

1
2
3
4
5
6
7
parameters:
  url: 'https://newsapi.org/v1/sources'
 
services:
  news.sources:
    class: Drupal\news\Services\NewsSources
    arguments: ['%url%']

Используем секцию parameters для указания урла, по которому будем забирать весь список источников.
Следующий шаг — класс сервиса NewsSources. Создаем файл класса и размещаем его по пути относительно корня модуля

1
news/src/Services/NewsSources.php

В конструкторе класса присваиваем урл из секции параметров переменной $url. Symfony 2 не имеет встроенного компонента для отправки запросов на внешние адреса, поэтому запрос будет выполнен с помощью HTTP клиента Guzzle[12], который входит в состав Drupal 8. Код класса следующий:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
namespace Drupal\news\Services;
 
use GuzzleHttp\Client;
 
/**
* Class NewsSources.
*
* @package Drupal\news\Services
*/
class NewsSources {
 
  /**
   * Newsapi url.
   *
   * @var
   */
  protected $url;
 
  /**
   * NewsSources constructor.
   */
  public function __construct($url) {
    $this->url = $url;
  }
 
  /**
   * Returns the list of news sources.
   */
  public function get() {
    $client = new Client();
    $request = $client->request('GET', $this->url);
    $request = \GuzzleHttp\json_decode($request->getBody());
    return $request->sources;
  }
 
}

Теперь остается только вернуть результат запроса в контроллере NewsSettingsController, который отвечает за ответ страницы admin/config/services/news.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace Drupal\news\Controller;
 
/**
* Class NewsSettingsController.
*
* @package Drupal\news\Controller
*/
class NewsSettingsController {
 
  /**
   * Returns settings page.
   */
  public function getSettingsPage() {
    $service = \Drupal::service('news.sources');
    return ['#markup' => '<pre>' . json_encode($service->get(), JSON_PRETTY_PRINT) . '</pre>'];
  }
 
}

В итоге получаем следующий результат.
json источников новостей

Задание по текущему уроку

Добавить контекстную ссылку в блок, который был создан в рамках домашнего задания Урока 4. Заголовок ссылки — "Edit news settings". Контекстная ссылка должна вести на страницу настроек модуля news, т.е. на admin/config/services/news.
Далее необходимо добавить на страницу (admin/config/services/news) два таба:

  1. News block list
  2. Settings

Под дефолтным табом "News block list" нужно разместить action ссылку "Add news block". Роутинги для табов и action ссылки берем фейковые, т.е. роутинги каких-либо админских путей. В дальнейшем эти роутинги будут заменены реальными.

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

  1. https://www.drupal.org/docs/8/api/menu-api - Menu API из официальной документации.
  2. https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-menu-links - определение menu ссылок.
  3. https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php/function/hook_menu_links_discovered_alter/8.2.x - hook_menu_links_discovered_alter() из документации.
  4. https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-local-tasks - определение табов.
  5. https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php/function/hook_menu_local_tasks_alter/8.2.x - альтер табов.
  6. https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-actions - определение actions ссылок.
  7. https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php/function/hook_menu_local_actions_alter/8 - альтер actions ссылок.
  8. https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-contextual-links - определение контекстных ссылок.
  9. https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php/function/hook_contextual_links_alter/8.2.x - alter контекстных ссылок.
  10. https://api.drupal.org/api/drupal/core!modules!contextual!contextual.api.php/function/hook_contextual_links_view_alter/8.2.x - alter списка контекстных ссылок.
  11. https://newsapi.org/ - API для получения новостей из различных источников.
  12. https://github.com/guzzle/guzzle - проект Guzzle на github.com.
  13. Версии программных продуктов, используемых в статье: Drupal 8.3.2

1 Комментарий

Аватар пользователя AK

Очень детально и понятно

Очень детально и понятно описано. Хотя уже разобрался с этой темой, просмотрел её повторно. Drupal'а много не бывает :)
Отдельное спасибо за оглавление на сайдбаре.