Knowledge Base Guide

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: displayProductAdditionalInfo aggiunge contenuto sotto il prezzo del prodotto.
  • Action hook (con prefisso action) — Notifiche di eventi. Non producono output — comunicano ai moduli che qualcosa è accaduto. Esempio: actionCartSave si 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 HookQuando si attivaCaso d’uso tipico
displayHeaderDentro <head> su ogni paginaMeta tag, script di tracciamento
displayFooterFooter della paginaWidget nel footer, analytics
displayHomeArea contenuti della homepageBanner, prodotti in evidenza
displayProductAdditionalInfoSotto “Aggiungi al carrello”Stime di consegna, guide alle taglie
displayShoppingCartPagina riepilogo carrelloCross-sell, stimatori di spedizione
displayOrderConfirmationDopo l’invio dell’ordineTracciamento conversioni
displayCustomerAccountPagina “Il mio account”Sezioni account personalizzate
displayBannerIn cima alla pagina, sopra l’headerAnnunci di promozioni

Display Hook del Back Office

Nome HookQuando si attivaCaso d’uso tipico
displayBackOfficeHeader<head> dell’adminCSS/JS admin, notifiche
displayAdminOrderPagina dettaglio ordinePannelli personalizzati, integrazioni spedizione
displayAdminProductsExtraTab dell’editor prodottoCampi prodotto personalizzati

Action Hook

Nome HookQuando si attivaCaso d’uso tipico
actionCartSaveCarrello creato o aggiornatoPrenotazione inventario, analytics
actionOrderStatusUpdateCambio stato dell’ordineNotifiche, sincronizzazione ERP
actionProductUpdateProdotto salvato nell’adminSincronizzazione catalogo esterno
actionCustomerAccountAddNuovo cliente registratoEmail di benvenuto, sincronizzazione CRM
actionValidateOrderOrdine validatoElaborazione pagamento, aggiornamento scorte
actionFrontControllerSetMediaIl front controller carica gli assetRegistrazione CSS/JS
actionAdminControllerSetMediaIl controller admin carica gli assetRegistrazione 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

ScenarioApproccio
Aggiungere contenuto a una paginaDisplay hook
Reagire a un eventoAction hook
Aggiungere CSS/JSHook di registrazione asset
Modificare un calcolo coreOverride (se non esiste un hook)
Modificare una query SQL coreOverride (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.php può 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

SintomoCausaSoluzione
Il metodo non viene mai chiamatoNon registrato sull’hookControllate ps_hook_module; resettate il modulo
Restituisce stringa vuotaTemplate non trovatoVerificate il percorso del template, attivate gli errori
Si attiva sulle pagine sbagliateNessun controllo sulla paginaAggiungete instanceof ProductController
Si attiva due volteRegistrato due volteCercate righe duplicate
$params['product'] è un arrayPS 1.7+ usa arrayUsate $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.

Loading...
Back to top