TL;DR: Hux es un módulo que permite implementar los hooks de Drupal de manera orientada a objetos de forma sencilla.
Los hooks de Drupal, esas funciones que permiten modificar el comportamiento de Drupal al gusto, y que a la mínima que se tenga un proyecto grande se puede terminar con archivos .module gigantescos... y a finales de 2023 todavía no hay una intención clara de cambiarlos por algo mejor.
Aunque existen maneras de abstraer la funcionalidad de los hooks tanto en el propio core (llamando a métodos de clases con ClassResolver) como en módulos contribuidos (por ejemplo Hook Event Dispatcher, usando eventos en lugar de hooks), existe el módulo Hux que permite directamente convertir métodos normales de clases en hooks.
Funcionamiento de Hux
El procedimiento para implementar hooks con Hux es el siguiente:
- Crear un método en una clase dentro de la carpeta Hooks de un módulo custom
- Crear una función que reciba los mismos argumentos que el hook
- Implementar el atributo #Hook([nombre del hook]) / #Alter([nombre del hook_alter])
- Limpiar caché, en caso de que la clase sea nueva
Nota: Hux es complementario al sistema de hooks de Drupal, lo que significa que los hooks normales que ya existiesen o los nuevos que se creen de la manera normal, seguirán funcionando.
Detalle de las implementaciones
Hooks normales
Ejemplo con hook_views_pre_build:
<?php
declare(strict_types = 1);
namespace Drupal\mymodule\Hooks;
use Drupal\hux\Attribute\Hook;
/**
* OOP version of views hooks.
*/
final class ViewsHooks {
/**
* Do something.
*/
#[Hook('views_pre_build')]
public function descriptiveFunctionName(ViewExecutable $view): void {
// Code here...
}
}
Importante: El nombre del hook hay que ponerlo sin el prefijo "hook_".
Hooks de alteración
Ejemplo con hook_form_alter:
<?php
declare(strict_types = 1);
namespace Drupal\mymodule\Hooks;
use Drupal\Core\Form\FormStateInterface;
use Drupal\hux\Attribute\Alter;
/**
* OOP version of form hooks.
*/
final class FormHooks {
/**
* Do something.
*/
#[Alter('form')]
public function descriptiveFunctionName(array &$form, FormStateInterface $form_state, string $form_id): void {
// Code here...
}
}
Importante: El nombre del hook hay que ponerlo sin el prefijo "hook_" ni el sufijo "_alter".
Reemplazar un hook existente de otro módulo
Ejemplo con hook_user_login del módulo user:
<?php
declare(strict_types = 1);
namespace Drupal\mymodule\Hooks;
use Drupal\Core\Form\FormStateInterface;
use Drupal\hux\Attribute\ReplaceOriginalHook;
/**
* OOP version of user hooks.
*/
final class UserHooks {
/**
* Do something.
*/
#[ReplaceOriginalHook(hook: 'user_login', moduleName: 'user')]
public function userFunction(AccountInterface $account) {
// Code here...
}
}
Importante: El nombre del hook hay que ponerlo sin el prefijo "hook_".
Peso / prioridad
Para especificar la prioridad en caso de que se tengan varias implementaciones del mismo hook se usa el atributo "priority":
#[Hook('entity_access', priority: 100)]
public function entityAccess1(...) {
}
#[Hook('entity_access', priority: 200)]
public function entityAccess2(...) {
}
Implementar varios hooks con la misma función
Simplemente hay que implementar un array de atributos Hook:
#[
Hook('entity_insert'),
Hook('entity_update'),
Hook('entity_delete'),
]
public function myEntityAccess(EntityInterface $entity): void {
}
Inyección de dependencias
Al usarse clases normales, puede usarse la inyección de dependencias haciendo que la clase implemente la interfaz ContainerInjectionInterface:
<?php
declare(strict_types = 1);
namespace Drupal\mymodule\Hooks;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\hux\Attribute\Hook;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* OOP version of views hooks.
*/
final class ViewsHooks implements ContainerInjectionInterface {
/**
* Class constructor.
*/
public function __construct(
private EntityTypeManagerInterface $entityTypeManager,
) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
);
}
/**
* Do something.
*/
#[Hook('views_pre_build')]
public function descriptiveFunctionName(ViewExecutable $view): void {
// Code here...
$this->entityTypeManager->getStorage('node')->load(...);
}
}
Modo rendimiento
Hux permite una ligera mejora de rendimiento al cachear las definiciones de hooks. En producción, lo ideal es añadar lo siguiente al archivo services.yml:
parameters:
hux:
optimize: true
Limitaciones
Por el momento no es posible implementar los hooks_preprocess usando Hux.