diff --git a/_i18n/en/_posts/2023-04-01-drupal-hooks-alternatives.md b/_i18n/en/_posts/2023-04-01-drupal-hooks-alternatives.md new file mode 100644 index 0000000..aa3e1e0 --- /dev/null +++ b/_i18n/en/_posts/2023-04-01-drupal-hooks-alternatives.md @@ -0,0 +1,118 @@ +--- +layout: post +title: "Alternatives of hook system in Drupal 10" +date: 2023-04-01 10:00:00 +0000 +categories: Drupal API +canonical_url: https://www.enik.pro/drupal/api/2023/04/01/drupal-hooks-alternatives.html +--- +A system of hooks was implemented in Drupal to change the behavior of the code. It implements the [“Mediator”](/drupal/architecture/2021/01/10/patterns.html) design pattern in procedural programming and provides a single interface for communication of different parts of the system. + +Time does not stand still and the procedural approach in Drupal versions up to 8 has been replaced by an object-oriented approach. Drupal 8 is built on top of the Symfony framework which already has an implementation of the Mediator pattern in the symfony/event-dispatcher library. Thus, in the Drupal core, there are two parallel systems that provide the ability for components to communicate with each other - hooks and [events](/en/drupal/api/2019/11/04/event-subscriber.html). + +Why are there currently two, in fact, duplicate systems, and what are the alternatives? + +## Hooks + +As already mentioned, Drupal 10 inherited hooks from older versions. They were a distinctive feature of Drupal, and when there was a migration to Symfony, they were not completely cut out and left as part of the Drupal identity, which was familiar and understandable to programmers. In order to use such a powerful Symfony tool as services and dependency injection in procedural code, we had to create the \Drupal class - a wrapper for a static service container. In fact, we only need the \Drupal class for hooks and template preprocesses, because in classes, instead of \Drupal, we use dependency injection to get services. Thus, while supporting hooks, we need to support additional functionality to be able to use services in hooks, which complicates the Drupal core. + +At this time, many modules use core's hooks and define hooks themselves. Therefore, since they were left in Drupal 8, so far it is quite difficult to get rid of them. They are still the main way to extend and change the functionality of Drupal. But there are many questions about the use of hooks. It is clear that they are a rudiment in OOP. + +## Events and "Hook Event Dispatcher" + +There is a strange situation in Drupal at the moment where we need to use hooks to extend certain functionality (for example `hook_form_alter()` to change a form behaviour) and for others we need to use events (for example changing of existing route). What is based on Symfony components is changed through events, and what is implemented in Drupal is changed through hooks. Pretty inconvenient, isn't it? + +An attempt to get rid of hooks was made in Drupal 8, then it was postponed until Drupal 9, but they are still present in Drupal 10. Drupal core does not provide events that we can use to replace hooks. But fortunately there is a module [Hook Event Dispatcher](https://www.drupal.org/project/hook_event_dispatcher) that provides events similar to hooks. + +Для изменения формы поиска через хуки нам достаточно кода: + +We only need the code to change the search form through hooks: + +```php +/** + * Implements hook_form_FORM_ID_alter(). + */ +function example_form_search_block_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { + $form['keys']['#attributes']['placeholder'] = t('Search'); +} + +``` + +We need to define a listener first to use an event from the “Hook Event Dispatcher” module: + +```yaml +services: + example.form_subscribers: + class: Drupal\example\ExampleFormEventSubscribers + tags: + - { name: event_subscriber } +``` + +And then implement it: + +```php +class ExampleFormEventSubscribers implements EventSubscriberInterface { + + /** + * Alter search form. + * + * @param \Drupal\core_event_dispatcher\Event\Form\FormIdAlterEvent $event + * The event. + */ + public function alterSearchForm(FormIdAlterEvent $event): void { + $form = &$event->getForm(); + // Add placeholder. + $form['keys']['#attributes']['placeholder'] = $this->t('Search'); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + 'hook_event_dispatcher.form_search_block_form.alter' => 'alterSearchForm', + ]; + } +} +``` + +Also “Hook Event Dispatcher” can be used for template theming - it provides preprocess events for core templates. + +If you use some module in your project that defines its own hooks or templates, then you have to implement events for these hooks in your project yourself, which, of course, does not make your life easier. Code above clearly shows how much less code is needed for a hook than for an event. + +But events have advantages over hooks: +* Easier to determine the sequence of events. +* Events can prevent the execution of subsequent events. +* Ability to define listeners dynamically. + +## Hux + +[Hux](https://www.drupal.org/project/hux) is a module that provides the ability to combine dependency injection and OOP with ease of use. + +The example above with the search form change in Hux would look like this: + +```php +namespace Drupal\example\Hooks; + +final class ExampleHooks { + + #[Alter('form_system_site_information_settings')] + public function formSystemSiteInformationSettingsAlter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { + $form['keys']['#attributes']['placeholder'] = t('Search'); + } +} +``` + +As you can see, everything looks quite simple - the file will be found automatically if placed in the example/src/Hooks folder. A PHP annotation is used to define a hook. If a class implements ContainerInjectionInterface, then any services can be connected via dependency injection. + +Hux can not work with templates. So the preprocess functions have to be defined as usual. But you can use any core's and module's hooks. There is also support for weights to change the order of execution of hooks and the ability to replace the implementation of hooks in modules with your own implementation. + +## What is the conclusion? + +The main problem with "Hook Event Dispatcher" and "Hux" is that **they decorate the standard module_handler service. The Drupal core calls hooks as usual, and these modules have to maintain the standard hook implementation and their own**, adding complexity and not making the whole system faster. You can call events from the “Hook Event Dispatcher” using the Event Dispatcher, but for Hux you still have to call hooks. If hooks are dropped tomorrow, then Hux will be useless. Although, at the moment it is more convenient to use them than events. + +For myself, I decided to **implement events** in developing of new modules, when the possibility of expanding the functionality is needed. In any case, we will not go anywhere from events, and in the long run it will be easier to support them than to switch from hooks to events. But for existing hooks in Drupal 10, I would recommend **use the standard functionality**, without installing a “Hook Event Dispatcher” or “Hux” with their overhead and added complexity. Essentially, these modules are an attempt to fix the Drupal architecture. But in order to solve the problem of hooks effectively, it has to be done in the Drupal core. + +**Links:** + +* [Use Symfony EventDispatcher for hook system](https://www.drupal.org/project/drupal/issues/1509164) +* [Add events for matching entity hooks](https://www.drupal.org/node/2551893) diff --git a/_i18n/ru/_posts/2019-11-04-event-subscriber.md b/_i18n/ru/_posts/2019-11-04-event-subscriber.md index b267780..dfc6b31 100644 --- a/_i18n/ru/_posts/2019-11-04-event-subscriber.md +++ b/_i18n/ru/_posts/2019-11-04-event-subscriber.md @@ -1,9 +1,9 @@ --- layout: post -title: "Разбираем систему событий в Drupal 8" -date: 2019-11-04 10:00:00 +0000 +title: "Разбираем систему событий в Drupal 8" +date: 2019-11-04 10:00:00 +0000 categories: ru Drupal API -canonical_url: https://www.enik.pro/ru/drupal/certification/2019/10/16/acquia-certification.html +canonical_url: https://www.enik.pro/ru/drupal/api/2019/11/04/event-subscriber.html --- ## Для чего нам Event Subscriber diff --git a/_i18n/ru/_posts/2023-04-01-drupal-hooks-alternatives.md b/_i18n/ru/_posts/2023-04-01-drupal-hooks-alternatives.md new file mode 100644 index 0000000..fbe6423 --- /dev/null +++ b/_i18n/ru/_posts/2023-04-01-drupal-hooks-alternatives.md @@ -0,0 +1,121 @@ +--- +layout: post +title: "Альтернативы системе хуков в Drupal 10" +date: 2023-04-01 10:00:00 +0000 +categories: ru Drupal API +canonical_url: https://www.enik.pro/ru/drupal/api/2023/04/01/drupal-hooks-alternatives.html +--- +Для изменения поведения кода в Drupal была реализована система хуков. По своей сути она является реализацией шаблона проектирования [“Посредник”](/ru/drupal/architecture/2021/01/10/patterns.html) в процедурном программировании и предоставляет единый интерфейс для “общения” разных частей системы. + +Время не стоит на месте и на смену процедурному подходу в Drupal версиях до 8 пришел объектно-ориентированный. Drupal 8 построен на основе фреймворка Symfony в котором уже есть реализация шаблона “Посредник” в библиотеке symfony/event-dispatcher. Таким образом в ядре Drupal существуют две параллельные системы предоставляющие возможность для коммуникации компонентов друг с другом - хуки и [события](/ru/drupal/api/2019/11/04/event-subscriber.html). + +Почему же в данное время существуют две, по-сути дублирующие системы, и какие есть альтернативы? + +## Хуки + +Как уже было сказано хуки достались Drupal 10 в наследство от более старых версий. Они являлись отличительной особенностью Drupal и когда была миграция на Symfony, то их не стали полностью вырезать и оставили как часть идентичности Drupal, которая была привычна и понятна программистам. Для того, чтобы использовать в процедурном коде такой мощный инструмент Symfony как сервисы и внедрение зависимостей, пришлось создавать класс \Drupal - оболочку статичного контейнера сервисов. По-сути, нам нужен класс \Drupal только ради хуков и препроцессов шаблонов потому, что в классах вместо \Drupal мы используем внедрение зависимостей для получения сервисов. Таким образом, поддерживая хуки нам нужно поддерживать дополнительный функционал для возможности использования в хуках сервисов, что усложняет ядро Drupal. + +В данное время многие модули используют хуки ядра и сами определяют хуки. Поэтому раз их оставили в Drupal 8, то пока что избавиться от них просто не получается. Они так и являются основным способом расширения и изменения функционала Drupal. Но возникает много вопросов по поводу использования хуков т.к. понятно что они являются рудиментом в ООП. + +## События и Hook Event Dispatcher + +В данное время в Drupal сложилась странная ситуация, что для расширения определенного функционала нам нужно использовать хуки (например `hook_form_alter()` для изменения работы формы), а для другого - события (например изменение существующего пути). То, что основано на компонентах Symfony изменяется через события, а то, что реализовано в Drupal - через хуки. Довольно таки неудобно, не правда ли? + +Попытка избавиться от хуков была предпринята еще в Drupal 8, затем это отложили до Drupal 9, но в Drupal 10 они все также присутствуют. Ядро Drupal не предоставляет события которые мы бы могли использовать для замены хуков. Но к счастью есть модуль [Hook Event Dispatcher](https://www.drupal.org/project/hook_event_dispatcher), который предоставляет события аналогичные хукам. + +Для изменения формы поиска через хуки нам достаточно кода: + +```php +/** + * Implements hook_form_FORM_ID_alter(). + */ +function example_form_search_block_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { + $form['keys']['#attributes']['placeholder'] = t('Search'); +} + +``` + +Для использования события из модуля “Hook Event Dispatcher” нам нужно вначале определить подписчик: + +```yaml +services: + example.form_subscribers: + class: Drupal\example\ExampleFormEventSubscribers + tags: + - { name: event_subscriber } +``` + +А затем реализовать его: + +```php +class ExampleFormEventSubscribers implements EventSubscriberInterface { + + /** + * Alter search form. + * + * @param \Drupal\core_event_dispatcher\Event\Form\FormIdAlterEvent $event + * The event. + */ + public function alterSearchForm(FormIdAlterEvent $event): void { + $form = &$event->getForm(); + // Add placeholder. + $form['keys']['#attributes']['placeholder'] = $this->t('Search'); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents(): array { + return [ + 'hook_event_dispatcher.form_search_block_form.alter' => 'alterSearchForm', + ]; + } +} +``` + +Также “Hook Event Dispatcher” может использоваться для темизации шаблонов - он предоставляет preprocess события для шаблонов ядра. + +Если вы используете в своем проекте какой-то модуль, который определяет свои хуки или шаблоны, то вам придется реализовать события для этого хука в своем проекте самостоятельно, что, конечно, не сделает вашу жизнь проще. Также наглядно видно насколько меньше кода нужно для хука чем для события. + +Но у событий есть преимущества над хуками: +* Проще определять последовательность выполнения событий. +* События могут блокировать выполнение последующих событий. +* Возможность определять слушателей динамически. + +## Hux + +[Hux](https://www.drupal.org/project/hux) это модуль, который предоставляет возможность сочетать внедрение зависимостей и ООП с простотой использования. + +Пример выше с изменением формы поиска в Hux будет выглядеть вот так: + +```php +