You are using an outdated browser. For a faster, safer browsing experience, upgrade for free today.

Jak zrychlit modul pro obchod OpenCart

Možná jste nainstalovali modul pro OpenCart 3 a po jeho zapnutí se stránka, na které se modul vykresluje, výrazně zpomalila. Častou příčinou bývá to, že modul při každém načtení znovu skládá stejná data z databáze. Řešením bývá doplnění datové cache do system/storage/cache/.

Tento návod popisuje obecný a bezpečný postup pro moduly třetích stran v OpenCart 3. Je zaměřený na situaci, kdy modul na frontendu opakovaně generuje stejný výstup a jeho výpočet je zbytečně náročný.

Jak funguje file cache v OpenCart 3

OpenCart má vestavěnou třídu Cache. Pokud je použitý adaptér file, data se ukládají do souborů ve složce system/storage/cache/.

Průběh je obvykle takovýto:

  • První požadavek: Modul vypočítá data, uloží je do cache a vrátí výstup.
  • Další požadavky: Modul se nejdříve pokusí načíst data z cache. Pokud existují, znovu nevykonává náročné SQL dotazy ani další náročnou logiku.
  • Expirace: U file cache v OpenCartu není potřeba ukládat vlastní timestamp do dat. Expirace je součástí názvu cache souboru a řeší ji samotná knihovna cache.
  • Invalidace: Když se změní nastavení modulu nebo zdrojová data, je potřeba příslušnou cache smazat metodou $this->cache->delete(...), případně při vlastním prefixu ručně smazat odpovídající soubory.

Kdy se tento postup hodí

Typicky tehdy, když modul na frontendu opakovaně:

  • načítá kategorie, produkty nebo jiné seznamy z databáze,
  • sestavuje složitá vícerozměrná pole, HTML bloky nebo stromové struktury,
  • volá více modelů uvnitř několika vnořených cyklů,
  • vrací stejný výsledek pro mnoho návštěvníků.

Naopak opatrnost je potřeba u modulů, jejichž výstup závisí na konkrétním zákazníkovi, košíku, přihlášení, měně, aktivní kategorii, jazyku nebo jiném kontextu. V takovém případě musí být tyto hodnoty součástí cache klíče, nebo se daná část nesmí cachovat vůbec.

Krok 1: Najděte část frontend controlleru, která server nejvíce zatěžuje

Otevřete controller modulu, nejčastěji v cestě catalog/controller/extension/module/[nazov_modulu].php.

Obvykle hledejte metodu index($setting) nebo podobnou hlavní metodu. Cílem je obalit cache pouze tu část, která připravuje datové pole pro šablonu. Běžné načtení jazyka, připojení JS/CSS nebo jiné lehké operace mohou zůstat mimo cache.

Krok 2: Sestavte správný cache klíč

Klíč musí rozlišit všechny stavy, ve kterých může mít modul jiný výstup. Nestačí jen název modulu. Často je potřeba zahrnout například:

  • nastavení konkrétní instance modulu v parametru $setting,
  • store_id, pokud máte více obchodů,
  • language_id, pokud modul vypisuje překlady nebo lokalizovaná data,
  • customer_group_id, pokud se liší ceny nebo viditelnost produktů,
  • theme, pokud se datová struktura liší podle šablony,
  • path, product_id, manufacturer_id nebo jiný parametr URL, pokud se podle něj mění aktivní položky nebo obsah modulu.

Bezpečný obecný vzor:

$customer_group_id = $this->customer->isLogged()
    ? (int)$this->customer->getGroupId()
    : (int)$this->config->get('config_customer_group_id');

$cache_key = 'module_[nazov_modulu].' . md5(json_encode(array(
    'setting'           => $setting,
    'store_id'          => (int)$this->config->get('config_store_id'),
    'language_id'       => (int)$this->config->get('config_language_id'),
    'customer_group_id' => $customer_group_id,
    'theme'             => $this->config->get('theme_' . $this->config->get('config_theme') . '_directory'),
    'path'              => isset($this->request->get['path']) ? (string)$this->request->get['path'] : ''
)));

Pokud výstup na path nebo jiných URL proměnných nezávisí, do klíče je nedávejte. Zbytečně byste si rozdělili cache do mnoha souborů.

Krok 3: Nejdříve zkuste načíst data z cache

Na začátek náročné části vložte kontrolu cache. Pokud data existují, rovnou je použijte.

$cache_data = $this->cache->get($cache_key);

if ($cache_data !== false) {
    return $this->load->view('extension/module/[nazov_modulu]', $cache_data);
}

Tady je důležité, že ukládáte přímo finální datové pole pro šablonu, typicky proměnnou $data. OpenCart file cache už sama řeší životnost souboru, takže není potřeba do cache ukládat další pole typu expire nebo ttl.

Krok 4: Ponechte původní logiku modulu a uložte výsledná data

Původní náročný kód zůstane prakticky beze změny. Jen po jeho dokončení uložte finální data do cache.

// Pokud cache neexistuje, proběhne původní logika modulu:
// - načtení kategorií
// - načtení produktů
// - skládání stromu
// - příprava pole $data pro twig

$this->cache->set($cache_key, $data);

return $this->load->view('extension/module/[nazov_modulu]', $data);

Tím dosáhnete toho, že první návštěvník výstup vypočítá a další už dostanou hotová data z cache souboru.

Krok 5: Invalidace cache po změně nastavení modulu

Otevřete administrátorský controller modulu, obvykle admin/controller/extension/module/[nazov_modulu].php.

Po uložení nastavení vložte smazání cache. Pokud používáte standardní OpenCart cache klíč přes $this->cache->set(), doporučený postup je mazat společný prefix, ne konkrétní hash.

$this->cache->delete('module_[nazov_modulu]');

U file cache v OpenCartu se tím smažou všechny soubory začínající tímto prefixem, například cache.module_[nazov_modulu].abc123.... To je důležité, protože jednotlivé varianty modulu bývají rozlišené hashem podle jazyka, obchodu, nastavení nebo URL kontextu.

Pokud však modul používá vlastní název souboru nebo vlastní prefix mimo standardní cache adaptér, je potřeba mazat právě tento vlastní prefix, případně konkrétní soubory přes glob() a unlink().

Krok 6: Invalidace i při změně zdrojových dat

Samotné uložení nastavení modulu často nestačí. Pokud modul zobrazuje produkty, kategorie, výrobce, recenze nebo jiná data z administrace, je vhodné mazat cache i při změně těchto dat.

Příklady:

  • modul pracující s produkty: mazat cache po uložení nebo smazání produktu,
  • modul pracující s kategoriemi: mazat cache po změně kategorie,
  • modul se značkami nebo blogem: mazat cache při úpravě odpovídajících záznamů.

Jinak se může stát, že modul zůstane funkční a rychlý, ale bude určitou dobu zobrazovat zastaralá data až do expirace cache.

Krok 7: Jak dlouho cache žije

Ve standardním OpenCart 3 je u file cache běžná životnost často 3600 sekund, tedy 1 hodina. Tato hodnota se předává při vytvoření instance cache a file adaptér ji zapisuje do názvu souboru.

Praktický důsledek:

  • po uplynutí času se cache soubor považuje za expirovaný,
  • staré soubory se průběžně mažou při práci s cache,
  • není potřeba vlastní ruční práce s timestampem uvnitř uložených dat.

Krok 8: Jak cache ručně vyčistit při testování

Při ladění nebo testování můžete cache odstranit i ručně ve složce system/storage/cache/.

  • Ruční smazání konkrétní skupiny: smažte soubory začínající na cache.module_[nazov_modulu].
  • Smazání po uložení v administraci: pokud jste správně doplnili $this->cache->delete(...), cache se smaže sama při uložení modulu
  • Automatická expirace: pokud nic nemažete, stará cache sama přestane platit po uplynutí nastavené doby

Poznámka: v některých instalacích OpenCartu tlačítka pro vyčištění vývojářských cache řeší hlavně Twig, SASS nebo modification cache. Datovou file cache modulu je jistější mazat přímo přes $this->cache->delete(...) nebo ručně ve složce system/storage/cache/.

Nejčastější chyby

  • Příliš obecný cache klíč: modul potom vrací nesprávný obsah pro jiný jazyk, jiný obchod nebo jinou stránku.
  • Příliš detailní cache klíč: cache se zbytečně tříští a přínos se snižuje.
  • Chybějící invalidace: modul je rychlý, ale po úpravách v administraci zobrazuje stará data.
  • Cachování per-customer výstupu: může dojít k zobrazení nesprávných dat jinému zákazníkovi.
  • Cachování příliš brzy: pokud do cache uložíte nedokončená data nebo výstup závislý na další logice, výsledek může být nekonzistentní.

Doporučený obecný vzor

public function index($setting) {
    $this->load->language('extension/module/[nazov_modulu]');

    $customer_group_id = $this->customer->isLogged()
        ? (int)$this->customer->getGroupId()
        : (int)$this->config->get('config_customer_group_id');

    $cache_key = 'module_[nazov_modulu].' . md5(json_encode(array(
        'setting'           => $setting,
        'store_id'          => (int)$this->config->get('config_store_id'),
        'language_id'       => (int)$this->config->get('config_language_id'),
        'customer_group_id' => $customer_group_id
    )));

    $cache_data = $this->cache->get($cache_key);

    if ($cache_data !== false) {
        return $this->load->view('extension/module/[nazov_modulu]', $cache_data);
    }

    $data = array();

    /* === ZDE ZAČÍNÁ PŮVODNÍ NÁROČNÁ LOGIKA MODULU === */
    /* načítání modelů, SQL dotazy, cykly, skládání výstupních dat */
    /* === ZDE KONČÍ PŮVODNÍ NÁROČNÁ LOGIKA MODULU === */

    $this->cache->set($cache_key, $data);

    return $this->load->view('extension/module/[nazov_modulu]', $data);
}

Praktická poznámka k modulu s více instancemi

Pokud se stejný modul používá na více layoutech nebo ve více instancích, je velmi důležité, aby byl součástí klíče i obsah $setting nebo alespoň module_id. Jinak si jednotlivé instance mohou navzájem přepisovat cache.

Po správném doplnění file cache se databázově náročný výpočet provede jen při prvním načtení nebo po invalidaci. Ostatní návštěvníci potom dostanou rychle připravená data ze souboru v system/storage/cache/.