Revisionato a giugno 2026 su PrestaShop 8.x e 9.x. Ogni nome di hook riportato qui sotto è un hook reale di PrestaShop, e i pattern per asset e template mostrati sono aggiornati dalla 1.7 alla 9 (core Symfony 6.4).
Distribuiamo oltre 140 moduli PrestaShop su mypresta.rocks, e la richiesta di assistenza più comune che riceviamo da altri sviluppatori che leggono il nostro codice è quasi sempre una variante di "il mio hook non viene eseguito". Non è quasi mai il metodo. È quasi sempre una di quattro cose: registrato sull'hook sbagliato, mai registrato, registrato due volte, oppure registrato correttamente ma chiamato da un contesto che lo sviluppatore non si aspettava. Questa è la guida pratica che abbiamo scritto per noi stessi nel corso degli anni.
Per il dizionario completo degli hook, i compromessi sugli override e le note di migrazione per PS 8/9, il riferimento Hook & Override PrestaShop è la pagina di approfondimento più estesa. Questo articolo è il suo compagno pratico: meno tabelle, più codice che usiamo davvero in produzione e le trappole specifiche in cui siamo caduti noi stessi.
Parti dal compito, non dal nome dell'hook
Scegli l'hook ragionando a ritroso dal comportamento che ti serve. Se il tuo modulo ha bisogno solo di CSS nelle pagine prodotto, non iniettare markup in displayHeader. Se devi reagire dopo la creazione di un ordine, non agganciare la logica a un hook di visualizzazione nella pagina di ringraziamento (torneremo sul perché). Se stai ricostruendo il checkout, aspettati di registrarti su diversi hook e accetta che un solo hook non possa reggere l'intero modulo.
| Compito del modulo | Hook migliore | Perché | Errore comune |
|---|---|---|---|
| Caricare CSS o JavaScript del modulo | actionFrontControllerSetMedia |
Registra gli asset tramite la pipeline di PrestaShop, con il contesto del controller già risolto. | Stampare tag <script> grezzi da displayHeader: manda in crisi CCC e cache-busting. |
| Aggiungere un blocco di fiducia nella pagina prodotto | displayProductAdditionalInfo |
Viene renderizzato accanto al pulsante di acquisto e ti passa il prodotto in $params. |
Agganciarsi a una posizione globale del footer e ricavare a mano l'ID prodotto. |
| Reagire dopo modifiche al carrello | actionCartSave |
Scatta dopo ogni aggiornamento del carrello senza alcun rendering visibile. | Fare chiamate API remote a ogni salvataggio e poi chiedersi perché la pagina del carrello sia lenta. |
| Accodare la sincronizzazione ERP / CRM dopo l'acquisto | actionValidateOrder |
Scatta una volta quando l'ordine viene realmente creato, con ordine, carrello, cliente e valuta nel contesto. | Sincronizzare dalla pagina di ringraziamento, che scatta di nuovo al refresh e dopo il ritorno dal pagamento. |
Controlla la schermata reale Posizioni
Prima di debuggare il codice, guarda il Back Office. La pagina Posizioni ti dice quali moduli sono attualmente collegati a ciascun hook, in quale ordine vengono eseguiti e se l'hook è attivo per il negozio. La usiamo ogni giorno: un metodo hook perfetto non verrà mai eseguito se il modulo non è stato registrato durante l'installazione, è stato trapiantato sull'hook sbagliato o è stato sganciato da un commerciante durante un cambio tema.
Schermata reale Posizioni di PrestaShop 9
Dal nostro negozio ps9-dev, non da un mockup. Il campo filtro in alto è il modo più rapido per confermare che un hook esista e vedere chi altro lo usa.
Per interventi sulla pagina prodotto, filtra displayProductAdditionalInfo. Per il caricamento degli asset, actionFrontControllerSetMedia. Per le integrazioni sugli ordini, actionValidateOrder. Se il nome del tuo modulo non compare in quell'elenco, riscrivere il metodo non risolverà nulla.

Registra solo gli hook che usi
La registrazione degli hook appartiene a install() e a nessun altro punto. Mantieni l'elenco esplicito. Un modulo che registra venti hook ma implementa solo tre metodi aggiunge una riga a ps_hook_module per ogni hook vuoto, e sulle pagine trafficate PrestaShop percorre comunque quell'elenco prima di decidere che non c'è nulla da fare. Abbiamo revisionato moduli registrati su oltre 40 hook "per sicurezza". Non essere quel modulo.
<?php
public function install()
{
return parent::install()
&& $this->registerHook('actionFrontControllerSetMedia')
&& $this->registerHook('displayProductAdditionalInfo')
&& $this->registerHook('actionValidateOrder');
}
Se aggiungi un hook nella v1.2 dopo che il modulo è già in circolazione dalla v1.0, i negozi esistenti non rieseguono mai install(). Ti serve uno script di aggiornamento in upgrade/install-1.2.0.php:
<?php
function upgrade_module_1_2_0($module)
{
return $module->registerHook('actionCartSave');
}
In passato abbiamo rilasciato versioni senza il file di aggiornamento. La coda dei ticket lo rende evidente in fretta.
Esempio: caricare gli asset nel modo corretto per PrestaShop
actionFrontControllerSetMedia è l'unico punto corretto per CSS o JS del front office dalla 1.7 in poi. Viene eseguito dopo che il controller ha stabilito quale pagina sarà renderizzata, quindi puoi puntare a un controller specifico in modo pulito. I moduli che ancora emettono tag <link> da displayHeader aggirano completamente CCC, non possono essere differiti e non ricevono la query string di cache-busting. In ogni audit su negozi lenti che abbiamo fatto negli ultimi tre anni ne abbiamo trovati almeno due.
<?php
public function hookActionFrontControllerSetMedia(array $params): void
{
$controller = $this->context->controller;
if (!isset($controller->php_self) || $controller->php_self !== 'product') {
return;
}
$controller->registerStylesheet(
'my-module-product',
'modules/' . $this->name . '/views/css/product.css',
['media' => 'all', 'priority' => 150]
);
$controller->registerJavascript(
'my-module-product',
'modules/' . $this->name . '/views/js/product.js',
['position' => 'bottom', 'priority' => 150]
);
}
La guardia su php_self conta. Senza di essa, ogni pagina categoria, pagina CMS, pagina di ricerca e passaggio del checkout paga il costo di asset pensati solo per il prodotto: richieste HTTP in più, più byte attraverso PurgeCSS, First Contentful Paint più lento. Quando abbiamo creato Performance Revolution, il responsabile più comune che abbiamo visto nei negozi lenti era esattamente questo pattern: un modulo che registrava asset globali necessari su un solo controller.
Esempio: renderizzare un blocco nella pagina prodotto
Gli hook di visualizzazione restituiscono markup. Il pattern è questo: prendi da $params ciò che ti serve, assegnalo a Smarty e renderizza tramite un template che il tema possa sovrascrivere.
<?php
public function hookDisplayProductAdditionalInfo(array $params): string
{
$productId = (int) ($params['product']['id_product'] ?? Tools::getValue('id_product'));
if ($productId <= 0) {
return '';
}
$this->context->smarty->assign([
'mpr_delivery_label' => $this->getCachedDeliveryLabel($productId),
'mpr_support_url' => $this->context->link->getCMSLink(3),
]);
return $this->fetch('module:' . $this->name . '/views/templates/hook/product-trust.tpl');
}
Due dettagli dalla produzione. Primo: dalla 1.7 in poi $params['product'] è un array del presenter, non un oggetto Product; se lo dimentichi e chiami $params['product']->reference, in PHP 8.1+ ottieni un fatal error. Secondo: quel fetch() con il prefisso module: è ciò che permette a un tema figlio di sovrascrivere il template in themes/your-theme/modules/your-module/views/templates/hook/product-trust.tpl. Se invece usi $this->display(__FILE__, ...), l'override del tema non funziona in silenzio e riceverai un'e-mail seccata da chi mantiene il tema.
È il pattern che usiamo per badge di fiducia, messaggi di consegna, note di garanzia, selettori di varianti personalizzati e il widget di finanziamento che i nostri negozi di esempio Checkout Revolution mostrano su ogni prodotto. Stessa struttura, ogni volta.
Esempio: accoda il lavoro sugli ordini, non bloccare il checkout
Gli hook sugli ordini sembrano un invito gratuito a fare qualsiasi cosa debba accadere "quando viene effettuato un ordine". Non farlo. actionValidateOrder scatta dentro la transazione di checkout: il cliente è ancora sulla pagina e aspetta il reindirizzamento alla pagina di ringraziamento. Se chiami un webhook ERP in modo sincrono e l'ERP impiega quattro secondi, il cliente aspetta quattro secondi. Se l'ERP non risponde, la richiesta di checkout può andare completamente in timeout.
<?php
public function hookActionValidateOrder(array $params): void
{
/** @var Order $order */
$order = $params['order'];
if ($this->syncAlreadyQueued((int) $order->id)) {
return;
}
$this->queueOrderSync([
'id_order' => (int) $order->id,
'id_customer' => (int) $order->id_customer,
'total_paid' => (float) $order->total_paid_tax_incl,
'created_at' => date('Y-m-d H:i:s'),
]);
}
Il controllo syncAlreadyQueued() si ripaga da solo. Avevamo un negozio cliente con un gateway di pagamento instabile in cui actionValidateOrder scattava due volte su circa lo 0,3% degli ordini: una volta sul pagamento autentico, una volta su un tentativo ripetuto che il gateway elaborava prima di andare in timeout. L'integrazione ERP creava tranquillamente due liste di prelievo di magazzino per ordine per settimane prima che qualcuno se ne accorgesse. L'idempotenza su questo hook non è paranoia: è il costo di lavorare con provider di pagamento reali.
Perché actionValidateOrder e non displayOrderConfirmation? Perché la pagina di conferma scatta ogni volta che il cliente aggiorna la pagina o torna da un provider di pagamento, e finiresti per attivare la sincronizzazione più volte per lo stesso ordine. actionValidateOrder scatta una volta, quando la riga dell'ordine viene realmente creata.
Debuggare la registrazione degli hook con SQL
Se un metodo non viene eseguito, dimostra che l'hook sia registrato prima di cambiare qualsiasi altra cosa. La pagina Posizioni è il controllo visivo; SQL è più rapido quando ti servono numeri.
SELECT h.name, COUNT(hm.id_module) AS modules
FROM ps_hook h
LEFT JOIN ps_hook_module hm ON hm.id_hook = h.id_hook
WHERE h.name IN (
'actionFrontControllerSetMedia',
'displayProductAdditionalInfo',
'actionCartSave',
'actionValidateOrder',
'displayAdminOrder'
)
GROUP BY h.name
ORDER BY h.name;
Sul nostro negozio live mypresta.rocks, gli hook comuni possono accumulare una lunga coda di moduli registrati. Ogni handler su actionFrontControllerSetMedia viene eseguito in sequenza sui caricamenti delle pagine del front office, quindi ciascuno deve essere veloce. Un handler lento moltiplicato per una coda affollata di hook diventa TTFB visibile prima ancora di arrivare al template.
L'altra query che eseguiamo continuamente è: "il modulo è collegato, sì o no?":
SELECT m.name, h.name AS hook, 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;
Se la riga non c'è, il tuo install() non è stato eseguito, oppure è stato eseguito ed è fallito in silenzio dopo l'installazione del parent. Quello è il bug. Non il metodo.
L'hook personalizzato che non abbiamo potuto evitare di costruire
Cerchiamo di non creare hook personalizzati. La maggior parte delle decisioni del tipo "aggiungo qui un hook per estensibilità" invecchia male: l'hook finisce per non essere usato da nessuno tranne il modulo originale, e ora è una API pubblica che devi mantenere. Ma c'è un'eccezione di cui vale la pena parlare, perché mostra quando un hook personalizzato è la scelta giusta.
In uno dei nostri moduli più grandi, l'editor prodotto dell'amministrazione ha una scheda personalizzata con campi propri. Altri moduli volevano estendere quella scheda: aggiungere una riga, iniettare un'impostazione, mostrare un badge di stato. Non volevamo che ogni modulo estensore sovrascrivesse lo stesso file template (conflitti di override, vedi il riferimento nella knowledge base). Quindi abbiamo aggiunto un singolo hook display personalizzato, eseguito dall'interno del template della scheda:
// In the module that owns the tab
$extraRows = Hook::exec(
'displayMprProductTabExtra',
['id_product' => (int) $idProduct, 'tab' => 'logistics'],
null,
true
);
// $extraRows is an associative array keyed by module name
// Render each extension's HTML inside the tab
E abbiamo registrato l'hook una sola volta in installazione, così compare in Design → Posizioni come qualsiasi altro:
$hook = new Hook();
$hook->name = 'displayMprProductTabExtra';
$hook->title = 'Product tab – extra content area';
$hook->add();
La regola pratica: crea un hook personalizzato solo quando il tuo modulo è esso stesso un punto di estensione e altri moduli vogliono collegarsi a esso. Non creare hook per organizzare il tuo codice interno: a quello servono i metodi.
L'unico override che distribuiamo ancora
La pagina di riferimento è netta: gli override sono legacy e andrebbero evitati. Siamo d'accordo. Ma in tutto il nostro catalogo distribuiamo ancora esattamente un override, e vale la pena spiegare perché, visto che lo stesso ragionamento si applica altrove.
È un override su StockAvailable, in un modulo di magazzino che deve intercettare il modo in cui lo stock viene decrementato nelle configurazioni multi-magazzino. Il comportamento che ci serve dipende dal magazzino da cui viene evaso un determinato ordine: non c'è un hook nel percorso di decremento dello stock, nessun servizio Symfony da decorare in PS 1.7 (che i clienti interessati usano ancora) e nessun event listener. Il comportamento che vogliamo è "estendere il metodo esistente, non sostituirlo": esattamente ciò per cui sono pensati gli override.
È documentato a caratteri grandi nel README del modulo. Va in conflitto con qualsiasi altro modulo che sovrascriva StockAvailable. Abbiamo accettato quel compromesso perché l'alternativa (forzarlo in qualche altro modo) sarebbe peggiore. Ogni override è debito tecnico con una data di rimozione nota; la data di rimozione di questo è "quando il nostro ultimo cliente 1.7 aggiornerà a PS 9".
Quando un hook è lo strumento sbagliato
Gli hook non sono una licenza per stipare ogni personalizzazione in un unico metodo. Se stai sostituendo un intero flusso, costruisci un vero controller o un layer di servizi e usa gli hook solo come punti di integrazione. Se stai modificando ampiamente la struttura dei template, un tema figlio è più pulito. Se stai rattoppando qualcosa che sembra un bug del core, dimostra la causa principale prima di avvolgerci attorno logica di hook.
| Situazione | Usare un hook? | Direzione migliore |
|---|---|---|
| Aggiungere un piccolo blocco visibile alle pagine prodotto | Sì | Hook display con supporto all'override del template tramite prefisso module:. |
| Caricare uno script specifico per una pagina | Sì | actionFrontControllerSetMedia con una guardia su php_self. |
| Sostituire layout e comportamento del checkout | In parte | Un'architettura di modulo solida; gli hook la collegano a PrestaShop. Checkout Revolution ricostruisce il checkout da cima a fondo invece di fingere che un solo hook possa reggerlo. |
| Cambiare ogni dettaglio del template prodotto | A volte | Di solito un tema figlio è più pulito: vedi la guida ai temi figli. |
| Risolvere problemi di prestazioni causati da molti moduli | No | Profila lo stack degli hook, poi restringi, metti in cache o rimuovi gli handler lenti. Vedi la guida alle prestazioni. |
I quattro controlli che intercettano la maggior parte dei bug sugli hook
Quando apriamo un ticket di assistenza che dice "l'hook non viene eseguito", la risposta è una di queste quattro, più o meno in quest'ordine di frequenza:
- Il modulo non era registrato sull'hook. Controlla
ps_hook_module. Se manca la riga, il tuoinstall()non ha eseguito quella riga, oppure hai aggiunto l'hook in una versione successiva senza uno script di aggiornamento. - Il nome del metodo è sbagliato.
hookActionValidateOrder, nonHookActionValidateOrderoactionValidateOrder. PHP non distingue maiuscole e minuscole nei nomi dei metodi, ma gli errori di battitura nel prefisso restano errori di battitura. - Non stai restituendo nulla da un hook display. Gli hook display devono fare
returndell'HTML; gli hook action scartano il valore restituito. Facile confonderli per errore. - L'hook scatta in un contesto che non ti aspettavi.
displayHeaderscatta anche sulle richieste AJAX.displayOrderConfirmationscatta a ogni refresh.actionCartSavescatta quando il carrello viene salvato per la sessione del cliente anche se non è cambiato nulla. Proteggi l'handler.
Segui quei quattro controlli in ordine, e quasi mai avrai bisogno di aprire il profiler Symfony.
FAQ
Qual è la differenza tra un hook "display" e un hook "action"?
Il nome ti dice il contratto. Un hook display* si aspetta che tu faccia return di HTML, che PrestaShop stampa in quella posizione: se non restituisci nulla ottieni uno spazio vuoto, non un errore. Un hook action* è un evento: PrestaShop ignora il valore restituito ed esegue semplicemente il tuo codice per i suoi effetti collaterali (accodare una sincronizzazione, scrivere un log, salvare una riga). Restituire HTML da un hook action non produce nulla; dimenticare di restituire HTML da un hook display è il bug silenzioso del terzo controllo qui sopra.
Ho aggiunto un nuovo hook in una versione successiva: perché non è registrato sui negozi esistenti?
install() viene eseguito una sola volta, la prima volta che il modulo viene installato. I negozi che hanno installato una versione precedente non lo rieseguono mai, quindi una riga registerHook() che aggiungi nella v1.2 resta invisibile per loro finché non reinstallano. La soluzione è uno script di aggiornamento nella directory upgrade/ del modulo: PrestaShop lo esegue automaticamente quando il commerciante aggiorna il modulo, come mostrato prima in questo articolo. Distribuisci la registrazione sia in install() (per i nuovi negozi) sia nello script di aggiornamento (per quelli esistenti).
I nomi degli hook sono cambiati in PrestaShop 9?
Gli hook principali del front office in questo articolo, actionFrontControllerSetMedia, displayProductAdditionalInfo, actionValidateOrder, actionCartSave, sono stabili tra 1.7, 8 e 9. Ciò che è cambiato nella 9 è il framework circostante (Symfony 6.4) e alcune API legacy rimosse, quindi un handler che chiama un helper deprecato come Tools::displayPrice() può causare un fatal error sulla 9 anche se l'hook in sé scatta ancora. L'hook va bene; quando passi alla 9, è il codice al suo interno che va revisionato.
Come trovo quale hook viene realmente renderizzato da una posizione del tema?
La schermata Posizioni (Design → Posizioni) è la mappa visiva più rapida: elenca ogni hook, i moduli collegati e il loro ordine. Filtra per nome hook per confermare che un hook esista e vedere chi altro lo usa. Quando ti servono numeri invece di una scansione visiva, conteggi, oppure un sì/no sul fatto che il tuo modulo sia collegato, le due query SQL mostrate prima in questo articolo su ps_hook e ps_hook_module sono più veloci.
Va mai bene emettere un <script> da displayHeader?
Quasi mai per gli asset del front office. Dalla 1.7, CSS e JS appartengono a actionFrontControllerSetMedia tramite registerStylesheet() / registerJavascript(), così passano dalla pipeline e ricevono CCC, cache-busting e deferimento. I tag grezzi da displayHeader aggirano tutto questo e compaiono in ogni audit su negozi lenti che eseguiamo. L'eccezione ristretta è il dato inline che la pipeline degli asset non può trasportare: un blocco di dati strutturati o un piccolo oggetto di configurazione bootstrap; anche in quel caso, tienilo minuscolo.
Letture correlate
- Riferimento Hook & Override PrestaShop, il compagno esteso: dizionario completo degli hook, compromessi sugli override, note di migrazione a PS 9
- Come migrare a PrestaShop 9: guida completa all'aggiornamento. Che cosa è cambiato per gli hook specificamente in PS 9
- Guida ai temi figli PrestaShop, quando gli override dei template battono la personalizzazione tramite hook
- Guida alla risoluzione dei problemi PrestaShop, dimostrare la causa principale prima di ricorrere a un hook
- Guida alle prestazioni PrestaShop, profilare lo stack degli hook su un negozio lento
- Checkout Revolution e Performance Revolution. Due dei nostri moduli che fanno largo uso degli hook; riferimenti utili dalla produzione
Commenti
Lascia un commento
Condividi una domanda, un dettaglio di installazione o un feedback utile per un altro lettore.