Jak tworzyć cron we własnych pluginach

dowiedz się

Automatyzacja cyklicznych zadań w wtyczkach to niezbędny element do wysyłki powiadomień, czyszczenia starych danych, synchronizacji z API czy generowania raportów. Poniższy przewodnik pokazuje, jak zaprojektować i wdrożyć cron w swojej wtyczka, krok po kroku: od wyboru mechanizmu (WP‑Cron vs systemowy), przez rejestrację harmonogramu i obsługę hooków, po testy, logowanie, bezpieczne blokady i praktyki produkcyjne poprawiające wydajność oraz stabilność. Znajdziesz tu konkretne receptury, typowe pułapki i sposoby ich omijania.

Podstawy i kontekst uruchamiania zadań cyklicznych

WP‑Cron a systemowy cron: różnice i kiedy który wybrać

WordPress dostarcza mechanizm WP‑Cron, który uruchamia zadania przy okazji wejścia użytkownika na stronę. Nie działa w tle stale – „budzi się” przy ruchu. To oznacza, że w godzinach bez odwiedzin harmonogram może się opóźnić, a przy dużym ruchu – odpalić wiele równoległych instancji. Z kolei systemowy cron (Linux/macOS/serwer) odpala zadania o dokładnych porach, niezależnie od ruchu WWW, jednak wymaga dostępu do środowiska serwera (panel, SSH) i osobnej konfiguracji.

Wybór strategii:

  • WP‑Cron: szybki start, brak dostępu do serwera nie jest problemem, dobra opcja na małe lub średnie instalacje.
  • Systemowy cron: wymagany dla twardych SLA czasowych, przetwarzania dużych kolejek, zadań wymagających przewidywalności i odporności na brak ruchu.

Cykl życia zadania i powiązane haki

Typowy przepływ w WP‑Cron wygląda tak:

  • Podczas aktywacji wtyczki rejestrujesz zdarzenie czasowe i interwał (np. co 5 minut).
  • Hook „cronowy” wskazuje na funkcję/callback, która wykona faktyczną robotę.
  • WordPress przy wejściu użytkownika sprawdza kolejkę zdarzeń i uruchamia zaległe.
  • Przy dezaktywacji wtyczki sprzątasz po sobie, anulując zaplanowane zdarzenia.

Dokładność i odporność: o czym pamiętać

WP‑Cron nie gwarantuje sekundowej dokładności; wydarzenia będą uruchamiane „tak szybko, jak to możliwe”, o ile jest ruch. Z tego powodu logika zadania powinna być:

  • Odporna na wielokrotne uruchomienie (tzw. idempotencja).
  • Chroniona przed równoległym wykonaniem (np. poprzez lekką blokada w bazie lub transient).
  • Projektowana w partiach (batching), aby unikać timeoutów i przekroczeń pamięci.

Szkielet własnej wtyczki i rejestracja harmonogramu

Minimalna struktura plików

Proponowany układ:

  • /wp-content/plugins/my-cron-plugin/my-cron-plugin.php – plik główny z nagłówkiem wtyczki, rejestracją hooków, aktywacją/dezaktywacją.
  • /wp-content/plugins/my-cron-plugin/includes/Tasks.php – logika zadań.
  • /wp-content/plugins/my-cron-plugin/includes/Schedule.php – funkcje harmonogramu (rejestracja interwałów, ustawianie zdarzeń).
  • /wp-content/plugins/my-cron-plugin/includes/Lock.php – narzędzia do blokad i idempotencji.
  • /wp-content/plugins/my-cron-plugin/includes/Logger.php – prosty logger do debugowania.

Nagłówek wtyczki i podstawowe hooki

W pliku my-cron-plugin.php umieść nagłówek oraz rejestrację aktywacji/dezaktywacji:

  • register_activation_hook(__FILE__, 'my_cron_plugin_activate’); – podczas aktywacji ustaw harmonogram.
  • register_deactivation_hook(__FILE__, 'my_cron_plugin_deactivate’); – podczas dezaktywacji wyczyść zaplanowane zdarzenia.

Dalej, dołącz pliki includes oraz zarejestruj akcję cronową:

  • add_action(’my_cron_plugin_task’, 'my_cron_plugin_run_task’); – callback do faktycznego zadania.

Rejestracja interwałów i ustawienie zdarzenia

Aby dodać niestandardowy interwał (np. 5 minut), użyj filtra cron_schedules:

  • add_filter(’cron_schedules’, function ($schedules) { $schedules[’every_five_minutes’] = [’interval’ => 300, 'display’ => 'Co 5 minut’]; return $schedules; });

Następnie, w funkcji aktywacyjnej:

  • if (!wp_next_scheduled(’my_cron_plugin_task’)) { wp_schedule_event(time() + 60, 'every_five_minutes’, 'my_cron_plugin_task’); }

Powyższe ustawi pierwsze uruchomienie za minutę, a kolejne zgodnie z interwałem.

Callback zadania i ładowanie środowiska

Callback (np. my_cron_plugin_run_task) powinien:

  • Sprawdzić blokadę (czy nie trwa inna instancja).
  • Załadować zależności (np. klasy z includes/).
  • Uruchomić pracę w małych porcjach, logując postęp.
  • Uwolnić blokadę po zakończeniu, niezależnie od wyniku.

W tym miejscu warto zaimplementować retry i obróbkę wyjątków.

Sprzątanie przy dezaktywacji

W my_cron_plugin_deactivate wyrejestruj zdarzenia:

  • $timestamp = wp_next_scheduled(’my_cron_plugin_task’);
  • if ($timestamp) { wp_unschedule_event($timestamp, 'my_cron_plugin_task’); }
  • delete_transient(’my_cron_plugin_lock’); – jeśli stosujesz transienty do blokad.

Dzięki temu nie pozostawisz „sierocych” wpisów w harmonogramie.

Harmonogramy w praktyce: interwały, blokady, odporność

Własne interwały vs wbudowane

WordPress posiada wbudowane interwały (hourly, twicedaily, daily). Niestandardowe dodamy filtrem cron_schedules. Przy krótkich interwałach (np. co minutę) pamiętaj o kosztach – jeśli nie masz ruchu, WP‑Cron może się spóźniać; jeśli ruch jest duży, zadania mogą się kumulować. W takich przypadkach rozważ systemowy cron lub alternatywne mechanizmy kolejkowania (Action Scheduler).

Blokady anty‑równoległe

Aby uniknąć równoległego uruchomienia:

  • Ustaw transient: set_transient(’my_cron_plugin_lock’, 1, 15 * MINUTE_IN_SECONDS); jeśli już istnieje – wyjdź.
  • W kodzie zadania na start sprawdź get_transient(’my_cron_plugin_lock’); jeśli jest – zakończ bez pracy.
  • Po zakończeniu pracy delete_transient(’my_cron_plugin_lock’).

Jeżeli używasz obiektu cache zewnętrznego, weryfikuj zachowanie transientów; w MVP‑wariancie można użyć opcji w bazie z operacją add_option jako prymitywnego locka (z obsługą TTL).

Idempotencja i bezpieczne powtórki

Projektuj zadania tak, aby wielokrotne odpalenie nie szkodziło:

  • Przetwarzaj rekordy po kluczu rosnącym (np. ID) i zapisuj ostatni przetworzony punkt kontrolny (checkpoint) w opcji.
  • Używaj znaczników stanu (np. data_synchronized_at) zamiast „flagi przetworzone” bez czasu – pozwala to na bezpieczne rekalkulacje.
  • Sprawdzaj wersję danych i warunki wyścigu (select‑then‑update z walidacją).

To kluczowe, gdy WP‑Cron odpali się dwa razy z rzędu, bądź gdy nastąpi awaria w połowie zadania.

Przetwarzanie wsadowe i limity

Unikaj „one‑shot” nadmiernie ciężkich zadań. Lepszy jest batch np. 100 rekordów na cykl:

  • Pobierz 100 elementów spełniających kryteria.
  • Przetwórz je z krótkimi timeoutami I/O.
  • Zaktualizuj checkpoint i zapisz metryki (ile, czas trwania, błędy).

Jeśli kolejka jest długa, dopuszczalne jest odpalenie cron co 1–5 minut i systematyczne przegryzanie zadań.

Planowanie jednorazowe vs cykliczne

Masz dwie podstawowe metody:

  • Jednorazowe: wp_schedule_single_event(timestamp, 'my_cron_plugin_task_once’, [$payload]); dobre do retry/backoff lub akcji „za X minut”.
  • Cykliczne: wp_schedule_event(start, 'every_five_minutes’, 'my_cron_plugin_task’); do regularnych prac utrzymaniowych.

Pamiętaj, że anulowanie pojedynczego wydarzenia wymaga podania tego samego payloadu, który był zaplanowany.

Monitorowanie, debugowanie i narzędzia

Prosty logger i dzienniki zdarzeń

Dobry logger w wtyczce ułatwia życie:

  • Funkcja my_cron_plugin_log($level, $message, $context = []): zapis do wp-content/uploads/my-cron-plugin/log-YYYY-MM-DD.txt.
  • Loguj start/stop, liczbę elementów, czas trwania, identyfikator blokady, błędy (stack trace).
  • W testach lokalnych włącz WP_DEBUG_LOG i monitoruj debug.log.

Zadbaj, by logi rotowały (np. dziennie) i nie gromadziły wrażliwych danych PII bez potrzeby.

WP‑CLI do ręcznego uruchamiania i inspekcji

Narzędzie WP-CLI pozwala uruchamiać i debugować:

  • wp cron event list – lista zaplanowanych zdarzeń.
  • wp cron event run my_cron_plugin_task – wymusza uruchomienie akcji.
  • wp cron schedule list – lista interwałów.
  • wp option get my_cron_plugin_checkpoint – sprawdzenie postępu.

Możesz też dodać własną komendę: wp my-cron-plugin run-batch, która wywoła bezpośrednio logikę i pominie harmonogram.

Symulowanie słabego ruchu i błędów

Aby przetestować opóźnienia WP‑Cron:

  • Wyłącz wywołanie WP‑Cron przy każdym żądaniu (DISABLE_WP_CRON) i uruchamiaj go co minutę systemowym cronem – zobaczysz, jak radzi sobie Twój kod.
  • Wstrzykuj sztuczne błędy API i timeouty, sprawdzając, czy retry/backoff działają zgodnie z planem.
  • Sprawdź zachowanie blokady w sytuacji awaryjnego wyjścia (czy TTL zwolni lock, a kolejne uruchomienia ruszą).

Inspekcja kolejki i zaległości

Jeśli podejrzewasz zaległości:

  • Porównaj liczbę zaplanowanych zdarzeń z liczbą zadań w kolejce do przetworzenia.
  • W logach szukaj długich czasów trwania; rozbijaj batch na mniejsze porcje.
  • Sprawdź ograniczenia hostingu (CPU, I/O, jednoczesne procesy PHP, max_execution_time).

W razie potrzeby zwiększ interwał lub przenieś cięższe prace na systemowy cron lub dedykowaną kolejkę.

Produkcja: wydajność, bezpieczeństwo i wdrożenia

Most: systemowy cron wywołujący WP‑Cron

Najczęstszy wzorzec produkcyjny:

  • W wp-config.php ustaw define(’DISABLE_WP_CRON’, true); aby WP nie odpalał cron przy każdym wejściu.
  • W systemowym cronie (crontab) dodaj: */1 * * * * php /ścieżka/do/wp/wp-cron.php > /dev/null 2>&1 – lub curl https://twoja-domena.pl/wp-cron.php?doing_wp_cron=1.

Zyskujesz przewidywalność bez konieczności przepisywania logiki.

Limity czasu, pamięci i rozmiaru wsadu

Dostosuj wielkość batcha do limitów hostingu. W callbacku:

  • Użyj krótkich timeoutów HTTP (wp_remote_get/post z timeout 5–10 s).
  • Ustaw konserwatywne limity batcha (np. 50–200 rekordów) i mierz realny czas trwania.
  • W razie potrzeby dziel większe kroki na etapy z checkpointami.

Pamiętaj o memory_get_usage i prostych metrykach, które podpowiedzą, gdzie cięcia są potrzebne.

Bezpieczeństwo danych i uprawnień

Callback cronowy działa w kontekście WordPressa, więc:

  • Waliduj dane wejściowe (nawet jeśli pochodzą z wewnętrznych źródeł).
  • Szanuj prywatność – nie zapisuj danych wrażliwych do logów, maskuj tokeny.
  • Rotuj klucze API; przechowuj w wp_options z autoload = no lub w bezpiecznym magazynie.

Jeśli zadanie wykonuje operacje na plikach, sprawdź uprawnienia katalogów i obsługuj błędy dyskowe.

Wersjonowanie i migracje

Podczas aktualizacji wtyczki:

  • Używaj opcji my_cron_plugin_version i funkcji upgrade_routine: jeśli wersja się zmieniła, zaktualizuj interwały i hooki.
  • Zapewnij „migracje” danych (np. zmiana formatu checkpointu) jako osobne, idempotentne kroki.
  • W stagingu przetestuj harmonogram: czy zdarzenia nie dublują się po update?

Zachowaj kompatybilność wsteczną, jeśli Twoi użytkownicy mają różne wersje WordPressa.

Multisite i środowiska z replikacją

W multisite każdy blog ma własny harmonogram. Decyduj:

  • Czy instalować i planować zadania per‑site (switch_to_blog w pętli)?
  • Czy centralne zadanie ma iterować po wszystkich blogach? Zwróć uwagę na czas wykonania i limity.

Przy replikacji bazy (master‑slave) pamiętaj, że WP‑Cron powinien wykonywać się na instancji mającej prawo zapisu. Zadbaj, by blokady trafiały na mastera i nie rozminęły się w odczytach z replica.

Wzorce gotowe do użycia i najczęstsze scenariusze

Czyszczenie starych danych i metadanych

Powszechny przypadek to porządkowanie bazy:

  • Co noc: pobierz 500 najstarszych wpisów‑do‑usunięcia, usuń bezpiecznie, zarejestruj liczbę rekordów i czas.
  • Idempotencja: przechowuj ostatnie ID i datę, powtarzaj do skutku, dopóki kolejka nie będzie pusta.
  • Bezpieczeństwo: używaj wp_delete_post z force_delete i sprawdzaj uprawnienia (map_meta_cap).

Synchronizacja z API zewnętrznym

Co 5–15 minut odświeżaj katalog produktów lub stany magazynowe:

  • Paginate po 100–200 rekordów, zapisuj etag/last_modified, aby unikać pełnych pobrań.
  • Obsłuż kody błędów 429/5xx: backoff (wp_schedule_single_event z +5, +15, +30 min).
  • Loguj numer strony, liczbę zmian i ewentualne konflikty wersji.

Wysyłka powiadomień i e‑maili

Wysyłaj porcjami, aby nie wpaść na limity SMTP:

  • Batch 50–100 wiadomości na cykl, z kolejką (tabela lub custom post type „mail_job”).
  • Retry per‑wiadomość z ograniczoną liczbą prób, zapisem przyczyny porażki.
  • Ogranicz równoległość przez lock i sprawdzanie dostępnej puli.

Generowanie raportów i cache’owanie

Raz dziennie generuj raporty PDF/CSV i odświeżaj cache:

  • Korzystaj z tymczasowych plików i atomowego przenoszenia (rename) po zakończeniu generacji.
  • Cache’uj wyniki drogich zapytań i unieważniaj je po przetworzeniu partii danych.
  • Dostarczaj status (np. opcja last_report_generated_at) oraz link do ostatniego pliku.

Mierniki, alerty i zdrowie procesów

Dodaj prosty „health check”:

  • Opcja last_success_at i last_failure_at dla każdego zadania.
  • Jeżeli last_success_at starsze niż 1h, wyślij alert do administratora.
  • Raport tygodniowy: sumaryczna liczba przetworzonych elementów, średni czas wsadu, odsetek błędów.

Takie metryki pomagają wykryć ciche awarie, zanim staną się widoczne dla użytkowników.

Checklist wdrożeniowy

Przed uruchomieniem na produkcji:

  • Testy lokalne: ręczne uruchomienie zadania, wymuszenie warunków brzegowych.
  • Staging: weryfikacja harmonogramu, blokady, logów i wskaźników.
  • Produkcja: DISABLE_WP_CRON + systemowy cron co 1–5 minut lub zachowanie domyślne – decyzja świadoma, uzasadniona ruchem.
  • Monitoring: alerty na brak sukcesu i wzrost błędów.
  • Dokumentacja: opis interwałów, hooków, ścieżek logów i procedur awaryjnych.

Na koniec warto pamiętać, że niezależnie od tego, czy używasz WP‑Cron czy systemowego crona, najważniejsze są: przejrzysta architektura, ograniczanie skutków błędów, świadome zarządzanie interwałami i obciążeniem oraz dbałość o bezpieczeństwo i przewidywalność. Dzięki tym zasadom nawet rozbudowane harmonogramy pozostaną stabilne i łatwe w utrzymaniu, a Twoje zadania będą wykonywać się terminowo i bez zbędnych niespodzianek.

< Powrót

Zapisz się do newslettera


Zadzwoń Napisz