WordPress działa na hookach (akcje i filtry). Czasami zdarzają się nieoczekiwane rzeczy – treść znika, tytuły się zmieniają, style się psują, strona działa wolno. Podejrzewasz, że jakaś wtyczka ingeruje, ale która? W tym kompletnym przewodniku nauczysz się profesjonalnie debugować hooki WordPress, identyfikować konflikty i optymalizować wydajność swojej strony.
Czym są hooki WordPress i dlaczego warto je debugować?
System hooków WordPressa to fundament jego rozszerzalności. Hooki pozwalają wtyczkom i motywom modyfikować zachowanie WordPressa bez edytowania plików core. Każda ważna funkcja w WordPressie uruchamia hooki, tworząc punkty zaczepienia dla deweloperów.
Dwa typy hooków
Akcje (Actions) – Wykonują się w określonym momencie cyklu życia WordPressa. Nie zwracają wartości, ale mogą wywoływać funkcje, wysyłać maile, modyfikować bazę danych.
Najpopularniejsze akcje:
wp_head– Wykonuje się w sekcji<head>stronywp_footer– Wykonuje się przed zamknięciem tagu</body>init– Wykonuje się podczas inicjalizacji WordPressatemplate_redirect– Wykonuje się przed wczytaniem szablonusave_post– Wykonuje się przy zapisywaniu wpisu
Filtry (Filters) – Modyfikują dane przed ich użyciem. Przyjmują wartość, modyfikują ją i zwracają zmienioną wersję.
Najpopularniejsze filtry:
the_content– Modyfikuje treść wpisu przed wyświetleniemthe_title– Modyfikuje tytuł wpisuwp_enqueue_scripts– Pozwala dodawać skrypty i styleexcerpt_length– Kontroluje długość wyciąguupload_mimes– Modyfikuje dozwolone typy plików
Kiedy debugowanie hooków jest niezbędne?
Debugowanie hooków staje się konieczne w wielu scenariuszach:
- Konflikty wtyczek – Dwie wtyczki modyfikują tę samą treść, powodując błędy
- Problemy z wydajnością – Strona ładuje się wolno z powodu zbyt wielu callbacków
- Nieoczekiwane zmiany – Treść lub wygląd zmieniają się bez wyraźnego powodu
- Błędy w motywach – Motyw nadpisuje funkcjonalność wtyczki
- Debugowanie produkcji – Musisz znaleźć źródło problemu na działającej stronie
Jak działa system hooków WordPress?
Zanim przejdziemy do debugowania, zrozummy mechanizm działania hooków. WordPress przechowuje wszystkie zarejestrowane hooki w globalnej zmiennej $wp_filter, która jest instancją klasy WP_Hook.
Struktura $wp_filter
// Przykładowa struktura $wp_filter
global $wp_filter;
$wp_filter['the_content'] = WP_Hook Object
(
[callbacks] => Array
(
[10] => Array // Priorytet 10 (domyślny)
(
[0] => Array
(
[function] => 'wppoland_modify_content'
[accepted_args] => 1
)
[1] => Array
(
[function] => Array
(
[0] => SomeClass Object
[1] => 'method_name'
)
[accepted_args] => 1
)
)
[5] => Array // Priorytet 5 (wykonuje się wcześniej)
(
// Funkcje z priorytetem 5
)
)
)
Priorytety hooków
Priorytety określają kolejność wykonywania callbacków:
- Niższy numer = wcześniejsze wykonanie
- Wyższy numer = późniejsze wykonanie
- Ten sam numer = kolejność rejestracji
// Ten callback wykona się pierwszy (priorytet 5)
add_filter('the_content', 'first_function', 5);
// Ten callback wykona się drugi (priorytet 10 - domyślny)
add_filter('the_content', 'second_function', 10);
// Ten callback wykona się trzeci (priorytet 99)
add_filter('the_content', 'third_function', 99);
Typy funkcji callback
WordPress obsługuje różne typy callbacków:
Funkcje nazwane:
add_filter('the_content', 'my_custom_function');
Metody statyczne klas:
add_filter('the_content', array('MyClass', 'static_method'));
// lub nowsza składnia:
add_filter('the_content', ['MyClass', 'static_method']);
Metody obiektów:
$obj = new MyClass();
add_filter('the_content', array($obj, 'method'));
Closures (funkcje anonimowe):
add_filter('the_content', function($content) {
return $content . '<p>Dodatkowa treść</p>';
});
Problemy z closures: Funkcje anonimowe są trudniejsze do debugowania, ponieważ nie mają nazwy. Wymagają specjalnego podejścia przy użyciu Reflection API.
Kompletny snippet do debugowania hooków
Oto zaawansowany snippet, który pozwala szczegółowo analizować hooki:
Podstawowa wersja debugowania
/**
* Podstawowa inspekcja hooków WordPress
*
* @param string $hook_name Nazwa hooka do sprawdzenia
* @return void
*/
function wppoland_inspect_hook_basic( $hook_name ) {
global $wp_filter;
if ( ! isset( $wp_filter[ $hook_name ] ) ) {
echo '<div style="background:#fff3cd; border:2px solid #ffc107; padding:15px; margin:20px 0; border-radius:4px;">';
echo "<strong>ℹ️ Hook '$hook_name' nie ma podpiętych funkcji.</strong>";
echo '</div>';
return;
}
$hooks = $wp_filter[ $hook_name ];
echo '<div style="background:#f8f9fa; border:2px solid #dee2e6; padding:20px; margin:20px 0; font-family:monospace; border-radius:4px;">';
echo "<h3 style='margin-top:0; color:#333;'>🔍 Hook: <code style='background:#e9ecef; padding:2px 6px; border-radius:3px;'>$hook_name</code></h3>";
echo "<p style='color:#666; font-size:14px;'>Liczba callbacków: " . count( $hooks->callbacks ) . "</p>";
foreach ( $hooks->callbacks as $priority => $callbacks ) {
echo "<h4 style='color:#495057; margin-top:20px; border-bottom:1px solid #dee2e6; padding-bottom:5px;'>Priorytet: $priority</h4>";
echo '<ul style="list-style:none; padding-left:0;">';
foreach ( $callbacks as $callback_id => $callback ) {
$function_name = wppoland_get_callback_name( $callback['function'] );
echo "<li style='margin:8px 0; padding:8px; background:#fff; border-left:3px solid #007bff;'>";
echo "<code style='color:#d63384;'>$function_name</code>";
echo "<span style='color:#6c757d; font-size:12px; margin-left:10px;'>(args: {$callback['accepted_args']})</span>";
echo "</li>";
}
echo '</ul>';
}
echo '</div>';
}
/**
* Pobiera czytelną nazwę callbacku
*
* @param mixed $callback Funkcja callback
* @return string Nazwa funkcji
*/
function wppoland_get_callback_name( $callback ) {
if ( is_string( $callback ) ) {
return $callback;
} elseif ( is_array( $callback ) ) {
if ( is_object( $callback[0] ) ) {
return get_class( $callback[0] ) . '->' . $callback[1];
} else {
return $callback[0] . '::' . $callback[1];
}
} elseif ( $callback instanceof Closure ) {
$reflection = new ReflectionFunction( $callback );
return 'Closure @ ' . $reflection->getFileName() . ':' . $reflection->getStartLine();
}
return 'Unknown';
}
Zaawansowana wersja z Reflection API
/**
* Zaawansowana inspekcja hooków z informacjami o źródle
*
* @param string $hook_name Nazwa hooka
* @param bool $show_source Pokaż informacje o pliku źródłowym
* @return void
*/
function wppoland_inspect_hook_advanced( $hook_name, $show_source = true ) {
// Tylko dla administratorów
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
global $wp_filter;
if ( ! isset( $wp_filter[ $hook_name ] ) ) {
echo "<p style='color:#856404; background:#fff3cd; padding:10px; border-radius:4px;'>";
echo "⚠️ Hook '$hook_name' nie ma podpiętych funkcji.";
echo "</p>";
return;
}
$hooks = $wp_filter[ $hook_name ];
$total_callbacks = 0;
// Policz wszystkie callbacki
foreach ( $hooks->callbacks as $callbacks ) {
$total_callbacks += count( $callbacks );
}
echo '<div style="background:#fff; border:1px solid #ddd; padding:20px; margin:20px 0; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; border-radius:8px; box-shadow:0 2px 4px rgba(0,0,0,0.1);">';
echo "<h3 style='margin-top:0; color:#23282d; border-bottom:2px solid #0073aa; padding-bottom:10px;'>";
echo "🔍 Analiza hooka: <code style='background:#f0f0f1; padding:4px 8px; border-radius:4px; color:#0073aa;'>$hook_name</code>";
echo "</h3>";
echo "<p style='color:#555;'><strong>Całkowita liczba callbacków:</strong> $total_callbacks</p>";
foreach ( $hooks->callbacks as $priority => $callbacks ) {
echo "<div style='margin-top:20px; background:#f6f7f7; padding:15px; border-radius:6px;'>";
echo "<h4 style='margin-top:0; color:#23282d;'>Priorytet: <span style='color:#0073aa;'>$priority</span></h4>";
foreach ( $callbacks as $callback_id => $callback ) {
echo "<div style='background:#fff; padding:12px; margin:10px 0; border-left:4px solid #0073aa; border-radius:4px; box-shadow:0 1px 2px rgba(0,0,0,0.05);'>";
// Nazwa funkcji
$function_info = wppoland_get_callback_info( $callback['function'] );
echo "<div style='font-weight:600; color:#23282d; margin-bottom:8px;'>";
echo "<code style='background:#f0f0f1; padding:2px 6px; border-radius:3px;'>{$function_info['name']}</code>";
echo "</div>";
// Informacje dodatkowe
echo "<div style='font-size:13px; color:#666; margin-left:10px;'>";
echo "<strong>Argumenty:</strong> {$callback['accepted_args']}<br>";
if ( $show_source && isset( $function_info['file'] ) ) {
echo "<strong>Plik:</strong> {$function_info['file']}<br>";
echo "<strong>Linia:</strong> {$function_info['line']}<br>";
}
if ( isset( $function_info['class'] ) ) {
echo "<strong>Klasa:</strong> {$function_info['class']}<br>";
}
echo "</div>";
echo "</div>";
}
echo "</div>";
}
echo '</div>';
}
/**
* Pobiera szczegółowe informacje o callbacku
*
* @param mixed $callback Funkcja callback
* @return array Informacje o funkcji
*/
function wppoland_get_callback_info( $callback ) {
$info = array(
'name' => 'Unknown',
'type' => 'unknown'
);
try {
if ( is_string( $callback ) ) {
$info['name'] = $callback;
$info['type'] = 'function';
if ( function_exists( $callback ) ) {
$reflection = new ReflectionFunction( $callback );
$info['file'] = str_replace( ABSPATH, '', $reflection->getFileName() );
$info['line'] = $reflection->getStartLine();
}
} elseif ( is_array( $callback ) ) {
if ( is_object( $callback[0] ) ) {
$class_name = get_class( $callback[0] );
$info['name'] = $class_name . '->' . $callback[1];
$info['type'] = 'method';
$info['class'] = $class_name;
$reflection = new ReflectionMethod( $callback[0], $callback[1] );
$info['file'] = str_replace( ABSPATH, '', $reflection->getFileName() );
$info['line'] = $reflection->getStartLine();
} else {
$info['name'] = $callback[0] . '::' . $callback[1];
$info['type'] = 'static';
$info['class'] = $callback[0];
$reflection = new ReflectionMethod( $callback[0], $callback[1] );
$info['file'] = str_replace( ABSPATH, '', $reflection->getFileName() );
$info['line'] = $reflection->getStartLine();
}
} elseif ( $callback instanceof Closure ) {
$reflection = new ReflectionFunction( $callback );
$info['name'] = 'Closure';
$info['type'] = 'closure';
$info['file'] = str_replace( ABSPATH, '', $reflection->getFileName() );
$info['line'] = $reflection->getStartLine();
}
} catch ( Exception $e ) {
$info['error'] = $e->getMessage();
}
return $info;
}
Praktyczne przykłady użycia
Przykład 1: Debugowanie konfliktu wtyczek
Załóżmy, że masz problem z formatowaniem treści wpisu. Dwie wtyczki modyfikują the_content i powodują błędy.
// Dodaj do functions.php tymczasowo
add_action('wp_footer', function() {
if ( current_user_can('manage_options') ) {
wppoland_inspect_hook_advanced('the_content');
}
});
Wynik pokaże wszystkie funkcje podpięte pod the_content wraz z ich priorytetami i źródłami. Możesz zidentyfikować, która wtyczka powoduje problem i zmienić jej priorytet lub wyłączyć.
Przykład 2: Optymalizacja wydajności
Jeśli strona ładuje się wolno, sprawdź hooki wp_head i wp_footer:
// Zlicz callbacki dla każdego hooka
function wppoland_count_callbacks() {
global $wp_filter;
$hooks_to_check = array('wp_head', 'wp_footer', 'wp_enqueue_scripts', 'template_redirect');
foreach ( $hooks_to_check as $hook ) {
if ( isset( $wp_filter[ $hook ] ) ) {
$count = 0;
foreach ( $wp_filter[ $hook ]->callbacks as $callbacks ) {
$count += count( $callbacks );
}
error_log( "Hook $hook ma $count callbacków" );
}
}
}
add_action('wp', 'wppoland_count_callbacks');
Przykład 3: Znajdowanie źródła niechcianego kodu
Jeśli na stronie pojawia się nieznany kod JavaScript:
// Sprawdź wszystkie funkcje podpięte pod wp_footer
add_action('wp_footer', function() {
if ( current_user_can('manage_options') ) {
echo "<h4>Debug: Funkcje w wp_footer</h4>";
wppoland_inspect_hook_advanced('wp_footer');
}
}, 999);
Narzędzia do debugowania hooków
Query Monitor
Query Monitor to najlepsza wtyczka do debugowania WordPress. Pokazuje:
- Wszystkie hooki i ich callbacki
- Czas wykonania każdego zapytania
- Błędy PHP i ostrzeżenia
- Żądania HTTP
Instalacja:
- Przejdź do Wtyczki → Dodaj nową
- Wyszukaj “Query Monitor”
- Zainstaluj i aktywuj
Użycie: Po aktywacji pojawi się nowy panel w pasku admina. Przejdź do zakładki “Hooks & Actions” aby zobaczyć wszystkie hooki.
Debug Bar
Debug Bar to klasyczne narzędzie do debugowania:
// Włącz debugowanie w wp-config.php
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
Własne narzędzie do logowania
/**
* Loguje wszystkie wywołania określonego hooka
*/
function wppoland_log_hook_calls( $hook_name ) {
add_action( $hook_name, function() use ( $hook_name ) {
$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 10 );
$caller = isset( $backtrace[2] ) ? $backtrace[2] : array();
$log_message = sprintf(
'[%s] Hook %s wywołany przez: %s%s w %s:%d',
current_time('mysql'),
$hook_name,
isset( $caller['class'] ) ? $caller['class'] . '->' : '',
isset( $caller['function'] ) ? $caller['function'] : 'unknown',
isset( $caller['file'] ) ? basename( $caller['file'] ) : 'unknown',
isset( $caller['line'] ) ? $caller['line'] : 0
);
error_log( $log_message );
}, 0 );
}
// Użycie:
wppoland_log_hook_calls( 'the_content' );
wppoland_log_hook_calls( 'wp_head' );
Typowe problemy i rozwiązania
Problem 1: Za dużo callbacków na jednym hooku
Objawy: Wolne ładowanie strony, timeouty
Rozwiązanie:
// Usuń niepotrzebne callbacki
function wppoland_remove_unnecessary_callbacks() {
// Usuń emoji scripts
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
// Usuń embed scripts jeśli nie używasz
remove_action('wp_head', 'wp_oembed_add_discovery_links');
remove_action('wp_head', 'wp_oembed_add_host_js');
}
add_action('init', 'wppoland_remove_unnecessary_callbacks', 999);
Problem 2: Konflikt priorytetów
Objawy: Funkcje wykonują się w złej kolejności
Rozwiązanie:
// Zmień priorytet istniejącego callbacka
function wppoland_reorder_callbacks() {
// Usuń i dodaj ponownie z innym priorytetem
remove_filter('the_content', 'problematic_function', 10);
add_filter('the_content', 'problematic_function', 5); // Wyższy priorytet
}
add_action('after_setup_theme', 'wppoland_reorder_callbacks');
Problem 3: Nieznane źródło modyfikacji
Objawy: Treść zmienia się, ale nie wiesz skąd
Rozwiązanie:
// Dodaj tymczasowy filtr do śledzenia zmian
add_filter('the_content', function($content) {
if ( current_user_can('manage_options') ) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
error_log('the_content modified by: ' . print_r($backtrace[2], true));
}
return $content;
}, 999);
Zaawansowane techniki debugowania
Profilowanie wydajności hooków
/**
* Mierzy czas wykonania callbacków dla danego hooka
*/
class WPPoland_Hook_Profiler {
private $timings = array();
public function start_profiling( $hook_name ) {
add_action( $hook_name, array( $this, 'profile_start' ), PHP_INT_MIN );
add_action( $hook_name, array( $this, 'profile_end' ), PHP_INT_MAX );
}
public function profile_start() {
$this->timings['start'] = microtime( true );
}
public function profile_end() {
$this->timings['end'] = microtime( true );
$duration = ( $this->timings['end'] - $this->timings['start'] ) * 1000;
error_log( sprintf( 'Hook execution time: %.2f ms', $duration ) );
}
}
// Użycie:
$profiler = new WPPoland_Hook_Profiler();
$profiler->start_profiling( 'wp_head' );
Śledzenie zmian w filtrach
/**
* Śledzi wszystkie modyfikacje wartości przez filtry
*/
function wppoland_trace_filter_changes( $tag, $value ) {
$original_value = $value;
add_filter( $tag, function( $value ) use ( $tag, $original_value ) {
static $call_count = 0;
$call_count++;
if ( $value !== $original_value ) {
$backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 4 );
$caller = isset( $backtrace[3] ) ? $backtrace[3] : array();
error_log( sprintf(
'Filter %s modified value (call #%d) by %s%s',
$tag,
$call_count,
isset( $caller['class'] ) ? $caller['class'] . '->' : '',
isset( $caller['function'] ) ? $caller['function'] : 'unknown'
) );
}
return $value;
}, PHP_INT_MAX );
}
// Użycie:
wppoland_trace_filter_changes( 'the_content', '' );
Najlepsze praktyki i bezpieczeństwo
Zasady bezpiecznego debugowania
-
Nigdy nie pokazuj informacji debugowania gościom – Zawsze używaj
current_user_can('manage_options') -
Używaj logów zamiast wyświetlania – W produkcji loguj do pliku zamiast wyświetlać na stronie:
if ( ! defined('WP_DEBUG') || ! WP_DEBUG ) {
error_log( $debug_info ); // Loguj zamiast wyświetlać
}
-
Usuwaj kod debugowania przed wdrożeniem – Upewnij się, że nie zostawiasz kodu debugowania na produkcji
-
Używaj wtyczek do debugowania – Query Monitor jest bezpieczniejszy niż własny kod
Checklist przed wdrożeniem
- Usunięto wszystkie wywołania
var_dump()iprint_r() - Kod debugowania jest owinięty w sprawdzanie uprawnień
- Logi debugowania są wyłączone w produkcji
- Przetestowano na kopii stagingowej
- Sprawdzono wpływ na wydajność
FAQ – Najczęściej zadawane pytania
Jak sprawdzić czy funkcja jest już podpięta pod hook?
if ( has_filter('the_content', 'my_function') ) {
echo 'Funkcja jest już podpięta';
}
// Lub sprawdź czy hook ma jakiekolwiek callbacki:
if ( has_action('wp_head') ) {
echo 'Hook wp_head ma podpięte funkcje';
}
Jak usunąć wszystkie callbacki z hooka?
// Usuń wszystkie filtry
remove_all_filters('the_content');
// Usuń wszystkie akcje
remove_all_actions('wp_head');
// Uwaga: Używaj ostrożnie, może to zepsuć funkcjonalność!
Jak zmienić priorytet istniejącego callbacka?
Nie można bezpośrednio zmienić priorytetu. Musisz usunąć i dodać ponownie:
function wppoland_change_priority() {
remove_filter('the_content', 'target_function', 10);
add_filter('the_content', 'target_function', 5); // Nowy priorytet
}
add_action('after_setup_theme', 'wppoland_change_priority');
Czy hooki działają w określonej kolejności?
Tak, hooki wykonują się według priorytetów (niższy numer = wcześniej). Przy tym samym priorytecie – w kolejności rejestracji.
Jak debugować hooki w REST API?
add_filter('rest_pre_dispatch', function($result, $server, $request) {
if ( current_user_can('manage_options') ) {
error_log('REST API request: ' . $request->get_route());
}
return $result;
}, 10, 3);
Jak znaleźć wszystkie dostępne hooki w WordPress?
Najlepszym źródłem jest oficjalna dokumentacja: Plugin API/Action Reference i Plugin API/Filter Reference
Czy hooki wpływają na wydajność?
Tak, każdy callback dodaje czas wykonania. Duża liczba callbacków na popularnych hookach (jak the_content czy wp_head) może spowolnić stronę. Zaleca się:
- Minimalizowanie liczby callbacków
- Używanie efektywnego kodu w callbackach
- Buforowanie wyników jeśli to możliwe
Jak debugować hooki w motywach potomnych?
// W functions.php motywu potomnego
add_action('after_setup_theme', function() {
if ( current_user_can('manage_options') ) {
wppoland_inspect_hook_advanced('wp_head');
}
}, 999);
Podsumowanie
Debugowanie hooków WordPress to kluczowa umiejętność dla każdego dewelopera. Dzięki zrozumieniu systemu hooków, użyciu odpowiednich narzędzi i stosowaniu najlepszych praktyk, możesz szybko identyfikować i rozwiązywać problemy na swoich stronach WordPress.
Kluczowe wnioski:
- Używaj globalnej zmiennej
$wp_filterdo inspekcji hooków - Reflection API pozwala znaleźć dokładne źródło funkcji
- Query Monitor to najlepsze narzędzie do wizualnego debugowania
- Zawsze zabezpieczaj kod debugowania sprawdzaniem uprawnień
- Testuj zmiany na kopii stagingowej przed wdrożeniem na produkcję
Mając te narzędzia i wiedzę, jesteś gotowy profesjonalnie debugować każdy problem związany z hookami WordPress.

