Drupal 11 introduce una mejora significativa en la forma de implementar hooks, permitiendo definirlos mediante clases y siguiendo principios de programación orientada a objetos.
Este nuevo enfoque convive con el sistema tradicional, lo que permite mantener compatibilidad con código legacy y realizar una migración progresiva sin necesidad de reescribir todo el proyecto de golpe.
Este cambio no solo moderniza la sintaxis, sino que también mejora la organización del código, facilita la inyección de dependencias y permite una arquitectura más mantenible y escalable.
El problema de los hooks tradicionales
function mymodule_node_insert(\Drupal\node\NodeInterface $node) {
\Drupal::logger('mymodule')->notice('Nodo creado: ' . $node->id());
}Funcionan bien, pero en proyectos reales suelen acabar siendo difíciles de testear, con lógica desordenada y fuertemente acoplados a servicios globales.
La nueva forma: hooks en clases (OOP)
namespace Drupal\mymodule\Hook;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\node\NodeInterface;
class NodeHooks {
#[Hook('node_insert')]
public function onInsert(NodeInterface $node) {
\Drupal::logger('mymodule')->notice('Nodo creado: ' . $node->id());
}
}¿Dónde va este código?
La estructura recomendada dentro de un módulo custom es:
modules/custom/mymodule/src/Hook/NodeHooks.php
Detalles importantes:
- Carpeta: src/Hook/
- Namespace: Drupal\mymodule\Hook
- Clase: libre (NodeHooks, FormHooks, etc.)
Drupal autodetecta estas clases como servicios gracias a los atributos, por lo que en muchos casos no es necesario registrarlas manualmente.
Reutilización de lógica
Una de las ventajas más interesantes es la posibilidad de reutilizar lógica fácilmente añadiendo múltiples atributos.
#[Hook('node_insert')]
#[Hook('node_update')]
public function onSave(NodeInterface $node) {
// lógica común
}Esto reduce duplicación y centraliza comportamiento relacionado.
Legacy Hooks
Durante la transición a hooks basados en clases en Drupal 11, se recomienda mantener un hook procedimental “shim” en el archivo .module. Esto permite empezar a usar hooks OOP incluso si el módulo aún no está desplegado en Drupal 11.1.
En lugar de duplicar lógica, el hook tradicional puede delegar directamente en la clase OOP correspondiente, lo que simplifica la migración y mantiene compatibilidad.
use Drupal\node\NodeInterface;
#[LegacyHook]
function services_hooks_example_node_insert(NodeInterface $node) {
\Drupal::service('services_hooks_example.node_hooks')->nodeInsert($node);
}El atributo #[LegacyHook] indica a Drupal que este hook es legado, evitando su ejecución si ya existe una implementación OOP equivalente.
Esto permite mantener el comportamiento actual mientras se habilita la testabilidad de la lógica en la nueva arquitectura. Cuando ya no sea necesario el soporte legacy, basta con eliminar estos hooks sin reescribir lógica adicional.
Si no necesitas soporte legacy, puedes mejorar el rendimiento desactivando el escaneo de hooks procedimentales desde el archivo .services.yml:
parameters:
services_hooks_example.skip_procedural_hook_scan: trueEsto evita que Drupal escanee, registre o ejecute hooks legacy, reduciendo carga y mejorando rendimiento.
Importante
no debes desactivar este soporte si tu módulo necesita compatibilidad con Drupal < 11.0.0.
Drupal 11 no elimina los hooks tradicionales, pero introduce una forma más estructurada y moderna de trabajarlos.
Este enfoque mejora la organización del código, la testabilidad, la reutilización de lógica y la transición desde legacy sin romper nada.
En la práctica, no es solo un cambio de sintaxis, sino una evolución hacia una arquitectura más limpia y mantenible.