Anleitungen Anleitung

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 rendernEinem display*-Hook, der ein Smarty-Template zurückgibtecho innerhalb von displayHeader
Auf eine Zustandsänderung reagieren (Bestellung aufgegeben, Warenkorb gespeichert, Produkt aktualisiert)Einem action*-HookEinem Override des Controllers
CSS oder JS zum Front-Office hinzufügenactionFrontControllerSetMedia + registerStylesheet / registerJavascriptRohen <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 gibtEinem Symfony-Service-Decorator (PS 8+) oder, als letztes Mittel, einem OverrideDem 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

HookFeuertEinsatz für
displayProductAdditionalInfoUnter dem „In den Warenkorb"-ButtonLieferschätzungen, Größentabellen, zusätzliche Inhalte auf der Produktseite, die wertvollste Fläche einer Produktseite
displayShoppingCartWarenkorb-ÜbersichtsseiteCross-Selling, Versandrechner, Dringlichkeitshinweise
displayOrderConfirmationDankesseite nach dem CheckoutConversion-Tracking, Upsells nach dem Kauf, Empfehlungsaufrufe
displayCustomerAccountSeite „Mein Konto"Eigene Bereiche, Wunschlisten, Treuepunkte, RMA-Anfragen
displayBannerSeitenanfang, oberhalb des HeadersSale-Streifen, Gratisversand-Balken, DSGVO-Hinweise
displayHomeInhaltsbereich der StartseiteHervorgehobene Kollektionen, Hero-Module, wiederverwendbare HTML-Blöcke

Back-Office, Display

HookFeuertEinsatz für
displayAdminOrderBestelldetailseiteEigene Panels, Versandanbindungen, ERP-Sync-Status, Betrugssignale
displayAdminProductsExtraNeuer Reiter im ProdukteditorEigene Produktfelder, Daten von Drittanbietern
displayBackOfficeHeaderInnerhalb des Admin-<head>Admin-CSS/JS, Dashboard-Widgets, Update-Hinweise

Action-Hooks

HookFeuertEinsatz für
actionValidateOrderBestellung erfolgreich validiertZahlungsbestätigung, Bestandsabbuchung, Workflow nach der Zahlung, der wichtigste Action-Hook
actionOrderStatusUpdateBestellstatus ändert sichVersandbenachrichtigungen, ERP-Sync, Kunden-E-Mails
actionCartSaveWarenkorb erstellt oder aktualisiertTrigger für abgebrochene Warenkörbe, Bestandsreservierung, Analytics
actionProductUpdateProdukt im Admin gespeichertSync mit externem Katalog, Reindexierung der Suche, Bild-Neugenerierung
actionCustomerAccountAddNeuer Kunde registriert sichWillkommens-E-Mail, CRM-Sync, Segmentierung
actionFrontControllerSetMediaFront-Controller lädt AssetsCSS/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()
        &amp;&amp; $this->registerHook('displayProductAdditionalInfo')
        &amp;&amp; $this->registerHook('actionCartSave')
        &amp;&amp; $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.

PrestaShop-8-Back-Office: Positionen-Seite mit Hook-Registrierungen und Modulreihenfolge

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.php cacht, 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:

SymptomWahrscheinlichste UrsacheBehebung
Methode aufgerufen, gibt nichts zurückTemplate-Pfad falsch oder fehlendes return bei einem Display-HookDebug aktivieren, Template-Pfad prüfen, sicherstellen, dass die gerenderte Ausgabe zurückgegeben wird
Feuert auf jeder Seite, obwohl nur Produktseiten gewünscht sindKeine Controller-Prüfungif (!($this->context->controller instanceof ProductController)) return '';
Feuert zweimal auf derselben SeiteZweimal in ps_hook_module registriertDie doppelte Zeile mit DELETE entfernen oder das Modul zurücksetzen
$params['product'] ist ein Array, kein ObjektPS 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 ProdKompilierte Smarty-Templates gecachtKompilierung 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

Lade ...
Nach oben