Hooks y Overrides de PrestaShop: Guía de referencia para desarrolladores
Referencia para desarrolladores sobre hooks y overrides en PrestaShop — hooks de visualización, de acción, personalizados, sistema de overrides y migración.
Qué son los hooks y por qué son importantes
Los hooks son el sistema de extensión de PrestaShop — el mecanismo mediante el cual los módulos interactúan con la plataforma base sin modificar los archivos del núcleo. Cada módulo que usted instala depende de los hooks para conectarse en el lugar correcto y en el momento adecuado.
Si alguna vez se ha preguntado cómo un módulo “sabe” mostrar algo debajo del precio del producto, o cómo reacciona cuando un cliente realiza un pedido — la respuesta son los hooks.
Los hooks son para PrestaShop lo que los enchufes eléctricos son para una casa. El núcleo proporciona enchufes en ubicaciones específicas, y los módulos se conectan a ellos. Usted no recablea la casa — se enchufa donde necesita energía.
Display hooks vs Action hooks
PrestaShop tiene dos tipos de hooks:
- Display hooks (con prefijo
display) — Puntos de inserción visual. Cuando se disparan, recopilan HTML de los módulos registrados y lo renderizan. Ejemplo:displayProductAdditionalInfoañade contenido debajo del precio del producto. - Action hooks (con prefijo
action) — Notificaciones de eventos. No producen salida — informan a los módulos de que algo ocurrió. Ejemplo:actionCartSavese dispara después de que se actualiza un carrito.
Cómo funciona el Hook Dispatcher
Cuando PrestaShop llega a un punto de hook, llama a Hook::exec(), que busca los módulos registrados en ps_hook_module, los ordena por posición, invoca el método hook de cada módulo con un array $params, y para los display hooks concatena el HTML devuelto.
Hooks esenciales que todo desarrollador debe conocer
Display hooks del Front Office
| Nombre del hook | Cuándo se dispara | Caso de uso típico |
|---|---|---|
displayHeader | Dentro de <head> en cada página | Meta tags, scripts de seguimiento |
displayFooter | Pie de página | Widgets del pie, analítica |
displayHome | Área de contenido de la página de inicio | Banners, productos destacados |
displayProductAdditionalInfo | Debajo de “Añadir al carrito” | Estimaciones de entrega, guías de tallas |
displayShoppingCart | Página de resumen del carrito | Ventas cruzadas, estimadores de envío |
displayOrderConfirmation | Después de realizar el pedido | Seguimiento de conversiones |
displayCustomerAccount | Página “Mi cuenta” | Secciones personalizadas de la cuenta |
displayBanner | Parte superior de la página, sobre el encabezado | Anuncios de ofertas |
Display hooks del Back Office
| Nombre del hook | Cuándo se dispara | Caso de uso típico |
|---|---|---|
displayBackOfficeHeader | <head> del administrador | CSS/JS de administración, notificaciones |
displayAdminOrder | Página de detalle del pedido | Paneles personalizados, integraciónes de envío |
displayAdminProductsExtra | Pestaña del editor de producto | Campos personalizados de producto |
Action hooks
| Nombre del hook | Cuándo se dispara | Caso de uso típico |
|---|---|---|
actionCartSave | Carrito creado o actualizado | Reserva de inventario, analítica |
actionOrderStatusUpdate | Cambio de estado del pedido | Notificaciones, sincronización con ERP |
actionProductUpdate | Producto guardado en el administrador | Sincronización de catálogo externo |
actionCustomerAccountAdd | Nuevo cliente se registra | Email de bienvenida, sincronización con CRM |
actionValidateOrder | Pedido validado | Procesamiento de pago, actualización de stock |
actionFrontControllerSetMedia | El controlador frontal carga recursos | Registrar CSS/JS |
actionAdminControllerSetMedia | El controlador admin carga recursos | Registrar CSS/JS de administración |
Registro de recursos (assets)
Desde PS 1.7, la forma correcta de añadir CSS/JS es mediante el registro de recursos dentro de actionFrontControllerSetMedia:
public function hookActionFrontControllerSetMedia($params)
{
$this->context->controller->registerStylesheet(
'my-module-style',
'modules/' . $this->name . '/views/css/front.css',
['media' => 'all', 'priority' => 150]
);
$this->context->controller->registerJavascript(
'my-module-script',
'modules/' . $this->name . '/views/js/front.js',
['position' => 'bottom', 'priority' => 150]
);
}
Esto permite la optimización CCC (Combine, Compress, Cache) — mucho mejor que inyectar etiquetas sin procesar a través de displayHeader.
Cómo usar hooks en un módulo
Registrar hooks durante la instalación
public function install()
{
return parent::install()
&& $this->registerHook('displayProductAdditionalInfo')
&& $this->registerHook('actionCartSave')
&& $this->registerHook('actionFrontControllerSetMedia');
}
Cada llamada inserta una fila en ps_hook_module. Si olvida registrar, el método hook nunca se ejecutará — aunque el método exista.
Problema frecuente en la depuración: el método hook existe y está correctamente nombrado, pero el módulo nunca se registró en el hook durante la instalación. Si añade un hook después del lanzamiento inicial, necesita un script de actualización o reiniciar el módulo.
Implementar el método hook
Convención de nombres del método: hook + nombre del hook con la primera letra en mayúscula.
public function hookDisplayProductAdditionalInfo($params)
{
$product = $params['product'];
$this->context->smarty->assign([
'delivery_estimate' => $this->calculateDeliveryEstimate($product),
]);
return $this->display(__FILE__, 'views/templates/hook/product-info.tpl');
}
// Action hook — sin valor de retorno
public function hookActionCartSave($params)
{
if (!isset($params['cart'])) {
return;
}
$this->logCartActivity($params['cart']->id);
}
El array $params
El contenido varía según el hook. Los más comunes: $params['cart'], $params['order'], $params['product'], $params['customer']. Para ver qué pasa un hook, añada un registro temporal:
file_put_contents(
_PS_ROOT_DIR_ . '/var/logs/hook_debug.log',
print_r(array_keys($params), true),
FILE_APPEND
);
Cómo encontrar los hooks disponibles
1. Modo depuración
Actívelo en Advanced Parameters → Performance → Debug mode: YES. El front office mostrará los nombres de los hooks en los puntos de inserción. En PS 8+, la barra de herramientas de depuración de Symfony muestra todos los hooks disparados por cada solicitud.
Nunca active el modo depuración en una tienda en producción. Expone datos internos y ralentiza el sitio.
2. Consulta a la base de datos
-- All hooks with "product" in the name
SELECT name, title FROM ps_hook WHERE name LIKE '%product%' ORDER BY name;
-- Which modules are on a specific hook
SELECT m.name, hm.position FROM ps_hook_module hm
JOIN ps_hook h ON h.id_hook = hm.id_hook
JOIN ps_module m ON m.id_module = hm.id_module
WHERE h.name = 'displayProductAdditionalInfo'
ORDER BY hm.position;
3. Buscar en plantillas y código fuente
# Display hooks in theme templates
grep -rn "{hook " themes/your-theme/templates/
# Action hooks in PHP core
grep -rn "Hook::exec" classes/ controllers/ src/
Crear hooks personalizados
Cuando los hooks integrados no se disparan en el punto exacto que usted necesita, cree los suyos propios:
// Dispatch a custom hook
$hookResult = Hook::exec('actionMyModuleBeforeProcess', [
'order_id' => $orderId,
'custom_data' => $myData,
]);
// For display hooks — true returns array per module
$extraHtml = Hook::exec('displayMyModuleExtraContent', [
'product' => $product,
], null, true);
Regístrelo en install() como lo haría con cualquier hook. Para darle un nombre descriptivo en Posiciones:
$hook = new Hook();
$hook->name = 'displayMyModuleExtraContent';
$hook->title = 'My Module - Extra Content Area';
$hook->add();
Cree hooks personalizados cuando su módulo proporcione puntos de extensión que otros módulos necesiten. No los cree para lógica exclusivamente interna — utilice métodos regulares.
El sistema de override
Los overrides reemplazan clases y controladores del núcleo sin modificar los archivos originales. Eran el método principal de personalización en PS 1.5/1.6, antes de que los hooks fueran completos.
Cómo funcionan los overrides
Un override extiende una clase del núcleo y reemplaza métodos específicos. El autoloader comprueba primero override/:
// override/classes/Cart.php
class Cart extends CartCore
{
public function getOrderTotal($with_taxes = true, $type = Cart::BOTH,
$products = null, $id_carrier = null, $use_cache = false)
{
$total = parent::getOrderTotal($with_taxes, $type, $products, $id_carrier, $use_cache);
if ($this->hasSpecialProducts()) {
$total += $this->calculateSurcharge();
}
return $total;
}
}
La estructura de directorios refleja la del núcleo: override/classes/, override/controllers/front/, override/controllers/admin/.
Override vs Hook
| Escenario | Enfoque |
|---|---|
| Añadir contenido a una página | Display hook |
| Reaccionar a un evento | Action hook |
| Añadir CSS/JS | Hook de registro de recursos |
| Cambiar un cálculo del núcleo | Override (si no existe un hook) |
| Modificar una consulta SQL del núcleo | Override (si no existe un hook) |
Los problemas de los overrides
- Conflictos: Dos módulos no pueden hacer override del mismo método. El segundo falla al instalarse.
- Rotura en actualizaciones: Las firmas de los métodos del núcleo cambian entre versiones. Un override para PS 1.7.7 puede fallar en la 1.7.8.
- Comportamiento oculto: Los overrides son invisibles desde el Back Office, lo que dificulta la depuración.
- Corrupción del índice: El mapeo de
class_index.phppuede corromperse, rompiendo toda la tienda.
El equipo central de PrestaShop ha estado desaconsejando activamente los overrides desde la versión 1.7. Si se encuentra a punto de usar un override, primero compruebe si existe un hook o una alternativa basada en Symfony.
Alternativas a los overrides (PS 8+)
Decoradores de servicios Symfony
Envuelva servicios del núcleo con su propia lógica — múltiples decoradores pueden encadenarse sin conflictos:
# modules/mymodule/config/services.yml
services:
mymodule.decorated_calculator:
class: MyModule\Service\CalculatorDecorator
decorates: 'prestashop.core.cart.calculator'
arguments:
- '@mymodule.decorated_calculator.inner'
Listeners de eventos Doctrine
Reaccione a cambios en las entidades sin hacer override de ObjectModel:
# modules/mymodule/config/services.yml
services:
mymodule.product_listener:
class: MyModule\EventListener\ProductListener
tags:
- { name: doctrine.event_listener, event: postUpdate }
Comandos y consultas CQRS
Enganche con las operaciónes de administración mediante manejadores de comandos/consultas. Es el enfoque más moderno y requiere familiaridad con la arquitectura Symfony de PrestaShop.
Resolver conflictos de override
Cuando dos módulos hacen override del mismo método, verá lo siguiente:
The method Cart::getOrderTotal is already overridden by module "othermodule".
La solución es la fusión manual — combine la lógica de ambos módulos en un único archivo override. Después de editar, reconstruya el índice de clases:
# Delete class index — PrestaShop rebuilds automátically
rm var/cache/prod/class_index.php
rm var/cache/dev/class_index.php
La fusión manual de overrides es frágil. Cuando cualquiera de los módulos se actualice, es posible que necesite volver a fusionar. Documente qué fusionó y por qué.
Si un módulo utiliza un override donde un hook funcionaría, contacte al desarrollador y solicite una solución basada en hooks.
Prioridad de hooks y orden de ejecución
Cuando múltiples módulos se registran en el mismo display hook, la posición determina el orden. Gestione esto en Design → Positions donde puede arrastrar y soltar módulos, desengancharlos o trasplantarlos a diferentes hooks.
Trasplante
Mueva un módulo de un hook a otro mediante Design → Positions → Transplant a Module. No todos los trasplantes se renderizan correctamente — depende de las plantillas del módulo.
Posicionamiento programático
// In install() — move to position 1
$this->updatePosition($this->getHookId('displayHome'), false, 1);
Depuración de hooks
Comprobaciones sistemáticas
-- Is the module registered on the hook?
SELECT m.name, hm.position FROM ps_hook_module hm
JOIN ps_hook h ON h.id_hook = hm.id_hook
JOIN ps_module m ON m.id_module = hm.id_module
WHERE h.name = 'displayProductAdditionalInfo' AND m.name = 'your_module_name';
-- Is the module active?
SELECT name, active FROM ps_module WHERE name = 'your_module_name';
Problemas comunes
| Síntoma | Causa | Solución |
|---|---|---|
| El método nunca se llama | No está registrado en el hook | Compruebe ps_hook_module; reinicie el módulo |
| Devuelve cadena vacía | Plantilla no encontrada | Verifique la ruta de la plantilla, active los errores |
| Se dispara en páginas incorrectas | Sin comprobación de página | Añada instanceof ProductController |
| Se dispara dos veces | Registrado dos veces | Compruebe si hay filas duplicadas |
| $params['product'] es un array | PS 1.7+ usa arrays | Use $params['product']['id_product'] |
Perfilado de hooks
Active el perfilado para ver los tiempos por hook:
// PS 1.7 — defines.inc.php
define('_PS_DEBUG_PROFILING_', true);
// PS 8+ — .env.local
APP_DEBUG=1
APP_ENV=dev
Cambios en PrestaShop 9
Nuevos hooks
PS 9 añade hooks en áreas que anteriormente requerían overrides: extensiones del formulario de producto en el administrador, aplicación de reglas de precios del carrito, operaciónes de recursos API y proceso de envío de emails.
Hooks obsoletos
Los hooks vinculados a controladores de administración heredados están obsoletos a medida que esos controladores migran a Symfony. Consulte _PS_DEPRECATED_HOOKS_ para ver la lista completa.
Futuro del sistema de override
Los overrides aún funcionan para las clases heredadas de ObjectModel, pero los nuevos servicios Symfony y los controladores de administración no pueden usarlos. El sistema de override finalmente se eliminará cuando la migración a Symfony se complete.
Si está comenzando un nuevo módulo hoy, constrúyalo exclusivamente con hooks y servicios Symfony. Cada override que escriba ahora es deuda técnica que tendrá que eliminar más adelante.
Referencia rápida
// Register hooks (install)
$this->registerHook(['displayHeader', 'actionCartSave']);
// Check if hook exists
$hookId = Hook::getIdByName('displayMyCustomHook');
// Get modules on a hook
$modules = Hook::getHookModuleExecList('displayHeader');
// Execute hook manually
$output = Hook::exec('displayMyHook', ['key' => 'value']);
// Conditional execution
public function hookDisplayHeader($params) {
if (!($this->context->controller instanceof ProductController)) return '';
return $this->display(__FILE__, 'views/templates/hook/header.tpl');
}
-- All hooks a module is registered on
SELECT h.name, hm.position FROM ps_hook_module hm
JOIN ps_hook h ON h.id_hook = hm.id_hook
JOIN ps_module m ON m.id_module = hm.id_module
WHERE m.name = 'your_module_name' ORDER BY h.name;
-- Hooks with no modules registered
SELECT h.name FROM ps_hook h
LEFT JOIN ps_hook_module hm ON h.id_hook = hm.id_hook
WHERE hm.id_hook IS NULL ORDER BY h.name;
Domine primero los hooks — cubren la gran mayoría de los casos de uso. Aprenda los overrides para comprender el código heredado y resolver conflictos. Para PrestaShop 8+, invierta en alternativas basadas en Symfony. El ecosistema avanza, y los hooks lideran el camino.
More guides available
Browse our knowledge base for more practical PrestaShop tutorials, or reach out if you need help.