A partir de Drupal 11.1 es posible usar hooks usando clases en lugar de amontonarlos en el archivo .module de un módulo.
Anteriormente, esto podía hacerse usando el módulo Hux. En este post se explica cómo pasar de usar Hux a atributos nativos de hooks del core:
- Desinstalar módulo hux: drush pm:uninstall hux
- Mover clase a la carpeta [módulo]/src/Hook
- En la nueva clase:
- Cambiar el namespace: de "Drupal\[módulo]\Hooks" a "Drupal\[modulo]\Hook"
- Cambiar el namespace del atributo Hook por el del core: de "use Drupal\hux\Attribute\Hook;" a "use Drupal\Core\Hook\Attribute\Hook;"
- Cambiar los atributos Alter por el atributo Hook del core (añadiendo "_alter" al final del hook): de "#[Alter('hook_sin_alter_al_final')]" a "#[Hook('hook_con_alter_al_final')]"
- Limpiar caché: drush cache:rebuild
Ejemplo de cómo quedaría una clase antes y después de la migración
Antes:
<?php
declare(strict_types=1);
namespace Drupal\slx\Hooks;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\hux\Attribute\Alter;
use Drupal\hux\Attribute\Hook;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Misc hooks.
*/
final class MiscHooks implements ContainerInjectionInterface {
use AutowireTrait;
use StringTranslationTrait;
public function __construct(
private RouteMatchInterface $routeMatch,
private RequestStack $requestStack,
) {
}
/**
* Adds the "open in new tab" option for links.
*/
#[Alter('ckeditor5_plugin_info')]
public function ckeditorOpenLinkNewTab(array &$plugin_definitions): void {
assert($plugin_definitions['ckeditor5_link'] instanceof CKEditor5PluginDefinition);
$link_plugin_definition = $plugin_definitions['ckeditor5_link']->toArray();
$link_plugin_definition['ckeditor5']['config']['link']['decorators'][] = [
'mode' => 'manual',
'label' => $this->t('Open in new window'),
'attributes' => [
'target' => '_blank',
],
];
$plugin_definitions['ckeditor5_link'] = new CKEditor5PluginDefinition($link_plugin_definition);
}
/**
* Redirects users to homepage. Inspired from login_redirect_per_role module.
*/
#[Hook('user_login')]
public function loginRedirectHome(AccountInterface $account): void {
// Do not redirect if there is a destination already.
$destination = $this->requestStack->getCurrentRequest()?->query->get('destination');
// Do not redirect when resetting password.
$disallowed_pages = [
'user.reset',
'user.reset.login',
'user.reset.form',
];
if ($destination
|| in_array($this->routeMatch->getRouteName(), $disallowed_pages)
) {
return;
}
$this->requestStack->getCurrentRequest()?->query->set('destination', Url::fromRoute('<front>')->toString());
}
}
Después:
<?php
declare(strict_types=1);
namespace Drupal\slx\Hook;
use Drupal\ckeditor5\Plugin\CKEditor5PluginDefinition;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Miscelaneous hooks.
*/
final class MiscHooks implements ContainerInjectionInterface {
use AutowireTrait;
use StringTranslationTrait;
public function __construct(
private RouteMatchInterface $routeMatch,
private RequestStack $requestStack,
) {
}
/**
* Adds the "open in new tab" option for links.
*/
#[Hook('ckeditor5_plugin_info_alter')]
public function ckeditorOpenLinkNewTab(array &$plugin_definitions): void {
assert($plugin_definitions['ckeditor5_link'] instanceof CKEditor5PluginDefinition);
$link_plugin_definition = $plugin_definitions['ckeditor5_link']->toArray();
$link_plugin_definition['ckeditor5']['config']['link']['decorators'][] = [
'mode' => 'manual',
'label' => $this->t('Open in new window'),
'attributes' => [
'target' => '_blank',
],
];
$plugin_definitions['ckeditor5_link'] = new CKEditor5PluginDefinition($link_plugin_definition);
}
/**
* Redirects users to homepage. Inspired from login_redirect_per_role module.
*/
#[Hook('user_login')]
public function loginRedirectHome(AccountInterface $account): void {
// Do not redirect if there is a destination already.
$destination = $this->requestStack->getCurrentRequest()?->query->get('destination');
// Do not redirect when resetting password.
$disallowed_pages = [
'user.reset',
'user.reset.login',
'user.reset.form',
];
if ($destination
|| in_array($this->routeMatch->getRouteName(), $disallowed_pages)
) {
return;
}
$this->requestStack->getCurrentRequest()?->query->set('destination', Url::fromRoute('<front>')->toString());
}
}
Información adicional
Por el momento, el core no permite reemplazar hooks de otros módulos, como sí permite Hux usando el atributo "ReplaceOriginalHook".
En caso de querer migrar los hooks manteniendo activo el módulo hux, habría que saltar el paso 1 y mantener una copia vacía de la clase (o al menos con los atributos de hux eliminados) en la ubicación original antes de limpiar caché. Después puede borrarse.
Herramienta desarrolladores