Knowledge Base Guide

Hooki i Overrides PrestaShop: Przewodnik referencyjny dla deweloperów

Referencja hooków i nadpisań PrestaShop — hooki display i action, własne hooki, system nadpisań i migracja do nowoczesnych wzorców.

Czym są hooki i dlaczego są ważne

Hooki to system rozszerzeń PrestaShop — mechanizm, dzięki któremu moduły wchodzą w interakcję z rdzeniem platformy bez modyfikowania plików podstawowych. Każdy instalowany moduł opiera się na hookach, aby podłączyć się we właściwym miejscu i we właściwym czasie.

Jeśli kiedykolwiek zastanawiałeś się, skąd moduł „wie”, że ma wyświetlić coś pod ceną produktu, albo jak reaguje, gdy klient składa zamówienie — odpowiedzią są hooki.

Hooki są dla PrestaShop tym, czym gniazdka elektryczne są dla domu. Rdzeń dostarcza gniazdka w określonych miejscach, a moduły się do nich podłączają. Nie przerabiasz instalacji elektrycznej — podłączasz się tam, gdzie potrzebujesz zasilania.

Hooki Display vs hooki Action

PrestaShop posiada dwa rodzaje hooków:

  • Hooki display (z prefiksem display) — wizualne punkty wstawiania. Po wywołaniu zbierają HTML z zarejestrowanych modułów i renderują go. Przykład: displayProductAdditionalInfo dodaje treść poniżej ceny produktu.
  • Hooki action (z prefiksem action) — powiadomienia o zdarzeniach. Nie generują wyjścia — informują moduły, że coś się wydarzyło. Przykład: actionCartSave uruchamia się po aktualizacji koszyka.

Jak działa Hook Dispatcher

Gdy PrestaShop dociera do punktu hooka, wywołuje Hook::exec(), który wyszukuje zarejestrowane moduły w ps_hook_module, sortuje według pozycji, wywołuje metodę hooka każdego modułu z tablicą $params, a w przypadku hooków display łączy zwrócony HTML.

Najważniejsze hooki, które każdy deweloper powinien znać

Hooki display Front Office

Nazwa hookaKiedy się uruchamiaTypowe zastosowanie
displayHeaderWewnątrz <head> na każdej stronieMeta tagi, skrypty śledzenia
displayFooterStopka stronyWidżety stopki, analityka
displayHomeObszar treści strony głównejBanery, polecane produkty
displayProductAdditionalInfoPoniżej „Dodaj do koszyka”Szacowany czas dostawy, tabele rozmiarów
displayShoppingCartStrona podsumowania koszykaCross-sell, kalkulatory wysyłki
displayOrderConfirmationPo złożeniu zamówieniaŚledzenie konwersji
displayCustomerAccountStrona „Moje konto”Własne sekcje konta
displayBannerGóra strony, nad nagłówkiemOgłoszenia o promocjach

Hooki display Back Office

Nazwa hookaKiedy się uruchamiaTypowe zastosowanie
displayBackOfficeHeaderAdmin <head>CSS/JS admina, powiadomienia
displayAdminOrderStrona szczegółów zamówieniaWłasne panele, integracje kurierskie
displayAdminProductsExtraZakładka edytora produktówWłasne pola produktu

Hooki action

Nazwa hookaKiedy się uruchamiaTypowe zastosowanie
actionCartSaveKoszyk utworzony lub zaktualizowanyRezerwacja stanów magazynowych, analityka
actionOrderStatusUpdateZmiana statusu zamówieniaPowiadomienia, synchronizacja z ERP
actionProductUpdateProdukt zapisany w adminieSynchronizacja z zewnętrznym katalogiem
actionCustomerAccountAddRejestracja nowego klientaE-mail powitalny, synchronizacja z CRM
actionValidateOrderZamówienie zwalidowanePrzetwarzanie płatności, aktualizacja stanu magazynowego
actionFrontControllerSetMediaKontroler front ładuje zasobyRejestracja CSS/JS
actionAdminControllerSetMediaKontroler admina ładuje zasobyRejestracja CSS/JS admina

Rejestracja zasobów

Od PS 1.7 właściwym sposobem dodawania CSS/JS jest rejestracja zasobów wewnątrz 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]
    );
}

Umożliwia to optymalizację CCC (Combine, Compress, Cache) — znacznie lepsze rozwiązanie niż wstrzykiwanie surowych tagów przez displayHeader.

Jak używać hooków w module

Rejestracja hooków podczas instalacji

public function install()
{
    return parent::install()
        && $this->registerHook('displayProductAdditionalInfo')
        && $this->registerHook('actionCartSave')
        && $this->registerHook('actionFrontControllerSetMedia');
}

Każde wywołanie wstawia wiersz do ps_hook_module. Jeśli zapomnisz o rejestracji, metoda hooka nigdy się nie uruchomi — nawet jeśli metoda istnieje.

Częsty problem przy debugowaniu: metoda hooka istnieje i jest poprawnie nazwana, ale moduł nigdy nie zarejestrował się na hooku podczas instalacji. Jeśli dodajesz hook po pierwszym wydaniu, potrzebujesz skryptu aktualizacji lub resetu modułu.

Implementacja metody hooka

Konwencja nazewnictwa metod: hook + nazwa hooka z wielką pierwszą literą.

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');
}

// Hook action — brak wartości zwracanej
public function hookActionCartSave($params)
{
    if (!isset($params['cart'])) {
        return;
    }
    $this->logCartActivity($params['cart']->id);
}

Tablica $params

Zawartość różni się w zależności od hooka. Najczęstsze: $params['cart'], $params['order'], $params['product'], $params['customer']. Aby sprawdzić, co dany hook przekazuje, dodaj tymczasowe logowanie:

file_put_contents(
    _PS_ROOT_DIR_ . '/var/logs/hook_debug.log',
    print_r(array_keys($params), true),
    FILE_APPEND
);

Znajdowanie dostępnych hooków

1. Tryb debugowania

Włącz w Advanced Parameters → Performance → Debug mode: YES. Front office będzie wyświetlać nazwy hooków w punktach wstawiania. W PS 8+ pasek debugowania Symfony pokazuje wszystkie hooki wywołane w ramach zapytania.

Nigdy nie włączaj trybu debugowania na sklepie produkcyjnym. Ujawnia dane wewnętrzne i spowalnia stronę.

2. Zapytanie do bazy danych

-- 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. Przeszukiwanie szablonów i kodu źródłowego

# Display hooks in theme templates
grep -rn "{hook " themes/your-theme/templates/

# Action hooks in PHP core
grep -rn "Hook::exec" classes/ controllers/ src/

Tworzenie własnych hooków

Gdy wbudowane hooki nie uruchamiają się dokładnie w potrzebnym momencie, utwórz własne:

// 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);

Zarejestruj go w install() tak jak każdy inny hook. Aby nadać mu przyjazną nazwę w Positions:

$hook = new Hook();
$hook->name = 'displayMyModuleExtraContent';
$hook->title = 'My Module - Extra Content Area';
$hook->add();

Twórz własne hooki, gdy Twój moduł oferuje punkty rozszerzeń, których potrzebują inne moduły. Nie twórz ich do wewnętrznej logiki — używaj zwykłych metod.

System override

Override zastępują klasy i kontrolery rdzenia bez modyfikowania plików podstawowych. Były główną metodą personalizacji w PS 1.5/1.6, zanim hooki stały się wszechstronne.

Jak działają override

Override rozszerza klasę rdzenia i zastępuje określone metody. Autoloader sprawdza najpierw katalog 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;
    }
}

Struktura katalogów odzwierciedla rdzeń: override/classes/, override/controllers/front/, override/controllers/admin/.

Override vs hook

ScenariuszPodejście
Dodanie treści na stronęHook display
Reakcja na zdarzenieHook action
Dodanie CSS/JSHook rejestracji zasobów
Zmiana obliczeń rdzeniaOverride (jeśli nie istnieje hook)
Modyfikacja zapytania SQL rdzeniaOverride (jeśli nie istnieje hook)

Problemy z override

  • Konflikty: Dwa moduły nie mogą nadpisać tej samej metody. Drugi nie zainstaluje się.
  • Awarie przy aktualizacji: Sygnatury metod rdzenia zmieniają się między wersjami. Override dla PS 1.7.7 może wywołać błąd na 1.7.8.
  • Ukryte zachowanie: Override są niewidoczne z Back Office, co utrudnia debugowanie.
  • Uszkodzenie indeksu: Mapowanie class_index.php może ulec uszkodzeniu, psując cały sklep.
Zespół PrestaShop aktywnie odradza używanie override od wersji 1.7. Jeśli sięgasz po override, najpierw sprawdź, czy istnieje hook lub alternatywa oparta na Symfony.

Alternatywy dla override (PS 8+)

Dekoratory serwisów Symfony

Opakowuj serwisy rdzenia własną logiką — wiele dekoratorów może działać w łańcuchu bez konfliktów:

# 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

Reaguj na zmiany encji bez nadpisywania ObjectModel:

# modules/mymodule/config/services.yml
services:
    mymodule.product_listener:
        class: MyModule\EventListener\ProductListener
        tags:
            - { name: doctrine.event_listener, event: postUpdate }

CQRS Commands and Queries

Wpływaj na operacje admina za pomocą handlerów poleceń i zapytań. To najnowocześniejsze podejście, wymagające znajomości architektury Symfony w PrestaShop.

Rozwiązywanie konfliktów override

Gdy dwa moduły nadpisują tę samą metodę, zobaczysz:

The method Cart::getOrderTotal is already overridden by module "othermodule".

Rozwiązaniem jest ręczne scalanie — połącz logikę obu modułów w jednym pliku override. Po edycji przebuduj indeks klas:

# Delete class index — PrestaShop rebuilds automatically
rm var/cache/prod/class_index.php
rm var/cache/dev/class_index.php
Ręczne scalanie override jest kruche. Gdy którykolwiek moduł się zaktualizuje, możesz potrzebować ponownego scalenia. Dokumentuj, co scałeś i dlaczego.

Jeśli moduł używa override tam, gdzie wystarczyłby hook, skontaktuj się z deweloperem i poproś o rozwiązanie oparte na hookach.

Priorytet hooków i kolejność wykonywania

Gdy wiele modułów jest zarejestrowanych na tym samym hooku display, pozycja określa kolejność. Zarządzaj tym w Design → Positions, gdzie możesz przeciągać moduły, odłączać je lub przenosić do innych hooków.

Transplantacja

Przenieś moduł z jednego hooka do drugiego przez Design → Positions → Transplant a Module. Nie każda transplantacja wyświetla się poprawnie — zależy to od szablonów modułu.

Programowe ustawianie pozycji

// In install() — move to position 1
$this->updatePosition($this->getHookId('displayHome'), false, 1);

Debugowanie hooków

Systematyczne sprawdzanie

-- 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';

Częste problemy

ObjawPrzyczynaRozwiązanie
Metoda nigdy nie jest wywoływanaBrak rejestracji na hookuSprawdź ps_hook_module; zresetuj moduł
Zwraca pusty ciągNie znaleziono szablonuSprawdź ścieżkę szablonu, włącz błędy
Uruchamia się na złych stronachBrak sprawdzania stronyDodaj instanceof ProductController
Uruchamia się dwukrotniePodwójna rejestracjaSprawdź zduplikowane wiersze
$params['product'] jest tablicąPS 1.7+ używa tablicUżyj $params['product']['id_product']

Profilowanie hooków

Włącz profilowanie, aby zobaczyć czas wykonania każdego hooka:

// PS 1.7 — defines.inc.php
define('_PS_DEBUG_PROFILING_', true);

// PS 8+ — .env.local
APP_DEBUG=1
APP_ENV=dev

Zmiany w PrestaShop 9

Nowe hooki

PS 9 dodaje hooki w obszarach, które wcześniej wymagały override: rozszerzenia formularza produktów w adminie, zastosowanie reguł cenowych koszyka, operacje na zasobach API oraz proces wysyłania e-maili.

Przestarzałe hooki

Hooki powiązane ze starszymi kontrolerami admina są oznaczone jako przestarzałe, ponieważ te kontrolery migrują do Symfony. Sprawdź _PS_DEPRECATED_HOOKS_, aby zobaczyć pełną listę.

Przyszłość systemu override

Override nadal działają dla starszych klas ObjectModel, ale nowe serwisy Symfony i kontrolery admina nie mogą ich używać. System override zostanie ostatecznie usunięty wraz z zakończeniem migracji do Symfony.

Jeśli zaczynasz nowy moduł dzisiaj, buduj wyłącznie z hookami i serwisami Symfony. Każdy override, który teraz napiszesz, to dług techniczny, który później usuniesz.

Szybka ściąga

// 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;

Opanuj najpierw hooki — pokrywają zdecydowaną większość przypadków użycia. Poznaj override, aby rozumieć starszy kod i rozwiązywać konflikty. W PrestaShop 8+ inwestuj w alternatywy oparte na Symfony. Ekosystem idźie do przodu, a hooki wiodą prym.

More guides available

Browse our knowledge base for more practical PrestaShop tutorials, or reach out if you need help.

Ładowanie...
Powrót do góry