Distribuiamo 140+ moduli PrestaShop su mypresta.rocks, e il ticket di supporto più comune che riceviamo da altri sviluppatori che leggono il nostro codice è una variante di "il mio hook non parte". 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 da campo che abbiamo scritto per noi stessi negli anni.
Per il dizionario completo degli hook, i trade-off degli override e le note di migrazione PS 8/9, il riferimento più lungo è PrestaShop Hooks & Overrides reference. Questo post è il companion pratico: meno tabelle, più codice che usiamo davvero in produzione, e le trappole specifiche in cui siamo caduti anche noi.
Parti dal lavoro, non dal nome dell'hook
Scegli l'hook lavorando a ritroso dal comportamento che ti serve. Se il modulo deve solo caricare CSS sulle pagine prodotto, non iniettare markup in displayHeader. Se devi reagire dopo la creazione di un ordine, non attaccare logica a un display hook sulla pagina di ringraziamento (ci torniamo tra poco). Se stai ricostruendo il checkout, aspettati di registrarti su più hook e accetta che un singolo hook non possa reggere tutto il modulo.
| Compito del modulo | Hook migliore | Perché | Errore comune |
|---|---|---|---|
| Caricare CSS o JavaScript del modulo | actionFrontControllerSetMedia |
Registra gli asset tramite la pipeline PrestaShop, con il contesto controller già risolto. | Stampare tag <script> grezzi da displayHeader: rompe CCC e cache-busting. |
| Aggiungere un blocco trust sul prodotto | displayProductAdditionalInfo |
Renderizza accanto al pulsante acquisto e passa il prodotto in $params. |
Agganciarsi a una posizione footer globale e ricavare a mano l'ID prodotto. |
| Reagire dopo modifiche al carrello | actionCartSave |
Scatta dopo ogni aggiornamento carrello senza rendering visibile. | Fare chiamate API remote a ogni salvataggio e chiedersi perché la pagina carrello è lenta. |
| Mettere in coda sync ERP / CRM dopo l'acquisto | actionValidateOrder |
Scatta una volta quando l'ordine viene davvero creato, con ordine, carrello, cliente e valuta disponibili. | Sincronizzare dalla pagina di ringraziamento, che scatta di nuovo su refresh e ritorni dal pagamento. |
Controlla la vera schermata Posizioni
Prima di fare debug del codice, guarda il Back Office. La pagina Posizioni ti dice quali moduli sono attualmente agganciati a ogni hook, l'ordine in cui vengono eseguiti e se l'hook è attivo per lo shop. La usiamo ogni giorno: un metodo hook perfetto non partirà mai se il modulo non è stato registrato durante l'installazione, è stato trapiantato sull'hook sbagliato o è stato sganciato da un merchant durante un cambio tema.
Schermata Posizioni reale di PrestaShop 9
Dal nostro shop ps9-dev, non un mockup. Il campo filtro in alto è il modo più rapido per confermare che un hook esista e vedere chi altro è agganciato.
Per lavoro sulle pagine prodotto, filtra displayProductAdditionalInfo. Per caricare asset, actionFrontControllerSetMedia. Per integrazioni ordine, actionValidateOrder. Se il nome del tuo modulo non appare in quella lista, nessuna riscrittura del metodo lo sistemerà.
Registra solo gli hook che usi
La registrazione degli hook appartiene a install() e a nessun altro posto. Mantieni la lista esplicita. 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 quella lista prima di decidere che non c'è nulla da fare. Abbiamo auditato moduli registrati su 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 in v1.2 dopo che il modulo è già in giro su v1.0, gli shop esistenti non rieseguono mai install(). Ti serve uno script di upgrade in upgrade/install-1.2.0.php:
<?php
function upgrade_module_1_2_0($module)
{
return $module->registerHook('actionCartSave');
}
Abbiamo pubblicato versioni senza il file di upgrade in passato. La coda ticket lo rende evidente in fretta.
Esempio: caricare asset nel modo PrestaShop
actionFrontControllerSetMedia è l'unico posto corretto per CSS o JS di front office da 1.7 in poi. Gira dopo che il controller ha risolto quale pagina viene renderizzata, quindi puoi targettizzare un controller specifico in modo pulito. I moduli che ancora stampano tag <link> da displayHeader bypassano completamente CCC, non possono essere deferred e non ricevono la query string di cache-busting. Ogni audit di shop lento che abbiamo fatto negli ultimi tre anni ne ha trovato 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 php_self conta. Senza, ogni pagina categoria, CMS, ricerca e ogni step checkout paga il costo di asset solo prodotto: richieste HTTP extra, più byte dentro PurgeCSS, First Contentful Paint più lento. Quando abbiamo costruito Performance Revolution, l'offender più comune sugli shop lenti era esattamente questo pattern: un modulo che registra asset globali di cui ha bisogno su un solo controller.
Esempio: renderizzare un blocco pagina prodotto
I display hook restituiscono markup. Il pattern: estrai ciò che serve da $params, assegna a Smarty, renderizza tramite un template che il tema può 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 da produzione. Primo, in 1.7+ $params['product'] è un array presenter, non un oggetto Product: se lo dimentichi e chiami $params['product']->reference, ottieni un fatal in PHP 8.1+. Secondo, quel fetch() con prefisso module: è ciò che permette a un child theme 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'email arrabbiata da chi mantiene il tema.
È il pattern che usiamo per trust badge, messaggi di consegna, note garanzia, selettori varianti custom e il widget finanziamento che i nostri shop demo Checkout Revolution mostrano su ogni prodotto. Stessa forma, ogni volta.
Esempio: metti in coda il lavoro ordine, non bloccare il checkout
Gli hook ordine sembrano un invito gratuito a fare tutto ciò che deve succedere "quando viene piazzato un ordine". Non farlo. actionValidateOrder scatta dentro la transazione di checkout: il cliente è ancora sulla pagina in attesa del redirect di ringraziamento. Se chiami sincronicamente un webhook ERP e l'ERP impiega quattro secondi, il cliente aspetta quattro secondi. Se l'ERP è giù, la richiesta 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 guadagna il posto. Avevamo uno shop cliente con un gateway pagamento instabile dove actionValidateOrder scattava due volte su circa lo 0,3% degli ordini: una sul pagamento genuino, una su un retry che il gateway processava prima di andare in timeout. L'integrazione ERP creava felice due picking ticket 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 o torna da un provider di pagamento, e attiveresti la sync più volte per lo stesso ordine. actionValidateOrder scatta una volta, quando la riga ordine viene davvero creata.
Debug della registrazione hook con SQL
Se un metodo non parte, dimostra che l'hook è registrato prima di cambiare qualunque altra cosa. La pagina Posizioni è il controllo visivo; SQL è più veloce 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 shop live mypresta.rocks, gli hook comuni possono accumulare una lunga coda di moduli registrati. Ogni handler su actionFrontControllerSetMedia gira in sequenza sui caricamenti front office, quindi ciascuno deve essere veloce. Un handler lento moltiplicato su una coda hook trafficata diventa TTFB visibile prima ancora di arrivare al template.
L'altra query che eseguiamo continuamente è "il modulo è agganciato, 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 è partito, oppure è partito e ha fallito in silenzio dopo l'install parent. Quello è il bug. Non il metodo.
L'hook custom che non potevamo evitare
Cerchiamo di non creare hook custom. Molte decisioni del tipo "aggiungo un hook qui per estensibilità" invecchiano male: l'hook finisce per essere usato solo dal modulo originale, e ora è una API pubblica da mantenere. Ma c'è un'eccezione che vale la pena raccontare, perché mostra quando un hook custom è la scelta giusta.
In uno dei nostri moduli più grandi, l'editor prodotto admin ha una scheda custom con campi form propri. Altri moduli volevano estendere quella scheda: aggiungere una riga, iniettare un'impostazione, mostrare un badge di stato. Non volevamo che ogni modulo estendente sovrascrivesse lo stesso file template (conflitti override, vedi riferimento KB). Quindi abbiamo aggiunto un singolo display hook custom chiamato 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 volta in install, così appare in Design → Posizioni come qualunque altro:
$hook = new Hook();
$hook->name = 'displayMprProductTabExtra';
$hook->title = 'Product tab — extra content area';
$hook->add();
Regola pratica: crea un hook custom solo quando il modulo è esso stesso un punto di estensione e altri moduli vogliono collegarsi. Non creare hook per organizzare il tuo codice interno: a quello servono i metodi.
L'unico override che distribuiamo ancora
La pagina di riferimento è ferma: gli override sono legacy e dovresti evitarli. Siamo d'accordo. Ma distribuiamo ancora esattamente un override in tutto il catalogo, e vale la pena spiegare perché: lo stesso ragionamento si applica altrove.
È un override su StockAvailable, in un modulo magazzino che deve intercettare come viene decrementato lo stock per setup multi-magazzino. Il comportamento che ci serve dipende da quale magazzino evade un ordine specifico: non c'è hook nel percorso di decremento stock, nessun servizio Symfony da decorare su PS 1.7 (che i clienti coinvolti usano ancora), nessun event listener. Il comportamento che vogliamo è "estendi il metodo esistente, non sostituirlo": esattamente ciò per cui gli override sono progettati.
È documentato nel README del modulo in lettere grandi. Confligge con qualunque altro modulo che sovrascrive StockAvailable. Abbiamo accettato quel trade-off perché l'alternativa (patcharlo 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 infilare ogni customizzazione in un singolo metodo. Se sostituisci un intero workflow, costruisci un vero controller o layer di servizi e usa gli hook solo come punti di integrazione. Se cambi ampiamente la struttura template, un child theme è più pulito. Se stai patchando ciò che sembra un bug core, dimostra la root cause prima di avvolgerci logica hook.
| Situazione | Usare un hook? | Direzione migliore |
|---|---|---|
| Aggiungere un piccolo blocco visibile alle pagine prodotto | Sì | Display hook con supporto override template tramite prefisso module:. |
| Caricare uno script specifico di pagina | Sì | actionFrontControllerSetMedia con guardia php_self. |
| Sostituire layout e comportamento checkout | In parte | Un'architettura modulo vera; gli hook la collegano a PrestaShop. Checkout Revolution ricostruisce il checkout end-to-end invece di fingere che un hook basti. |
| Cambiare ogni dettaglio dei template prodotto | A volte | Un child theme è di solito più pulito: vedi la guida child themes. |
| Patchare problemi performance causati da molti moduli | No | Profila lo stack hook, poi restringi, cachea o rimuovi gli handler lenti. Vedi la guida performance. |
I quattro controlli che catturano quasi tutti i bug hook
Quando apriamo un ticket che dice "l'hook non parte", la risposta è una di queste quattro, più o meno in questo ordine di frequenza:
- Il modulo non era registrato sull'hook. Controlla
ps_hook_module. Se manca la riga, il tuoinstall()non ha eseguito quella linea, oppure hai aggiunto l'hook in una versione successiva senza script di upgrade. - Il nome del metodo è sbagliato.
hookActionValidateOrder, nonHookActionValidateOrderoactionValidateOrder. PHP è case-insensitive per i nomi metodo, ma i typo nel prefisso restano typo. - Non restituisci nulla da un display hook. I display hook devono
returnl'HTML; gli action hook ignorano il valore di ritorno. Facile scambiarli per errore. - L'hook scatta in un contesto che non ti aspettavi.
displayHeaderscatta anche su richieste AJAX.displayOrderConfirmationscatta a ogni refresh.actionCartSavescatta quando il carrello viene salvato per la sessione cliente anche se nulla è cambiato. Proteggi l'handler.
Segui questi quattro controlli in ordine e quasi mai ti servirà aprire un Symfony profiler.
Letture correlate
- PrestaShop Hooks & Overrides reference — il companion lungo: dizionario hook completo, trade-off override, note migrazione PS 9
- How to Migrate to PrestaShop 9: Complete Upgrade Guide — cosa è cambiato per gli hook in PS 9 nello specifico
- PrestaShop Child Themes guide — quando gli override template battono la customizzazione via hook
- PrestaShop troubleshooting guide — dimostrare la root cause prima di arrivare a un hook
- PrestaShop performance guide — profilare lo stack hook su uno shop lento
- Checkout Revolution e Performance Revolution — due nostri moduli che usano molto gli hook; riferimenti utili di produzione
Commenti (2)
Lascia un commento
Condividi una domanda, un dettaglio di installazione o un feedback utile per un altro lettore.