Knowledge Base Guide

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: displayProductAdditionalInfo añ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: actionCartSave se 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 hookCuándo se disparaCaso de uso típico
displayHeaderDentro de <head> en cada páginaMeta tags, scripts de seguimiento
displayFooterPie de páginaWidgets del pie, analítica
displayHomeÁrea de contenido de la página de inicioBanners, productos destacados
displayProductAdditionalInfoDebajo de “Añadir al carrito”Estimaciones de entrega, guías de tallas
displayShoppingCartPágina de resumen del carritoVentas cruzadas, estimadores de envío
displayOrderConfirmationDespués de realizar el pedidoSeguimiento de conversiones
displayCustomerAccountPágina “Mi cuenta”Secciones personalizadas de la cuenta
displayBannerParte superior de la página, sobre el encabezadoAnuncios de ofertas

Display hooks del Back Office

Nombre del hookCuándo se disparaCaso de uso típico
displayBackOfficeHeader<head> del administradorCSS/JS de administración, notificaciones
displayAdminOrderPágina de detalle del pedidoPaneles personalizados, integraciónes de envío
displayAdminProductsExtraPestaña del editor de productoCampos personalizados de producto

Action hooks

Nombre del hookCuándo se disparaCaso de uso típico
actionCartSaveCarrito creado o actualizadoReserva de inventario, analítica
actionOrderStatusUpdateCambio de estado del pedidoNotificaciones, sincronización con ERP
actionProductUpdateProducto guardado en el administradorSincronización de catálogo externo
actionCustomerAccountAddNuevo cliente se registraEmail de bienvenida, sincronización con CRM
actionValidateOrderPedido validadoProcesamiento de pago, actualización de stock
actionFrontControllerSetMediaEl controlador frontal carga recursosRegistrar CSS/JS
actionAdminControllerSetMediaEl controlador admin carga recursosRegistrar 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

EscenarioEnfoque
Añadir contenido a una páginaDisplay hook
Reaccionar a un eventoAction hook
Añadir CSS/JSHook de registro de recursos
Cambiar un cálculo del núcleoOverride (si no existe un hook)
Modificar una consulta SQL del núcleoOverride (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.php puede 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íntomaCausaSolución
El método nunca se llamaNo está registrado en el hookCompruebe ps_hook_module; reinicie el módulo
Devuelve cadena vacíaPlantilla no encontradaVerifique la ruta de la plantilla, active los errores
Se dispara en páginas incorrectasSin comprobación de páginaAñada instanceof ProductController
Se dispara dos vecesRegistrado dos vecesCompruebe si hay filas duplicadas
$params['product'] es un arrayPS 1.7+ usa arraysUse $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.

Cargando...
Volver arriba