Wo Sie sich in PrestaShop einhängen, und wann ein Override besser ist
Entwickler-Referenz für PrestaShop Hooks und Overrides: Display-Hooks, Action-Hooks, eigene Hooks, das Override-System und Migration zu modernen Patterns.
Was Hooks wirklich tun (und woran die meisten scheitern)
Jedes Modul, das wir bei mypresta.rocks ausliefern, klinkt sich über Hooks in PrestaShop ein, und nachdem wir 140+ davon im produktiven Einsatz haben, können wir Ihnen sagen, dass sich die meisten Hook-Fehler, die wir seit 2013 behoben haben, auf dieselbe Handvoll Fehlerquellen zurückführen lassen: am falschen Hook registriert, gar nicht registriert, doppelt ausgelöst oder dort ausgelöst, wo der Entwickler ein Override vermutet hat.
Diese Seite ist die Referenz, die wir uns zum Einstieg gewünscht hätten. Es ist außerdem die Seite, die wir Kunden schicken, wenn sie uns fragen: „Warum braucht dieses Modul ein Update für PrestaShop 9?", denn die Antwort lautet fast immer: „Der genutzte Hook hat sich geändert."
Zwei Arten von Hooks und die eine Regel, auf die es ankommt
PrestaShop kennt Display-Hooks (sie fragen Ihr Modul nach HTML zur Darstellung) und Action-Hooks (sie teilen Ihrem Modul mit, dass etwas passiert ist (Bestellung aufgegeben, Warenkorb gespeichert, Kunde registriert) und ignorieren, was Sie zurückgeben).
Die Regel, die viele zu Fall bringt: Bei Display-Hooks ist der Rückgabewert entscheidend, bei Action-Hooks nicht. Wenn Sie aus einem Action-Hook return $this->display(...) zurückgeben, passiert nichts Schlimmes. Wenn Sie das return bei einem Display-Hook vergessen, rendert Ihr Modul nichts, und Sie verlieren eine Stunde mit der Frage, woran es liegt.
Wie PrestaShop Ihren Code tatsächlich aufruft
Wenn der Core einen Hook-Punkt erreicht, fragt Hook::exec() die Tabelle ps_hook_module ab, sortiert die aktiven Module nach Position, ruft die hookXxx()-Methode jedes Moduls mit einem $params-Array auf und verkettet (bei Display-Hooks) das zurückgegebene HTML.
Das ist alles. Keine Magie. Der Grund, warum Ihr Hook nicht feuert, ist fast immer eine fehlende Zeile in ps_hook_module, und nicht etwa ein Fehler in Ihrer Methode.
Die vier Muster, die die meisten Module verwenden
Bevor Sie zur Dokumentation greifen, fragen Sie sich, welche dieser vier Aufgaben Sie gerade erledigen. Wählen Sie das falsche Muster, kämpfen Sie für den Rest des Modullebens gegen das Framework an.
| Sie möchten … | Greifen Sie zu … | Lassen Sie die Finger von … |
|---|---|---|
| HTML in einem Theme oder einer Admin-Seite rendern | Einem display*-Hook, der ein Smarty-Template zurückgibt | echo innerhalb von displayHeader |
| Auf eine Zustandsänderung reagieren (Bestellung aufgegeben, Warenkorb gespeichert, Produkt aktualisiert) | Einem action*-Hook | Einem Override des Controllers |
| CSS oder JS zum Front-Office hinzufügen | actionFrontControllerSetMedia + registerStylesheet / registerJavascript | Rohen <link>-Tags über displayHeader, das setzt CCC außer Kraft und zerstört die Asset-Versionierung |
| Eine Core-Berechnung ändern, für die es keinen Hook gibt | Einem Symfony-Service-Decorator (PS 8+) oder, als letztes Mittel, einem Override | Dem Bearbeiten von Core-Dateien |
Die Hooks, die Sie tatsächlich brauchen
PrestaShop liefert Hunderte von Hooks mit. Wir registrieren über unseren gesamten Modulkatalog hinweg auf etwa zwölf davon. Diese hier lohnt es sich zu merken.
Front-Office, Display
| Hook | Feuert | Einsatz für |
|---|---|---|
displayProductAdditionalInfo | Unter dem „In den Warenkorb"-Button | Lieferschätzungen, Größentabellen, zusätzliche Inhalte auf der Produktseite, die wertvollste Fläche einer Produktseite |
displayShoppingCart | Warenkorb-Übersichtsseite | Cross-Selling, Versandrechner, Dringlichkeitshinweise |
displayOrderConfirmation | Dankesseite nach dem Checkout | Conversion-Tracking, Upsells nach dem Kauf, Empfehlungsaufrufe |
displayCustomerAccount | Seite „Mein Konto" | Eigene Bereiche, Wunschlisten, Treuepunkte, RMA-Anfragen |
displayBanner | Seitenanfang, oberhalb des Headers | Sale-Streifen, Gratisversand-Balken, DSGVO-Hinweise |
displayHome | Inhaltsbereich der Startseite | Hervorgehobene Kollektionen, Hero-Module, wiederverwendbare HTML-Blöcke |
Back-Office, Display
| Hook | Feuert | Einsatz für |
|---|---|---|
displayAdminOrder | Bestelldetailseite | Eigene Panels, Versandanbindungen, ERP-Sync-Status, Betrugssignale |
displayAdminProductsExtra | Neuer Reiter im Produkteditor | Eigene Produktfelder, Daten von Drittanbietern |
displayBackOfficeHeader | Innerhalb des Admin-<head> | Admin-CSS/JS, Dashboard-Widgets, Update-Hinweise |
Action-Hooks
| Hook | Feuert | Einsatz für |
|---|---|---|
actionValidateOrder | Bestellung erfolgreich validiert | Zahlungsbestätigung, Bestandsabbuchung, Workflow nach der Zahlung, der wichtigste Action-Hook |
actionOrderStatusUpdate | Bestellstatus ändert sich | Versandbenachrichtigungen, ERP-Sync, Kunden-E-Mails |
actionCartSave | Warenkorb erstellt oder aktualisiert | Trigger für abgebrochene Warenkörbe, Bestandsreservierung, Analytics |
actionProductUpdate | Produkt im Admin gespeichert | Sync mit externem Katalog, Reindexierung der Suche, Bild-Neugenerierung |
actionCustomerAccountAdd | Neuer Kunde registriert sich | Willkommens-E-Mail, CRM-Sync, Segmentierung |
actionFrontControllerSetMedia | Front-Controller lädt Assets | CSS/JS auf die richtige Weise registrieren |
Der Asset-Hook, bitte diesen verwenden, nicht displayHeader
Seit PrestaShop 1.7 ist die einzig korrekte Stelle, um CSS oder JS im Front-Office hinzuzufügen, 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]
);
}
Module, die immer noch rohe <link>- und <script>-Tags über displayHeader einschleusen, brechen Combine-Compress-Cache (CCC), umgehen den Cache-Busting-Query-String und lassen sich nicht verzögert laden. Jedes Mal, wenn wir einen langsamen Shop auditieren, machen mindestens zwei Module genau das. Gehören Sie nicht dazu.
Einen Hook in Ihr Modul einbinden
Bei der Installation registrieren
public function install()
{
return parent::install()
&& $this->registerHook('displayProductAdditionalInfo')
&& $this->registerHook('actionCartSave')
&& $this->registerHook('actionFrontControllerSetMedia');
}
Jeder Aufruf von registerHook() fügt eine Zeile in ps_hook_module ein. Vergessen Sie die Registrierung, könnte Ihre Hook-Methode genauso gut nicht existieren. PrestaShop wird sie nie aufrufen. Das ist die mit Abstand häufigste Ursache für „Mein Modul funktioniert nicht", die uns im Support begegnet.
Noch eine Falle: Wenn Sie in Version 1.2 Ihres Moduls einen neuen Hook hinzufügen, nachdem Sie 1.0 ausgeliefert haben, erhalten bestehende Kundeninstallationen die Registrierung nicht. Sie benötigen entweder ein Upgrade-Skript (upgrade/install-1.2.0.php), das registerHook() aufruft, oder eine Anweisung, das Modul zurückzusetzen. Nehmen Sie das in die Release-Notes auf, sonst bekommen Sie Tickets.
Die Methode implementieren
Methodenname = wörtlich hook + der Hook-Name, erster Buchstabe großgeschrieben:
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-Hooks: Arbeit erledigen, der Rückgabewert wird verworfen
public function hookActionCartSave($params)
{
if (!isset($params['cart'])) {
return;
}
$this->logCartActivity($params['cart']->id);
}
Was in $params steckt
Das hängt vom Hook ab. Die fünf, die Ihnen am häufigsten begegnen, sind $params['cart'], $params['order'], $params['product'], $params['customer'] und $params['cookie']. Wenn Sie unsicher sind, loggen Sie es:
file_put_contents(
_PS_ROOT_DIR_ . '/var/logs/hook_debug.log',
date('c') . "\n" . print_r(array_keys($params), true) . "\n",
FILE_APPEND
);
Entfernen Sie das Logging, bevor Sie ausliefern. Wir haben mehr als einmal Module mit übrig gebliebenem Debug-Logging ausgeliefert, es füllt var/logs/ auf stark frequentierten Shops und frisst Speicherplatz.
Einen Hook finden, wenn Sie nicht wissen, welchen Sie verwenden sollen
Die Positionen-Seite im PrestaShop-8-Back-Office.
Diese Seite zeigt, welche Module auf welchen Hooks registriert sind und in welcher Reihenfolge. Wenn ein Modul nicht feuert, ist dies die erste Anlaufstelle zur Prüfung.
Der Debug-Modus zeigt Ihnen die Seite
Erweiterte Einstellungen → Leistung → Debug-Modus → Ja. Laden Sie die Seite neu. PrestaShop versieht jeden Einfügepunkt eines Display-Hooks mit dessen Namen. Ab PS 8 listet die Symfony-Toolbar zusätzlich jeden Hook auf, der bei der Anfrage gefeuert wurde.
Wichtig: Schalten Sie den Debug-Modus niemals in der Produktion ein. Er legt Stack-Traces offen, bremst die Seite aus und gibt unter PS 9 außerdem die Datenbank-Zugangsdaten in Fehlerseiten preis.
Die Datenbank weiß es
-- Alle Hooks mit "product" im Namen
SELECT name, title FROM ps_hook WHERE name LIKE '%product%' ORDER BY name;
-- Welche Module auf einem bestimmten Hook liegen, in Ausführungsreihenfolge
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;
Den Quellcode durchsuchen (grep)
# Display-Hooks in Theme-Templates
grep -rn "{hook " themes/your-theme/templates/
# Action-Hooks im Core-PHP
grep -rn "Hook::exec" classes/ controllers/ src/
Bei PS 8.x und 9.x sollten Sie auch src/ durchsuchen, Symfony-Controller feuern ebenfalls Hooks, und die übersieht man leicht, wenn man nur im Legacy-Verzeichnis controllers/ nachsieht.
Einen eigenen Hook erstellen
Tun Sie das nur, wenn Ihr Modul selbst ein Erweiterungspunkt ist, also wenn Sie möchten, dass sich andere Module in Ihres einklinken. Erstellen Sie keine Hooks nur, um Ihren eigenen Code zu strukturieren; dafür sind Methoden da.
// Irgendwo in Ihrem Modul
$hookResult = Hook::exec('actionMyModuleBeforeProcess', [
'order_id' => $orderId,
'custom_data' => $myData,
]);
// Bei Display-Hooks null + true übergeben, um HTML je Modul zu erhalten
$extraHtml = Hook::exec('displayMyModuleExtraContent', [
'product' => $product,
], null, true);
Damit Ihr Hook unter Design → Positionen sichtbar wird, registrieren Sie ihn einmalig:
$hook = new Hook();
$hook->name = 'displayMyModuleExtraContent';
$hook->title = 'My Module: Extra Content Area';
$hook->add();
Das Override-System: Legacy, aber nicht tot
Overrides ermöglichen es Ihnen, Core-Methoden zu ersetzen, ohne Core-Dateien zu bearbeiten. Sie waren in der Ära 1.5/1.6 die Methode, PrestaShop anzupassen, bevor Hooks die meisten Anwendungsfälle abdeckten. Sie funktionieren weiterhin (und wir nutzen sie nach wie vor, sparsam), haben aber echte Probleme, die Sie kennen sollten, bevor Sie zu einem greifen.
So sieht ein Override aus
// 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;
}
}
Das Override-Verzeichnis spiegelt den Core: override/classes/, override/controllers/front/, override/controllers/admin/. Der Autoloader von PrestaShop sieht zuerst hier nach, sodass Ihr Cart das CartCore überschattet.
Warum wir Overrides nach Möglichkeit vermeiden
- Sie kollidieren. Zwei Module können nicht dieselbe Methode überschreiben. Das zweite, das installiert wird, scheitert komplett. Das ist der häufigste Grund dafür, dass „zwei Module, die ich nutzen möchte, nicht zusammen funktionieren."
- Sie brechen beim Upgrade. Methodensignaturen ändern sich zwischen PrestaShop-Versionen. Ein für 1.7.7 geschriebenes Override kann 1.7.8 mit einem Fatal Error zum Absturz bringen und den gesamten Shop lahmlegen.
- Sie sind unsichtbar. Das Back-Office zeigt keinerlei Hinweis darauf, dass ein Override existiert. Sie debuggen ein Problem und ahnen nicht, dass die Hälfte Ihrer
Cart-Klasse gar nicht die ist, für die Sie sie halten. - Der Klassenindex kann verrotten.
cache/class_index.phpcacht, was was überschreibt. Gerät er aus dem Takt (meist nach einer fehlgeschlagenen Modulinstallation), zeigt der Shop einen Whitescreen, bis Sie die Datei löschen.
Das PrestaShop-Core-Team rät seit 2017 davon ab, Overrides zu schreiben. Damit haben sie recht. Verwenden Sie eines nur, wenn es wirklich keinen Hook, keinen Service zum Dekorieren und kein Ereignis zum Lauschen gibt.
Moderne Alternativen (PS 8+)
Für PS 8 und 9 ersetzen drei Muster die meisten Overrides:
Symfony-Service-Decorators, umhüllen einen Core-Service mit Ihrer eigenen Logik. Mehrere Module können denselben Service ohne Konflikte dekorieren:
# 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, reagieren auf die Persistierung von Entitäten, ohne ObjectModel abzuleiten:
# modules/mymodule/config/services.yml
services:
mymodule.product_listener:
class: MyModule\EventListener\ProductListener
tags:
- { name: doctrine.event_listener, event: postUpdate }
CQRS-Command-/Query-Handler, für Back-Office-Operationen dekorieren Sie den Command-Bus. Die steilste Lernkurve, aber die sauberste Trennung; genau das verwendet neuer PrestaShop-Core-Code intern.
Wenn zwei Module kollidieren
Das sehen Sie in var/logs/:
The method Cart::getOrderTotal is already overridden by module "othermodule".
Die einzige echte Lösung ist manuelles Zusammenführen: Öffnen Sie beide Override-Dateien, vereinen Sie die Logik in einer und installieren Sie das zweite Modul. Löschen Sie nach der Bearbeitung den Klassenindex:
rm var/cache/prod/class_index.php
rm var/cache/dev/class_index.php
Das ist fragil. Wenn eines der Module aktualisiert wird, kann Ihr Merge brechen. Dokumentieren Sie, was Sie zusammengeführt haben, und bewahren Sie die Originaldateien an einem sicheren Ort auf. Und falls ein Modul ein Override ausliefert, wo ein Hook genügen würde, eröffnen Sie bitte ein Support-Ticket beim Entwickler, die meisten von uns wechseln auf Hooks, wenn man darum bittet.
Positionen, Reihenfolge und Transplantationen
Wenn mehrere Module auf demselben Display-Hook registriert sind, bestimmt die Position die Render-Reihenfolge. Der Händler kann sie unter Design → Positionen per Drag-and-drop anordnen, alles aushängen, was er nicht möchte, oder ein Modul von einem Hook auf einen anderen transplantieren.
Die Transplantation funktioniert in den einfachen Fällen. Sie funktioniert nicht immer, und Module, die einen bestimmten Hook voraussetzen (weil sie den Controller-Kontext auslesen, bestimmte $params erwarten oder hook-spezifische Smarty-Variablen verwenden), rendern falsch oder gar nicht. Wenn der Händler klagt „Das Modul ist verschwunden, als ich es verschoben habe", dann liegt es daran.
Sie können die Position auch programmatisch bei der Installation setzen:
// In install(): auf Position 1 verschieben
$this->updatePosition($this->getHookId('displayHome'), false, 1);
Debuggen, wenn ein Hook nicht feuert
Arbeiten Sie diese Punkte der Reihe nach ab. Die Ursache liegt fast immer in den ersten beiden.
-- 1. Ist das Modul auf dem Hook registriert?
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';
-- 2. Ist das Modul aktiv und installiert?
SELECT name, active FROM ps_module WHERE name = 'your_module_name';
Wenn beide Zeilen existieren und die Methode trotzdem nicht feuert, prüfen Sie:
| Symptom | Wahrscheinlichste Ursache | Behebung |
|---|---|---|
| Methode aufgerufen, gibt nichts zurück | Template-Pfad falsch oder fehlendes return bei einem Display-Hook | Debug aktivieren, Template-Pfad prüfen, sicherstellen, dass die gerenderte Ausgabe zurückgegeben wird |
| Feuert auf jeder Seite, obwohl nur Produktseiten gewünscht sind | Keine Controller-Prüfung | if (!($this->context->controller instanceof ProductController)) return ''; |
| Feuert zweimal auf derselben Seite | Zweimal in ps_hook_module registriert | Die doppelte Zeile mit DELETE entfernen oder das Modul zurücksetzen |
$params['product'] ist ein Array, kein Objekt | PS 1.7+ verwendet im Front-Office Presenter-Arrays | $params['product']['id_product'] verwenden, oder new Product($id) aufrufen, wenn Sie das vollständige Objekt benötigen |
| Funktioniert in Dev, bricht in Prod | Kompilierte Smarty-Templates gecacht | Kompilierung erzwingen + Cache leeren unter Erweiterte Einstellungen → Leistung |
Hook-Profiling
Aktivieren Sie das Profiling, um die Laufzeit je Hook zu sehen:
// PS 1.7: defines.inc.php
define('_PS_DEBUG_PROFILING_', true);
// PS 8+: .env.local
APP_DEBUG=1
APP_ENV=dev
Was sich in PrestaShop 9 geändert hat
Wenn Sie ein Modul migrieren, ist dies der Teil, den Sie aufmerksam lesen sollten.
Neue Hooks
PS 9 fügt an Stellen Hooks hinzu, die früher Overrides erforderten: Erweiterungen des Admin-Produktformulars, Anwendung von Preisregeln im Warenkorb, API-Ressourcenoperationen und E-Mail-Versand. Jedes Override, das wir bei unserer PS-9-Migration entfernt haben, wurde durch einen davon ersetzt.
Veraltete (deprecated) Hooks
Hooks, die an die Legacy-Admin-Controller gebunden sind, gelten als veraltet, da diese Controller zu Symfony migrieren. Die Deprecation-Liste steht in _PS_DEPRECATED_HOOKS_, suchen Sie danach in der Core-Konstantendatei. Ihr Modul funktioniert weiterhin, aber bei jedem Seitenaufruf wird eine Deprecation-Warnung protokolliert, und der Hook wird in einem künftigen Minor-Release entfernt.
Overrides werden weniger nützlich, nicht abgeschafft
Overrides funktionieren weiterhin für Legacy-ObjectModel-Klassen. Sie funktionieren nicht für Symfony-Services oder neue Admin-Controller. Es gibt nichts zum Ableiten. Je mehr vom Back-Office zu Symfony migriert, desto geringer wird die Override-Abdeckung. Wir behandeln jedes neue Override, das wir schreiben, als technische Schuld mit bekanntem Ablaufdatum.
Kurzreferenz
// Einen oder mehrere registrieren
$this->registerHook(['displayHeader', 'actionCartSave']);
// Existiert dieser Hook?
$hookId = Hook::getIdByName('displayMyCustomHook');
// Welche Module liegen auf diesem Hook?
$modules = Hook::getHookModuleExecList('displayHeader');
// Einen eigenen Hook feuern
$output = Hook::exec('displayMyHook', ['key' => 'value']);
// Nur auf Produktseiten rendern
public function hookDisplayHeader($params) {
if (!($this->context->controller instanceof ProductController)) {
return '';
}
return $this->display(__FILE__, 'views/templates/hook/header.tpl');
}
-- Alle Hooks, auf denen ein bestimmtes Modul liegt
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 ohne registrierte Module (potenziell verwaiste Hooks)
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;
Weiterführende Lektüre
- Migration auf PrestaShop 9: Der komplette Upgrade-Leitfaden. Welche Hooks in PS 9 hinzugekommen und veraltet sind
- PrestaShop-Child-Themes: Leitfaden zur Anpassung von Classic & Hummingbird, für Änderungen auf Template-Ebene empfiehlt sich ein Child-Theme statt eines Overrides
- Unverzichtbare Werkzeuge für die PrestaShop-Entwicklung, die Debugging-Tools, die wir täglich verwenden
- PrestaShop-Hooks meistern: Entwicklerreferenz für 1.7, 8.x und 9.x, ein längeres Begleitstück mit mehr Beispielen
- Checkout Revolution und Performance Revolution, zwei unserer Module, die stark auf Hooks setzen; nützlich als Produktionsreferenzen