Skip to content
Menu

Anatómia WordPress hooku a prečo väčšina pluginov zabíja výkon

Autor: Lukáš Kameniczki
⏱️ Čas čítania: 11 min (2,111 slov)

WordPress dnes poháňa viac ako 40 % všetkých webových stránok na svete. Za týmto masívnym úspechom stojí predovšetkým jeho bezkonkurenčná modularita. Jadrom tejto modularity je systém hookov – mechanizmus, ktorý umožňuje vývojárom modifikovať alebo rozširovať správanie jadra (core), tém a pluginov bez toho, aby museli zasahovať do ich pôvodného zdrojového kódu.

Hoci väčšina WordPress vývojárov denne používa funkcie ako add_action() a add_filter(), len málokto skutočne rozumie tomu, čo sa deje pod kapotou na úrovni PHP engine. WordPress hooky nie sú magické konštrukty; sú postavené na relatívne jednoduchých, no potenciálne nebezpečných štruktúrach globálnych polí a dynamických volaní funkcií.

Tento článok podrobne analyzuje internú architektúru WordPress hookov, porovnáva rozdiely medzi akciami a filtrami z hľadiska dátových štruktúr, prechádza vykonávacím cyklom kódu a odhaľuje, prečo neefektívne napísané pluginy dokážu dramaticky degradovať výkon celej aplikácie.

 

1. Architektúra WordPress Hookov: Čo sa deje v pamäti?

Pre pochopenie fungovania hookov musíme preniknúť hlboko do jadra WordPressu, konkrétne do súboru wp-includes/plugin.php a triedy WP_Hook, ktorá bola predstavená vo verzii WordPress 4.7 (vydanej v decembri 2016).

Pred verziou 4.7 bol systém hookov spravovaný prostredníctvom masívneho multidimenzionálneho globálneho poľa $wp_filter. Tento prístup bol náchylný na chyby, neposkytoval objektovo orientované rozhranie a sťažoval ladenie. Od verzie 4.7 je každé registrované meno hooku (napr. wp_head alebo the_content) inštanciou triedy WP_Hook.

Globálna premenná $wp_filter však nezmizla. Dnes funguje ako asociatívne pole, kde kľúčom je názov hooku a hodnotou je inštancia triedy WP_Hook.

 

// Zjednodušená vizualizácia globálneho poľa v pamäti PHP:
$GLOBALS[‚wp_filter‘] = [
‚wp_head‘ => instance of WP_Hook,
‚the_content‘ => instance of WP_Hook,
// …
];

 

Anatomická štruktúra triedy WP_Hook

Trieda WP_Hook implementuje zabudované PHP rozhrania ArrayAccess, Iterator a Countable. To zabezpečuje spätnú kompatibilitu, takže starší kód, ktorý pristupoval k $wp_filter priamo ako k poľu, stále funguje bez vyvolania fatálnych chýb.

Vnútorná štruktúra triedy WP_Hook obsahuje niekoľko kľúčových vlastností:

  • $callbacks: Multidimenzionálne pole, ktoré ukladá registrované funkcie (callbacks). Je štruktúrované podľa priority.
  • $priorities: Indexované pole obsahujúce zoznam všetkých registrovaných priorít v danom hooku (zoradené vzostupne).
  • $nesting_level: Celé číslo (integer) sledujúce, koľkokrát je tento konkrétny hook práve spustený v rekurzívnom volaní. Prevencia pred nekonečnými slučkami.
  • $iterations: Pole iterátorov pre bezpečné prechádzanie callbackov, aj keď sa počas vykonávania hooku pridávajú alebo odstraňujú iné callbacky.
  • $current_priority: Pole sledujúce aktuálne spracovávanú prioritu v každej úrovni vnorenia.

 

Keď zaregistrujete callback pomocou add_filter( ‚the_content‘, ‚moja_funkcia‘, 10, 2 ), WordPress vykoná nasledovné kroky:

Vyhľadá v $GLOBALS[‚wp_filter‘][‚the_content‘]. Ak neexistuje, vytvorí novú inštanciu triedy WP_Hook.

Zavolá metódu WP_Hook::add_filter().

Vytvorí unikátny identifikátor pre callback (pomocou funkcie _wp_filter_build_unique_id()). Tento identifikátor rieši anonymné funkcie, metódy objektov aj statické metódy tried.

Uloží callback do poľa $callbacks pod špecifikovanú prioritu (v tomto prípade 10):

$this->callbacks[10][‚moja_funkcia‘] = [
‚accepted_args‘ => 2,
‚function‘ => ‚moja_funkcia‘
];

 

2. Actions vs. Filters: Mýtus o rozdielnosti

Medzi začínajúcimi, ale aj mierne pokročilými vývojármi koluje mýtus, že akcie (actions) a filtre (filters) sú dva odlišné systémy. Z hľadiska používateľského API to tak vyzerá: akcie používame na vykonanie činnosti v určitom bode, zatiaľ čo filtre používame na modifikáciu dát a očakávame návratovú hodnotu.

Pod kapotou WordPressu sú však akcie a filtre identické. Akcie sú postavené priamo na filtroch. Ak sa pozrieme na definíciu funkcie add_action() v wp-includes/plugin.php, uvidíme presne toto:

function add_action( $hook_name, $callback_to_add, $priority = 10, $accepted_args = 1 ) {
return add_filter( $hook_name, $callback_to_add, $priority, $accepted_args );
}

add_action() je iba obal (wrapper) pre add_filter(). Jediný skutočný rozdiel nastáva vo fáze ich spúšťania (vykonávania).

 

  • Vykonávanie filtrov: apply_filters()
    Keď voláme apply_filters( ‚tag‘, $value, …$args ), prvá odovzdaná hodnota ($value) je tá, ktorú chceme filtrovať. Každý registrovaný callback musí túto hodnotu prijať, spracovať ju a vrátiť ju späť (return). Ak nejaký callback v reťazci zabudne hodnotu vrátiť (alebo nevráti nič), nasledujúci callback dostane null, čo často vedie k neočakávaným chybám.
  • Vykonávanie akcií: do_action()
    Keď voláme do_action( ‚tag‘, …$args ), WordPress interne zavolá apply_filters() s jedným zásadným rozdielom: návratová hodnota sa ignoruje. Akcie slúžia ako „udalosti“ (events). Ich cieľom nie je transformovať dáta, ale spustiť procesy – napríklad poslať e-mail, zapísať záznam do databázy alebo vygenerovať HTML kód.

function do_action( $hook_name, …$arg ) {
// … inicializácia a kontrola prítomnosti hooku …

$wp_filter[ $hook_name ]->do_action( $arg );
}

Metóda WP_Hook::do_action() prechádza registrované callbacky rovnako ako pri filtroch, no neočakáva od nich návratovú hodnotu pomocou return.

 

3. Životný cyklus hooku: Od registrácie po vykonanie

Aby sme pochopili vplyv na výkon, musíme presne zmapovať, čo robí PHP interpreter, keď spracováva hooky počas načítania stránky (request lifecycle).

Krok 1: Registrácia (Parsing a bootstrap)

Pri načítaní WordPressu (od inicializácie jadra cez načítanie pluginov až po tému) PHP postupne prechádza súbory a naráža na príkazy add_action() a add_filter().
V tejto fáze sa nevykonáva žiadny kód vo vnútri callbackov. Iba sa buduje obrovská mapa v pamäti ($wp_filter). Každá registrácia alokuje pamäť pre inštanciu triedy WP_Hook a jej vnútorné polia.

Krok 2: Spustenie hooku (Triggering)

Keď tok programu dosiahne bod, kde je umiestnený napríklad hook wp_head, zavolá sa do_action(‚wp_head‘). V tomto okamihu začína proces, ktorý trieda WP_Hook riadi vo svojej metóde apply_filters:

  • Triedenie priorít: Ak boli pridané nové callbacky od posledného spustenia, prioritné pole $priorities sa usporiada vzostupne pomocou internej funkcie PHP (od najnižšej priority, napr. -100, cez predvolenú 10, až po vysokú, napr. 999).
  • Iterácia callbackov: PHP začne prechádzať pole $callbacks podľa zoradených priorít.
  • Dynamické volanie: Na vykonanie samotného callbacku WordPress používa dynamické PHP volanie. V závislosti od počtu parametrov využíva buď priame priradenie s variadickými parametrami, alebo funkciu call_user_func_array().

// Zjednodušený pohľad na vykonanie callbacku v triede WP_Hook
foreach ( $this->priorities as $priority ) {
foreach ( $this->callbacks[ $priority ] as $the_ ) {
// … overenie parametrov …
$args = array_slice( $args, 0, $the_[‚accepted_args‘] );

// Dynamické vykonanie funkcie
$value = call_user_func_array( $the_[‚function‘], $args );
}
}

 

4. Prečo väčšina pluginov zabíja výkon (Slabé miesta architektúry)

Systém hookov je geniálny, ale prináša so sebou obrovskú daň za dynamickosť. Väčšina vývojárov pluginov píše kód s dôrazom na rýchlosť vývoja, nie na efektivitu exekúcie PHP. To vedie k trom hlavným typom výkonnostných anomálií:

4.1. Réžia dynamických volaní (call_user_func_array)

V PHP sú statické volania funkcií (MojaTrieda::metoda()) alebo priame volania (moja_funkcia()) mimoriadne rýchle, pretože PHP engine (Zend VM) ich dokáže optimalizovať a v mnohých prípadoch predkompilovať pomocou OPcache.

Ak však voláme funkcie dynamicky cez call_user_func_array(), PHP musí:

  • Vyhľadať názov funkcie alebo objektu v tabuľke symbolov počas behu (runtime).
  • Overiť, či je daná štruktúra „callable“ (či sa dá zavolať).
  • Dynamicky pripraviť zásobník (stack) pre argumenty.

Táto dynamická réžia sa pri jednom či dvoch volaniach neprejaví. Na bežnom webe s 30 nainštalovanými pluginmi sa však počas jedného načítania stránky spustia tisíce hookov. Ak sa v každom z nich používa dynamické volanie, celková exekučná doba PHP (TTFB – Time to First Byte) merateľne narastá.

4.2. „Užitočné zaťaženie“ (Payload) a neefektívne načítanie kódu

Najväčším hriechom vývojárov pluginov je registrovanie callbackov na hooky, ktoré sa spúšťajú pri každom načítaní stránky, hoci ich kód je potrebný len na špecifických miestach (napr. iba v administrácii alebo len na podstránke s košíkom).

Predstavte si plugin pre kontaktný formulár, ktorý v hlavnom súbore urobí toto:

add_action( ‚wp_enqueue_scripts‘, ‚enqueue_form_assets‘ );
add_filter( ‚the_content‘, ‚parse_form_shortcode‘ );

Aj keď sa na 95 % podstránok webu žiadny formulár nenachádza:

Súbor pluginu sa musí načítať a analyzovať (parsovať) pri každom requeste.

Hook the_content bude pri každom príspevku spúšťať funkciu parse_form_shortcode, ktorá bude pomocou regulárnych výrazov (preg_match) zbytočne prehľadávať text a hľadať shortcode. Regulárne výrazy sú na procesor (CPU) extrémne náročné.

4.3. Databázové dopyty vo vnútri cyklických filtrov

Naozajstný „zabijak výkonu“ nastáva, keď vývojári umiestnia databázový dopyt (SQL query) do filtra, ktorý sa spúšťa v rámci WordPress loopu (cyklu príspevkov).

Typickým príkladom je filter the_title alebo post_class. Ak sa na domovskej stránke zobrazuje 10 príspevkov a v každom z nich filter vykoná SQL dopyt (napr. na získanie nejakej dodatočnej informácie z tabuľky wp_postmeta pomocou get_post_meta()), namiesto jedného optimalizovaného dopytu vygeneruje web ďalších 10 dopytov do databázy. Tento jav je známy ako N+1 dopytový problém.

 

5. Prípadová štúdia: Diagnostika a meranie zneužívania hookov

Pozrime sa na reálny scenár. Máme WordPress web, ktorého načítanie trvá príliš dlho. Chceme presne lokalizovať, ktoré pluginy zneužívajú systém hookov.

Na diagnostiku môžeme použiť profilovacie nástroje ako Xdebug v kombinácii s Webgrind alebo moderné APM (Application Performance Monitoring) nástroje ako New Relic či tideways. Ak však chceme rýchly prehľad priamo v kóde bez inštalácie ťažkých nástrojov, môžeme dočasne monitorovať globálne pole $wp_filter.

Nasledujúci skript (ktorý môžeme umiestniť do mu-plugins – Must-Use Plugins) zaznamenáva čas vykonávania jednotlivých hookov:

class Hook_Performance_Profiler {
private static $starts = [];
private static $log = [];

public static function start( $hook_name ) {
self::$starts[ $hook_name ] = microtime( true );
}

public static function stop( $hook_name ) {
if ( isset( self::$starts[ $hook_name ] ) ) {
$duration = microtime( true ) – self::$starts[ $hook_name ];
if ( ! isset( self::$log[ $hook_name ] ) ) {
self::$log[ $hook_name ] = [ ‚count‘ => 0, ‚time‘ => 0 ];
}
self::$log[ $hook_name ][‚count‘]++;
self::$log[ $hook_name ][‚time‘] += $duration;
}
}

public static function get_results() {
// Zoradenie podľa celkového času vykonávania
uasort( self::$log, function( $a, $b ) {
return $b[‚time‘] <=> $a[‚time‘];
});
return self::$log;
}
}

Pri profilovaní zistíme, že najproblematickejšie bývajú hooky, ktoré prechádzajú veľké množstvo dát, napríklad the_content, init alebo template_redirect. Ak v nich pluginy vykonávajú náročné operácie, čas načítania stránky rastie exponenciálne s veľkosťou obsahu.

 

6. Best Practices: Ako písať vysoko výkonný kód

Ak chcete písať pluginy a témy, ktoré nespôsobujú spomalenie webu, musíte dodržiavať prísne architektonické pravidlá.

 

6.1. On-Demand registrácia hookov (Lazy Loading)

Neregistrujte hooky globálne, ak to nie je nevyhnutné. Rozdeľte váš plugin na logické časti a načítavajte ich selektívne.

Administrácia vs. Front-end: Ak váš kód rieši nastavenia v admin rozhraní, registrujte ho výhradne v bloku is_admin().

Špecifické stránky: Ak potrebujete hook len na WooCommerce podstránkach košíka a pokladne, overte kontext predtým, ako zaregistrujete hooky, alebo ich registrujte v rámci hooku template_redirect, kedy už WordPress pozná dopytovanú stránku (conditional tags sú už dostupné).

 

// Zlý prístup – načíta sa všade
add_action( ‚wp_enqueue_scripts‘, ‚my_checkout_assets‘ );

 

// Správny prístup – registrácia len tam, kde je to potrebné
add_action( ‚template_redirect‘, ‚my_conditional_registration‘ );
function my_conditional_registration() {
if ( is_checkout() ) {
add_action( ‚wp_enqueue_scripts‘, ‚my_checkout_assets‘ );
}
}

 

6.2. Striktné uplatňovanie Statického cacheovania a Memoizácie

Ak vaša funkcia naviazaná na filter (napr. translate_text) vykonáva výpočtovo náročnú operáciu, uistite sa, že výsledok ukladáte do internej pamäte (in-memory cache) pre prípad, že bude rovnaká funkcia v rámci jedného requestu zavolaná viackrát s rovnakými parametrami.

function my_expensive_filter( $text ) {
static $cache = [];

if ( isset( $cache[ $text ] ) ) {
return $cache[ $text ];
}

// Simulácia náročnej operácie (napr. parsovanie markdownu)
$processed_text = expensive_processing_function( $text );

$cache[ $text ] = $processed_text;
return $processed_text;
}
add_filter( ‚the_title‘, ‚my_expensive_filter‘ );

 

6.3. Odstraňovanie nepotrebných callbackov (remove_action / remove_filter)

Ako vývojári optimalizujúci existujúci web môžete drasticky zvýšiť výkon odregistrovaním neefektívnych callbackov z iných pluginov alebo dokonca z jadra WordPressu, ak ich funkcie na webe nevyužívate.

Ak napríklad nepoužívate emoji na vašom webe, WordPress predvolene načítava dodatočné skripty cez wp_head. Môžete ich jednoducho odstrániť:

 

add_action( ‚init‘, ‚disable_emojis‘ );
function disable_emojis() {
remove_action( ‚wp_head‘, ‚print_emoji_detection_script‘, 7 );
remove_action( ‚wp_print_styles‘, ‚print_emoji_styles‘ );
remove_filter( ‚the_content_feed‘, ‚wp_staticize_emoji‘ );
// … a ďalšie súvisiace remove_filter …
}

Pri odstraňovaní callbackov, ktoré sú definované vo vnútri tried (objektov), však narazíte na problém s prístupom k inštancii danej triedy. V takom prípade musíte získať prístup ku globálnej inštancii pluginu alebo použiť reflexiu na prechádzanie $wp_filter a nájdenie príslušného objektu.

 

WordPress hooky sú základným stavebným kameňom celého ekosystému. Ich elegancia spočíva v jednoduchosti používania, no ich skutočná sila (a zároveň slabosť) je ukrytá v dynamickej povahe PHP interpretácie. Trieda WP_Hook odviedla kus skvelej práce pri modernizácii tohto systému, no nedokáže zabrániť neefektívnemu návrhu kódu zo strany vývojárov tretích strán.

Zodpovedný vývojár nesmie vnímať hooky ako bezplatný nástroj na vkladanie kódu kamkoľvek. Každé volanie add_action() a add_filter() so sebou prináša pamäťovú réžiu, a každé spustenie cez do_action() či apply_filters() spotrebováva CPU cykly na dynamické vyhľadávanie a spúšťanie callbackov. Iba minimalizovaním globálnych hookov, selektívnym načítavaním kódu a dôsledným cachovaním dopytov môžeme vyvíjať pluginy a weby, ktoré sú nielen modulárne, ale aj bleskovo rýchle.

 

Pre hlbšie štúdium problematiky odporúčam nasledujúce autoritatívne zdroje a dokumentáciu:

WordPress Core Developer Resources: The Plugin API and WP_Hook Class Reference  – Oficiálna dokumentácia k architektúre triedy WP_Hook a API pre vývojárov.

WordPress Core Trac: Ticket #38707: Refactor Action/Filter hook system using a WP_Hook class. – Kompletná diskusia jadrových vývojárov WordPressu o prepise systému hookov do objektovej štruktúry vo verzii 4.7.

PHP Manual: Overloading and Dynamic Function Calls (call_user_func_array) – Detailný pohľad na fungovanie, parametre a výkonnostnú réžiu dynamického volania funkcií v PHP engine.

Zend Engine Internals: OPcache Optimization and Class/Function Lookup Tables. phpinternals.net – Odborný rozbor toho, ako PHP ukladá definície funkcií do pamäte a prečo dynamické štruktúry obchádzajú niektoré fázy kompilácie a optimalizácie.

 

Lukáš Kameniczki
Senior Web Developer — Bc. v odbore Aplikovaná informatika, Senior Developer, 8 rokov praxe

Štúdium v odbore Aplikovaná informatika na Ústave počítačových technológií a informatiky, Univerzita sv. Cyrila a Metoda v Trnave.

📚 Web Development, UI/UX Design, Wordpress, Laravel, React, Node.js, Python

Podobné články

Sme online

w.

Prestaňte web stránku len vlastniť. Začnite na web stránke zarábať!

Pripravíme vám stratégiu, ktorá z vás urobí lídra na trhu!

Google
Bing
Yandex
Seznam
Naver
Yep
Hub
Websub