Урок 6. Menu API
Опубликовано вт, 23/05/2017 - 00:46
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.
- attributes — ассоциативный массив HTML атрибутов, который будет использоваться при формировании ссылки. Некоторые атрибуты:
Все параметры, кроме 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 ссылка на странице настроек сайта.
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()):
- hook_contextual_links_alter() — позволяет вносить изменения в ссылки до их рендеринга[9].
- 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>']; } } |
В итоге получаем следующий результат.
Задание по текущему уроку
Добавить контекстную ссылку в блок, который был создан в рамках домашнего задания Урока 4. Заголовок ссылки — "Edit news settings". Контекстная ссылка должна вести на страницу настроек модуля news, т.е. на admin/config/services/news.
Далее необходимо добавить на страницу (admin/config/services/news) два таба:
- News block list
- Settings
Под дефолтным табом "News block list" нужно разместить action ссылку "Add news block". Роутинги для табов и action ссылки берем фейковые, т.е. роутинги каких-либо админских путей. В дальнейшем эти роутинги будут заменены реальными.
Дополнительная информация по статье
- https://www.drupal.org/docs/8/api/menu-api - Menu API из официальной документации.
- https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-menu-links - определение menu ссылок.
- 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() из документации.
- https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-local-tasks - определение табов.
- https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php/function/hook_menu_local_tasks_alter/8.2.x - альтер табов.
- https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-actions - определение actions ссылок.
- https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php/function/hook_menu_local_actions_alter/8 - альтер actions ссылок.
- https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-contextual-links - определение контекстных ссылок.
- https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Menu!menu.api.php/function/hook_contextual_links_alter/8.2.x - alter контекстных ссылок.
- https://api.drupal.org/api/drupal/core!modules!contextual!contextual.api.php/function/hook_contextual_links_view_alter/8.2.x - alter списка контекстных ссылок.
- https://newsapi.org/ - API для получения новостей из различных источников.
- https://github.com/guzzle/guzzle - проект Guzzle на github.com.
- Версии программных продуктов, используемых в статье: Drupal 8.3.2
5 Comments
AK - сб, 10/06/2017 - 18:16
Очень детально и понятно
Очень детально и понятно описано. Хотя уже разобрался с этой темой, просмотрел её повторно. Drupal'а много не бывает :)
Отдельное спасибо за оглавление на сайдбаре.
Александр - вс, 14/01/2018 - 15:57
Очень понятно написано.
Очень понятно написано. Спасибо за статью, лайк, подписка :)
Новичок - пт, 12/07/2019 - 15:28
Здравствуйте возник такой
Здравствуйте возник такой вопрос почему здесь работает одна url ссылка news.services.yml,объясните пожалуйста.
Гость - пт, 19/07/2019 - 18:08
это просто как пример, ссылок
это просто как пример, ссылок может быть и больше
Новичок - пн, 22/07/2019 - 22:29
Спасибо большое)
Спасибо большое)