Skip to content

Commit

Permalink
Fri Mar 31 20:59:16 CEST 2023
Browse files Browse the repository at this point in the history
  • Loading branch information
yenyasinn committed Mar 31, 2023
1 parent 1f96229 commit 8b12696
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 3 deletions.
118 changes: 118 additions & 0 deletions _i18n/en/_posts/2023-04-01-drupal-hooks-alternatives.md
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 3 additions & 3 deletions _i18n/ru/_posts/2019-11-04-event-subscriber.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
121 changes: 121 additions & 0 deletions _i18n/ru/_posts/2023-04-01-drupal-hooks-alternatives.md
Original file line number Diff line number Diff line change
@@ -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
<?php

namespace Drupal\example\Hooks;

/**
* Examples.
*/
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');
}
}
```

Как вы видите все выглядит довольно просто - файл будет найден автоматически если будет размещен в example/src/Hooks папке. Для определения хука используется PHP аннотация. Если класс реализует ContainerInjectionInterface, то можно подключить любые сервисы через внедрение зависимостей.

Hux не умеет работать с шаблонами. Так что preprocess функции придется определять как обычно. Но вы можете использовать любые хуки ядра и модулей. Также есть поддержка весов для изменения порядка выполнения хуков и возможность заменять реализацию хуков в модулях на свою реализацию.

## Что в итоге?

Основная проблема “Hook Event Dispatcher” и “Hux” это то, что **они декорируют стандартный сервис module_handler. Ядро Drupal как обычно вызывает хуки, а этим модулям приходится поддерживать как стандартную реализацию хуков, так и свою собственную**, добавляя сложность и не делая всю систему быстрее. И если события из “Hook Event Dispatcher” вы можете вызвать используя Event Dispatcher, то для Hux вам все равно придется вызывать хуки. Если завтра от хуков откажутся, то Hux окажется бесполезным. Хотя, в данный момент пользоваться ими удобнее чем событиями.

Для себя я решил, что при разработке новых модулей, в случае когда нужна будет возможность расширения функционала, я буду **реализовывать события**. В любом случае от событий мы никуда уже не денемся и в долгосрочной перспективе проще будет поддерживать именно их, чем переходить с хуков на события. Но для существующих хуков в Drupal 10 я бы рекомендовал **использовать стандартный функционал**, без установки “Hook Event Dispatcher” или “Hux” с их накладными расходами и дополнительным усложнением. По-сути эти модули являются попыткой исправить архитектуру Drupal. Но чтобы решить проблему хуков эффективно нужно сделать это именно в ядре.

**Ссылки:**

* [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)

0 comments on commit 8b12696

Please sign in to comment.