PrestaShop Hooks & Overrides: Entwickler-Referenz
Entwickler-Referenz für PrestaShop Hooks und Overrides — Display-Hooks, Action-Hooks, eigene Hooks, das Override-System und Migration zu modernen Patterns.
Was sind Hooks und warum sie wichtig sind
Hooks sind das Erweiterungssystem von PrestaShop — der Mechanismus, über den Module mit der Kernplattform interagieren, ohne Kerndateien zu verändern. Jedes Modul, das Sie installieren, nutzt Hooks, um sich an der richtigen Stelle zum richtigen Zeitpunkt einzuklinken.
Wenn Sie sich jemals gefragt haben, wie ein Modul „weiß“, dass es etwas unterhalb des Produktpreises anzeigen soll, oder wie es reagiert, wenn ein Kunde eine Bestellung aufgibt — die Antwort lautet: Hooks.
Hooks sind für PrestaShop das, was Steckdosen für ein Haus sind. Der Kern stellt Steckdosen an bestimmten Stellen bereit, und Module stecken sich dort ein. Sie verkabeln nicht das ganze Haus neu — Sie stecken den Stecker dort ein, wo Sie Strom brauchen.
Display Hooks vs Action Hooks
PrestaShop verfügt über zwei Arten von Hooks:
- Display Hooks (mit dem Präfix
display) — Visuelle Einfügepunkte. Wenn sie ausgelöst werden, sammeln sie HTML von registrierten Modulen und rendern es. Beispiel:displayProductAdditionalInfofügt Inhalte unterhalb des Produktpreises hinzu. - Action Hooks (mit dem Präfix
action) — Ereignisbenachrichtigungen. Sie erzeugen keine Ausgabe — sie teilen Modulen mit, dass etwas passiert ist. Beispiel:actionCartSavewird ausgelöst, nachdem ein Warenkorb aktualisiert wurde.
Wie der Hook Dispatcher funktioniert
Wenn PrestaShop einen Hook-Punkt erreicht, ruft es Hook::exec() auf, das registrierte Module in ps_hook_module nachschlägt, nach Position sortiert, die Hook-Methode jedes Moduls mit einem $params-Array aufruft und bei Display Hooks das zurückgegebene HTML zusammenfügt.
Wichtige Hooks, die jeder Entwickler kennen sollte
Front Office Display Hooks
| Hook-Name | Wann er ausgelöst wird | Typischer Anwendungsfall |
|---|---|---|
displayHeader | Innerhalb von <head> auf jeder Seite | Meta-Tags, Tracking-Skripte |
displayFooter | Seiten-Footer | Footer-Widgets, Analytics |
displayHome | Inhaltsbereich der Startseite | Banner, hervorgehobene Produkte |
displayProductAdditionalInfo | Unterhalb von „In den Warenkorb“ | Lieferschätzungen, Größenratgeber |
displayShoppingCart | Warenkorb-Übersichtsseite | Cross-Selling, Versandrechner |
displayOrderConfirmation | Nach Aufgabe der Bestellung | Conversion-Tracking |
displayCustomerAccount | „Mein Konto“-Seite | Individuelle Kontobereiche |
displayBanner | Seitenoberkante, über dem Header | Aktionsankündigungen |
Back Office Display Hooks
| Hook-Name | Wann er ausgelöst wird | Typischer Anwendungsfall |
|---|---|---|
displayBackOfficeHeader | Admin <head> | Admin-CSS/JS, Benachrichtigungen |
displayAdminOrder | Bestelldetailseite | Individuelle Panels, Versandintegrationen |
displayAdminProductsExtra | Produkt-Editor-Tab | Benutzerdefinierte Produktfelder |
Action Hooks
| Hook-Name | Wann er ausgelöst wird | Typischer Anwendungsfall |
|---|---|---|
actionCartSave | Warenkorb erstellt oder aktualisiert | Bestandsreservierung, Analytics |
actionOrderStatusUpdate | Bestellstatus ändert sich | Benachrichtigungen, ERP-Synchronisation |
actionProductUpdate | Produkt im Admin gespeichert | Externe Katalogsynchronisation |
actionCustomerAccountAdd | Neuer Kunde registriert sich | Willkommens-E-Mail, CRM-Synchronisation |
actionValidateOrder | Bestellung validiert | Zahlungsabwicklung, Bestandsaktualisierung |
actionFrontControllerSetMedia | Front-Controller lädt Assets | CSS/JS registrieren |
actionAdminControllerSetMedia | Admin-Controller lädt Assets | Admin-CSS/JS registrieren |
Asset-Registrierung
Seit PS 1.7 ist der korrekte Weg, CSS/JS hinzuzufügen, die Asset-Registrierung innerhalb von 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]
);
}
Dies ermöglicht die CCC-Optimierung (Combine, Compress, Cache) — deutlich besser als das Einfügen von rohen Tags über displayHeader.
Hooks in einem Modul verwenden
Hooks während der Installation registrieren
public function install()
{
return parent::install()
&& $this->registerHook('displayProductAdditionalInfo')
&& $this->registerHook('actionCartSave')
&& $this->registerHook('actionFrontControllerSetMedia');
}
Jeder Aufruf fügt eine Zeile in ps_hook_module ein. Wenn Sie die Registrierung vergessen, wird die Hook-Methode nie ausgeführt — selbst wenn die Methode existiert.
Häufige Fehlerquelle beim Debugging: Die Hook-Methode existiert und ist korrekt benannt, aber das Modul hat sich während der Installation nie auf dem Hook registriert. Wenn Sie einen Hook nach der Erstveröffentlichung hinzufügen, benötigen Sie ein Upgrade-Skript oder einen Modul-Reset.
Die Hook-Methode implementieren
Namenskonvention für Methoden: hook + Hook-Name mit großem Anfangsbuchstaben.
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 — kein Rückgabewert
public function hookActionCartSave($params)
{
if (!isset($params['cart'])) {
return;
}
$this->logCartActivity($params['cart']->id);
}
Das $params-Array
Der Inhalt variiert je nach Hook. Häufig verwendet: $params['cart'], $params['order'], $params['product'], $params['customer']. Um zu sehen, was ein Hook übergibt, fügen Sie ein temporäres Logging hinzu:
file_put_contents(
_PS_ROOT_DIR_ . '/var/logs/hook_debug.log',
print_r(array_keys($params), true),
FILE_APPEND
);
Verfügbare Hooks finden
1. Debug-Modus
Aktivieren Sie ihn unter Erweiterte Einstellungen → Leistung → Debug-Modus: JA. Das Front Office zeigt dann Hook-Namen an den Einfügepunkten an. In PS 8+ zeigt die Symfony-Debug-Toolbar alle pro Anfrage ausgelösten Hooks an.
Aktivieren Sie niemals den Debug-Modus in einem Produktiv-Shop. Er legt interne Daten offen und verlangsamt die Seite.
2. Datenbankabfrage
-- 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. Templates und Quellcode durchsuchen
# Display hooks in theme templates
grep -rn "{hook " themes/your-theme/templates/
# Action hooks in PHP core
grep -rn "Hook::exec" classes/ controllers/ src/
Eigene Hooks erstellen
Wenn integrierte Hooks nicht genau an der benötigten Stelle auslösen, erstellen Sie eigene:
// 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);
Registrieren Sie ihn in install() wie jeden anderen Hook. Um ihm einen benutzerfreundlichen Namen unter „Positionen“ zu geben:
$hook = new Hook();
$hook->name = 'displayMyModuleExtraContent';
$hook->title = 'My Module - Extra Content Area';
$hook->add();
Erstellen Sie eigene Hooks, wenn Ihr Modul Erweiterungspunkte bereitstellt, die andere Module benötigen. Erstellen Sie sie nicht für rein interne Logik — verwenden Sie dafür reguläre Methoden.
Das Override-System
Overrides ersetzen Kernklassen und Controller, ohne Kerndateien zu verändern. Sie waren die primäre Anpassungsmethode in PS 1.5/1.6, bevor Hooks umfassend verfügbar waren.
Wie Overrides funktionieren
Ein Override erweitert eine Kernklasse und ersetzt bestimmte Methoden. Der Autoloader prüft zuerst 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;
}
}
Die Verzeichnisstruktur spiegelt den Kern wider: override/classes/, override/controllers/front/, override/controllers/admin/.
Override vs Hook
| Szenario | Ansatz |
|---|---|
| Inhalt zu einer Seite hinzufügen | Display Hook |
| Auf ein Ereignis reagieren | Action Hook |
| CSS/JS hinzufügen | Asset-Registrierung-Hook |
| Eine Kernberechnung ändern | Override (falls kein Hook existiert) |
| Eine Kern-SQL-Abfrage ändern | Override (falls kein Hook existiert) |
Die Probleme mit Overrides
- Konflikte: Zwei Module können nicht dieselbe Methode überschreiben. Das zweite scheitert bei der Installation.
- Upgrade-Brüche: Kern-Methodensignaturen ändern sich zwischen Versionen. Ein Override für PS 1.7.7 kann unter 1.7.8 zu Abstürzen führen.
- Verstecktes Verhalten: Overrides sind im Back Office nicht sichtbar, was das Debugging erschwert.
- Index-Beschädigung: Das
class_index.php-Mapping kann beschädigt werden und den gesamten Shop lahmlegen.
Das PrestaShop-Kernteam rät seit Version 1.7 aktiv von Overrides ab. Wenn Sie zu einem Override greifen möchten, prüfen Sie zunächst, ob ein Hook oder eine Symfony-basierte Alternative existiert.
Override-Alternativen (PS 8+)
Symfony Service Decorators
Umhüllen Sie Kerndienste mit Ihrer eigenen Logik — mehrere Decorators können ohne Konflikte verkettet werden:
# 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 Listeners
Reagieren Sie auf Entity-Änderungen, ohne ObjectModel zu überschreiben:
# modules/mymodule/config/services.yml
services:
mymodule.product_listener:
class: MyModule\EventListener\ProductListener
tags:
- { name: doctrine.event_listener, event: postUpdate }
CQRS Commands und Queries
Klinken Sie sich über Command/Query-Handler in Admin-Operationen ein. Der modernste Ansatz, der Vertrautheit mit der Symfony-Architektur von PrestaShop erfordert.
Override-Konflikte lösen
Wenn zwei Module dieselbe Methode überschreiben, sehen Sie:
The method Cart::getOrderTotal is already overridden by module "othermodule".
Die Lösung ist manuelles Zusammenführen — kombinieren Sie die Logik beider Module in einer einzigen Override-Datei. Bauen Sie nach der Bearbeitung den Klassenindex neu auf:
# Delete class index — PrestaShop rebuilds automatically
rm var/cache/prod/class_index.php
rm var/cache/dev/class_index.php
Manuelles Zusammenführen von Overrides ist fragil. Wenn eines der Module aktualisiert wird, müssen Sie möglicherweise erneut zusammenführen. Dokumentieren Sie, was Sie zusammengeführt haben und warum.
Wenn ein Modul ein Override verwendet, obwohl ein Hook funktionieren würde, kontaktieren Sie den Entwickler und fordern Sie eine Hook-basierte Lösung an.
Hook-Priorität und Ausführungsreihenfolge
Wenn mehrere Module auf demselben Display Hook registriert sind, bestimmt die Position die Reihenfolge. Verwalten Sie dies unter Design → Positionen, wo Sie Module per Drag-and-Drop verschieben, aushängen oder auf andere Hooks transplantieren können.
Transplantieren
Verschieben Sie ein Modul von einem Hook auf einen anderen über Design → Positionen → Modul transplantieren. Nicht alle Transplantationen werden korrekt dargestellt — das hängt von den Templates des Moduls ab.
Programmatische Positionierung
// In install() — move to position 1
$this->updatePosition($this->getHookId('displayHome'), false, 1);
Hooks debuggen
Systematische Prüfungen
-- 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';
Häufige Probleme
| Symptom | Ursache | Lösung |
|---|---|---|
| Methode wird nie aufgerufen | Nicht auf Hook registriert | ps_hook_module prüfen; Modul zurücksetzen |
| Gibt leeren String zurück | Template nicht gefunden | Template-Pfad prüfen, Fehler aktivieren |
| Wird auf falschen Seiten ausgelöst | Keine Seitenprüfung | instanceof ProductController hinzufügen |
| Wird zweimal ausgelöst | Doppelt registriert | Auf doppelte Einträge prüfen |
| $params['product'] ist ein Array | PS 1.7+ verwendet Arrays | $params['product']['id_product'] verwenden |
Hook-Profiling
Aktivieren Sie Profiling, um die Zeitmessung pro Hook zu sehen:
// PS 1.7 — defines.inc.php
define('_PS_DEBUG_PROFILING_', true);
// PS 8+ — .env.local
APP_DEBUG=1
APP_ENV=dev
Änderungen in PrestaShop 9
Neue Hooks
PS 9 fügt Hooks in Bereichen hinzu, die zuvor Overrides erforderten: Erweiterungen des Admin-Produktformulars, Anwendung von Warenkorbpreisregeln, API-Ressourcenoperationen und der E-Mail-Versandprozess.
Veraltete Hooks
Hooks, die an Legacy-Admin-Controller gebunden sind, werden als veraltet markiert, sobald diese Controller auf Symfony migriert werden. Die vollständige Liste finden Sie unter _PS_DEPRECATED_HOOKS_.
Zukunft des Override-Systems
Overrides funktionieren weiterhin für Legacy-ObjectModel-Klassen, aber neue Symfony-Dienste und Admin-Controller können sie nicht verwenden. Das Override-System wird letztendlich entfernt, wenn die Symfony-Migration abgeschlossen ist.
Wenn Sie heute ein neues Modul starten, bauen Sie ausschließlich mit Hooks und Symfony-Diensten. Jedes Override, das Sie jetzt schreiben, ist technische Schuld, die Sie später beseitigen müssen.
Kurzreferenz
// 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;
Beherrschen Sie zuerst Hooks — sie decken die große Mehrheit der Anwendungsfälle ab. Lernen Sie Overrides, um Legacy-Code zu verstehen und Konflikte zu lösen. Für PrestaShop 8+ investieren Sie in Symfony-basierte Alternativen. Das Ökosystem entwickelt sich weiter, und Hooks führen dabei den Weg.
More guides available
Browse our knowledge base for more practical PrestaShop tutorials, or reach out if you need help.