Hook e Override PrestaShop: Guida di riferimento per sviluppatori
Riferimento su hook e override PrestaShop — display hook, action hook, hook personalizzati, sistema di override e migrazione a pattern moderni.
Cosa sono gli hook e perché sono importanti
Gli hook sono il sistema di estensione di PrestaShop — il meccanismo attraverso il quale i moduli interagiscono con la piattaforma core senza modificare i file principali. Ogni modulo che installate si affida agli hook per collegarsi nel punto giusto al momento giusto.
Se vi siete mai chiesti come un modulo “sappia” di dover visualizzare qualcosa sotto il prezzo del prodotto, o come reagisca quando un cliente effettua un ordine — la risposta sono gli hook.
Gli hook sono per PrestaShop ciò che le prese elettriche sono per una casa. Il core fornisce prese in posizioni specifiche, e i moduli vi si collegano. Non ricablate la casa — vi collegate dove avete bisogno di corrente.
Display Hook vs Action Hook
PrestaShop ha due tipi di hook:
- Display hook (con prefisso
display) — Punti di inserimento visuale. Quando vengono attivati, raccolgono l’HTML dai moduli registrati e lo renderizzano. Esempio:displayProductAdditionalInfoaggiunge contenuto sotto il prezzo del prodotto. - Action hook (con prefisso
action) — Notifiche di eventi. Non producono output — comunicano ai moduli che qualcosa è accaduto. Esempio:actionCartSavesi attiva dopo l’aggiornamento di un carrello.
Come funziona il Hook Dispatcher
Quando PrestaShop raggiunge un punto hook, chiama Hook::exec(), che cerca i moduli registrati in ps_hook_module, li ordina per posizione, richiama il metodo hook di ciascun modulo con un array $params, e per i display hook concatena l’HTML restituito.
Hook essenziali che ogni sviluppatore dovrebbe conoscere
Display Hook del Front Office
| Nome Hook | Quando si attiva | Caso d’uso tipico |
|---|---|---|
displayHeader | Dentro <head> su ogni pagina | Meta tag, script di tracciamento |
displayFooter | Footer della pagina | Widget nel footer, analytics |
displayHome | Area contenuti della homepage | Banner, prodotti in evidenza |
displayProductAdditionalInfo | Sotto “Aggiungi al carrello” | Stime di consegna, guide alle taglie |
displayShoppingCart | Pagina riepilogo carrello | Cross-sell, stimatori di spedizione |
displayOrderConfirmation | Dopo l’invio dell’ordine | Tracciamento conversioni |
displayCustomerAccount | Pagina “Il mio account” | Sezioni account personalizzate |
displayBanner | In cima alla pagina, sopra l’header | Annunci di promozioni |
Display Hook del Back Office
| Nome Hook | Quando si attiva | Caso d’uso tipico |
|---|---|---|
displayBackOfficeHeader | <head> dell’admin | CSS/JS admin, notifiche |
displayAdminOrder | Pagina dettaglio ordine | Pannelli personalizzati, integrazioni spedizione |
displayAdminProductsExtra | Tab dell’editor prodotto | Campi prodotto personalizzati |
Action Hook
| Nome Hook | Quando si attiva | Caso d’uso tipico |
|---|---|---|
actionCartSave | Carrello creato o aggiornato | Prenotazione inventario, analytics |
actionOrderStatusUpdate | Cambio stato dell’ordine | Notifiche, sincronizzazione ERP |
actionProductUpdate | Prodotto salvato nell’admin | Sincronizzazione catalogo esterno |
actionCustomerAccountAdd | Nuovo cliente registrato | Email di benvenuto, sincronizzazione CRM |
actionValidateOrder | Ordine validato | Elaborazione pagamento, aggiornamento scorte |
actionFrontControllerSetMedia | Il front controller carica gli asset | Registrazione CSS/JS |
actionAdminControllerSetMedia | Il controller admin carica gli asset | Registrazione CSS/JS admin |
Registrazione degli asset
A partire da PS 1.7, il modo corretto per aggiungere CSS/JS è attraverso la registrazione degli asset all’interno di 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]
);
}
Questo abilita l’ottimizzazione CCC (Combine, Compress, Cache) — molto meglio che iniettare tag grezzi tramite displayHeader.
Come utilizzare gli hook in un modulo
Registrare gli hook durante l’installazione
public function install()
{
return parent::install()
&& $this->registerHook('displayProductAdditionalInfo')
&& $this->registerHook('actionCartSave')
&& $this->registerHook('actionFrontControllerSetMedia');
}
Ogni chiamata inserisce una riga in ps_hook_module. Se dimenticate di registrare, il metodo hook non verrà mai eseguito — anche se il metodo esiste.
Problema di debug comune: il metodo hook esiste ed è denominato correttamente, ma il modulo non si è mai registrato sull’hook durante l’installazione. Se aggiungete un hook dopo il rilascio iniziale, avete bisogno di uno script di aggiornamento o di un reset del modulo.
Implementare il metodo hook
Convenzione di denominazione dei metodi: hook + nome dell’hook con la prima lettera maiuscola.
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 — nessun valore di ritorno
public function hookActionCartSave($params)
{
if (!isset($params['cart'])) {
return;
}
$this->logCartActivity($params['cart']->id);
}
L’array $params
Il contenuto varia a seconda dell’hook. Quelli comuni: $params['cart'], $params['order'], $params['product'], $params['customer']. Per vedere cosa passa un hook, aggiungete un log temporaneo:
file_put_contents(
_PS_ROOT_DIR_ . '/var/logs/hook_debug.log',
print_r(array_keys($params), true),
FILE_APPEND
);
Trovare gli hook disponibili
1. Modalità Debug
Attivatela in Advanced Parameters → Performance → Debug mode: YES. Il front office mostrerà i nomi degli hook nei punti di inserimento. In PS 8+, la toolbar di debug Symfony mostra tutti gli hook eseguiti per ogni richiesta.
Non attivate mai la modalità debug su un negozio in produzione. Espone dati interni e rallenta il sito.
2. Query al database
-- 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. Cercare nei template e nel codice sorgente
# Display hooks in theme templates
grep -rn "{hook " themes/your-theme/templates/
# Action hooks in PHP core
grep -rn "Hook::exec" classes/ controllers/ src/
Creare hook personalizzati
Quando gli hook integrati non si attivano nel punto esatto di cui avete bisogno, createne uno vostro:
// 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);
Registratelo in install() come fareste con qualsiasi hook. Per dargli un nome descrittivo nelle Posizioni:
$hook = new Hook();
$hook->name = 'displayMyModuleExtraContent';
$hook->title = 'My Module - Extra Content Area';
$hook->add();
Create hook personalizzati quando il vostro modulo fornisce punti di estensione di cui altri moduli hanno bisogno. Non createli per logica esclusivamente interna — usate metodi regolari.
Il sistema di override
Gli override sostituiscono classi e controller core senza modificare i file principali. Erano il metodo di personalizzazione principale in PS 1.5/1.6, prima che gli hook fossero completi.
Come funzionano gli override
Un override estende una classe core e sostituisce metodi specifici. L’autoloader controlla prima la directory 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 struttura delle directory rispecchia il core: override/classes/, override/controllers/front/, override/controllers/admin/.
Override vs Hook
| Scenario | Approccio |
|---|---|
| Aggiungere contenuto a una pagina | Display hook |
| Reagire a un evento | Action hook |
| Aggiungere CSS/JS | Hook di registrazione asset |
| Modificare un calcolo core | Override (se non esiste un hook) |
| Modificare una query SQL core | Override (se non esiste un hook) |
I problemi degli override
- Conflitti: Due moduli non possono fare l’override dello stesso metodo. Il secondo fallisce durante l’installazione.
- Rottura negli aggiornamenti: Le firme dei metodi core cambiano tra le versioni. Un override per PS 1.7.7 potrebbe causare un crash su 1.7.8.
- Comportamento nascosto: Gli override non sono visibili dal Back Office, rendendo più difficile il debug.
- Corruzione dell’indice: La mappatura in
class_index.phppuò corrompersi, mandando in crash l’intero negozio.
Il team core di PrestaShop scoraggia attivamente gli override dalla versione 1.7. Se vi trovate a voler usare un override, verificate prima se esiste un hook o un’alternativa basata su Symfony.
Alternative agli override (PS 8+)
Symfony Service Decorator
Avvolgete i servizi core con la vostra logica — più decorator possono concatenarsi senza conflitti:
# modules/mymodule/config/services.yml
services:
mymodule.decorated_calculator:
class: MyModule\Service\CalculatorDecorator
decorates: 'prestashop.core.cart.calculator'
arguments:
- '@mymodule.decorated_calculator.inner'
Doctrine Event Listener
Reagite ai cambiamenti delle entità senza fare l’override di ObjectModel:
# modules/mymodule/config/services.yml
services:
mymodule.product_listener:
class: MyModule\EventListener\ProductListener
tags:
- { name: doctrine.event_listener, event: postUpdate }
CQRS Command e Query
Agganciatevi alle operazioni admin tramite handler di comandi e query. È l’approccio più moderno, che richiede familiarità con l’architettura Symfony di PrestaShop.
Risolvere i conflitti di override
Quando due moduli fanno l’override dello stesso metodo, vedrete:
The method Cart::getOrderTotal is already overridden by module "othermodule".
La soluzione è un merge manuale — combinare la logica di entrambi i moduli in un singolo file di override. Dopo la modifica, ricostruite l’indice delle classi:
# Delete class index — PrestaShop rebuilds automatically
rm var/cache/prod/class_index.php
rm var/cache/dev/class_index.php
Il merge manuale degli override è fragile. Quando uno dei due moduli si aggiorna, potreste dover rifare il merge. Documentate cosa avete unito e perché.
Se un modulo usa un override dove basterebbe un hook, contattate lo sviluppatore e richiedete una soluzione basata su hook.
Priorità degli hook e ordine di esecuzione
Quando più moduli si registrano sullo stesso display hook, la posizione determina l’ordine. Gestite questo in Design → Positions dove potete trascinare i moduli, sganciarli o trapiantarli su hook diversi.
Trapianto
Spostate un modulo da un hook a un altro tramite Design → Positions → Transplant a Module. Non tutti i trapianti vengono visualizzati correttamente — dipende dai template del modulo.
Posizionamento programmatico
// In install() — move to position 1
$this->updatePosition($this->getHookId('displayHome'), false, 1);
Debug degli hook
Verifiche sistematiche
-- 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';
Problemi comuni
| Sintomo | Causa | Soluzione |
|---|---|---|
| Il metodo non viene mai chiamato | Non registrato sull’hook | Controllate ps_hook_module; resettate il modulo |
| Restituisce stringa vuota | Template non trovato | Verificate il percorso del template, attivate gli errori |
| Si attiva sulle pagine sbagliate | Nessun controllo sulla pagina | Aggiungete instanceof ProductController |
| Si attiva due volte | Registrato due volte | Cercate righe duplicate |
| $params['product'] è un array | PS 1.7+ usa array | Usate $params['product']['id_product'] |
Profilazione degli hook
Attivate la profilazione per vedere il tempo di esecuzione per ogni hook:
// PS 1.7 — defines.inc.php
define('_PS_DEBUG_PROFILING_', true);
// PS 8+ — .env.local
APP_DEBUG=1
APP_ENV=dev
Novità di PrestaShop 9
Nuovi hook
PS 9 aggiunge hook in aree che in precedenza richiedevano override: estensioni del form prodotto admin, applicazione regole prezzo carrello, operazioni sulle risorse API e processo di invio email.
Hook deprecati
Gli hook legati ai controller admin legacy sono deprecati man mano che quei controller vengono migrati a Symfony. Consultate _PS_DEPRECATED_HOOKS_ per l’elenco completo.
Futuro del sistema di override
Gli override funzionano ancora per le classi ObjectModel legacy, ma i nuovi servizi Symfony e i controller admin non possono utilizzarli. Il sistema di override verrà alla fine rimosso con il completamento della migrazione a Symfony.
Se state iniziando un nuovo modulo oggi, costruitelo esclusivamente con hook e servizi Symfony. Ogni override che scrivete ora è debito tecnico che dovrete rimuovere in futuro.
Riferimento rapido
// 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;
Padroneggiate prima gli hook — coprono la grande maggioranza dei casi d’uso. Imparate gli override per comprendere il codice legacy e risolvere i conflitti. Per PrestaShop 8+, investite nelle alternative basate su Symfony. L’ecosistema sta andando avanti, e gli hook sono in prima linea.
More guides available
Browse our knowledge base for more practical PrestaShop tutorials, or reach out if you need help.