WP Cron ile WordPress Zamanlanmış Görev Oluşturma
WordPress ile eklenti geliştiriyorsanız veya mevcut bir siteyi yönetiyorsanız, er ya da geç zamanlanmış görevlerle yüzleşmek zorunda kalırsınız. “Neden her gece fiyat güncelleme işim yarıda kalıyor?” ya da “Neden e-posta bildirimleri düzgün gitmiyor?” sorularının arkasında çoğu zaman WP-Cron var. Bu yazıda WP-Cron’u gerçekten anlamak ve doğru kullanmak için bilmeniz gereken her şeyi ele alacağım.
WP-Cron Nedir ve Nasıl Çalışır?
WP-Cron, WordPress’in kendi içindeki sahte bir cron sistemidir. “Sahte” diyorum çünkü gerçek bir Unix cron job gibi belirli aralıklarla sistem tarafından tetiklenmez. Bunun yerine siteye gelen her HTTP isteğinde WordPress, wp-cron.php dosyasını kontrol eder ve varsa bekleyen işleri çalıştırır.
Bu yaklaşımın bir avantajı var: Hosting üzerinde cron erişiminiz olmasa bile zamanlanmış görevler çalışır. Ama ciddi dezavantajları da var. Sitenize hiç ziyaretçi gelmezse görevler hiç tetiklenmez. Tersine, çok yoğun bir siteye sahipseniz aynı anda onlarca istek geldiğinde WP-Cron her seferinde devreye girerek gereksiz yük oluşturabilir.
Prodüksiyonda bunu nasıl yönettiğimize birazdan geleceğiz, ama önce temel yapıyı anlayalım.
Temel WP-Cron Fonksiyonları
WordPress’in cron API’si birkaç temel fonksiyon üzerine kurulu:
- wp_schedule_event(): Tekrarlayan bir görev zamanlar
- wp_schedule_single_event(): Tek seferlik bir görev zamanlar
- wp_unschedule_event(): Belirli bir zamanlanmış görevi kaldırır
- wp_clear_scheduled_hook(): Bir hook’a bağlı tüm zamanlamaları temizler
- wp_next_scheduled(): Bir sonraki zamanlanmış çalışmanın timestamp’ini döndürür
- wp_get_schedules(): Mevcut zaman aralıklarını listeler
Görev Zamanlamanın Doğru Yolu
Bir görevi plugin aktivasyonunda zamanlamak için register_activation_hook kullanmak standart yaklaşım:
<?php
// Plugin aktivasyonunda görevi zamanla
register_activation_hook( __FILE__, 'mysite_activate_cron' );
function mysite_activate_cron() {
if ( ! wp_next_scheduled( 'mysite_hourly_task' ) ) {
wp_schedule_event( time(), 'hourly', 'mysite_hourly_task' );
}
}
// Plugin deaktivasyonunda görevi temizle
register_deactivation_hook( __FILE__, 'mysite_deactivate_cron' );
function mysite_deactivate_cron() {
$timestamp = wp_next_scheduled( 'mysite_hourly_task' );
wp_unschedule_event( $timestamp, 'mysite_hourly_task' );
}
// Gerçek işi yapan fonksiyon
add_action( 'mysite_hourly_task', 'mysite_do_hourly_task' );
function mysite_do_hourly_task() {
// Burada iş yapılıyor
error_log( 'Saatlik görev çalıştı: ' . current_time( 'mysql' ) );
}
Burada önemli bir detay var: wp_next_scheduled() kontrolü olmadan wp_schedule_event() çağrırsanız, aynı görev birden fazla kez zamanlanabilir. Plugin güncellemelerinde bu sorun sık görülür ve aniden duplike işler oluşur.
Özel Zaman Aralıkları Tanımlama
WordPress varsayılan olarak hourly, twicedaily ve daily aralıklarını sunar. Ama pratikte bunlar çoğu zaman yetmez. Beş dakikada bir çalışan bir görev veya haftalık bir rapor için özel aralık tanımlamanız gerekir:
<?php
add_filter( 'cron_schedules', 'mysite_add_cron_intervals' );
function mysite_add_cron_intervals( $schedules ) {
// Her 5 dakikada bir
$schedules['every_five_minutes'] = array(
'interval' => 300,
'display' => __( 'Her 5 Dakikada Bir' ),
);
// Her 30 dakikada bir
$schedules['every_thirty_minutes'] = array(
'interval' => 1800,
'display' => __( 'Her 30 Dakikada Bir' ),
);
// Haftalık
$schedules['weekly'] = array(
'interval' => 604800,
'display' => __( 'Haftalık' ),
);
return $schedules;
}
Bu filtreyi kullandıktan sonra kendi interval adınızı wp_schedule_event() içinde kullanabilirsiniz.
Gerçek Dünya Senaryosu: WooCommerce Stok Senkronizasyonu
Birkaç yıl önce büyük bir e-ticaret müşterisinin projesinde çalışırken şöyle bir gereksinim çıktı: Dış bir depo yönetim sisteminden her 15 dakikada bir stok verisi çekilecek, WooCommerce ürün stokları güncellenecekti. İşte o projeden sadeleştirilmiş bir versiyon:
<?php
/**
* Stok senkronizasyon cron görevi
*/
register_activation_hook( __FILE__, 'stock_sync_activate' );
register_deactivation_hook( __FILE__, 'stock_sync_deactivate' );
function stock_sync_activate() {
if ( ! wp_next_scheduled( 'stock_sync_event' ) ) {
wp_schedule_event( time(), 'every_fifteen_minutes', 'stock_sync_event' );
}
}
function stock_sync_deactivate() {
wp_clear_scheduled_hook( 'stock_sync_event' );
}
add_filter( 'cron_schedules', 'stock_sync_add_interval' );
function stock_sync_add_interval( $schedules ) {
$schedules['every_fifteen_minutes'] = array(
'interval' => 900,
'display' => '15 Dakikada Bir',
);
return $schedules;
}
add_action( 'stock_sync_event', 'stock_sync_run' );
function stock_sync_run() {
$api_url = get_option( 'stock_sync_api_url' );
$api_key = get_option( 'stock_sync_api_key' );
if ( empty( $api_url ) || empty( $api_key ) ) {
error_log( '[StockSync] API bilgileri eksik, senkronizasyon atlandı.' );
return;
}
$response = wp_remote_get( $api_url, array(
'headers' => array( 'Authorization' => 'Bearer ' . $api_key ),
'timeout' => 30,
) );
if ( is_wp_error( $response ) ) {
error_log( '[StockSync] API hatası: ' . $response->get_error_message() );
return;
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( empty( $data['products'] ) ) {
return;
}
foreach ( $data['products'] as $item ) {
$product_id = wc_get_product_id_by_sku( $item['sku'] );
if ( ! $product_id ) {
continue;
}
$product = wc_get_product( $product_id );
$product->set_stock_quantity( intval( $item['quantity'] ) );
$product->save();
}
error_log( '[StockSync] Senkronizasyon tamamlandı: ' . count( $data['products'] ) . ' ürün güncellendi.' );
}
Bu yapı güzel çalışıyor, ama dikkat edin: wp_remote_get() için timeout değerini mutlaka belirtin. Varsayılan değer çok düşük olduğundan yavaş API’larda görev timeout’a düşer ve sessizce başarısız olur.
Tek Seferlik Zamanlanmış Görevler
Bazen tekrarlayan değil, tek seferlik ertelenmiş bir işlem yapmanız gerekir. Örneğin kullanıcı kayıt olduğunda 24 saat sonra bir hatırlatma maili atmak:
<?php
// Kullanıcı kayıt olduğunda tetikleniyor
add_action( 'user_register', 'schedule_welcome_followup' );
function schedule_welcome_followup( $user_id ) {
// 24 saat sonra çalışacak tek seferlik görev
wp_schedule_single_event(
time() + DAY_IN_SECONDS,
'send_welcome_followup_email',
array( $user_id )
);
}
add_action( 'send_welcome_followup_email', 'do_send_welcome_followup_email' );
function do_send_welcome_followup_email( $user_id ) {
$user = get_userdata( $user_id );
if ( ! $user ) {
return;
}
$subject = 'Merhaba ' . $user->display_name . ', nasıl gidiyor?';
$message = 'Sitemizdeki ilk gününüzden bu yana 24 saat geçti...';
wp_mail( $user->user_email, $subject, $message );
}
wp_schedule_single_event() fonksiyonuna parametre geçerken dikkat edin: Üçüncü parametre mutlaka array olmalıdır, tek bir değer bile olsa array( $user_id ) şeklinde göndermeniz gerekiyor.
WP-Cron’u Devre Dışı Bırakıp Gerçek Cron Kullanmak
Prodüksiyon ortamında WP-Cron’un HTTP tabanlı tetikleyici yapısı ciddi sorunlara yol açabilir. Özellikle yüksek trafikli sitelerde her sayfa yüklemesinde cron kontrolü gereksiz PHP işlemcisi tüketir. Bunun çözümü önce WP-Cron’u devre dışı bırakıp sonra sistem cron’u ile tetiklemek:
wp-config.php dosyasına ekleyin:
define( 'DISABLE_WP_CRON', true );
Sonra sistem cron’una ekleyin:
# crontab -e ile düzenleyin
# Her 5 dakikada bir WP-Cron'u çalıştır
*/5 * * * * php /var/www/html/wp-cron.php > /dev/null 2>&1
# Alternatif olarak wget kullanabilirsiniz
*/5 * * * * wget -q -O - https://siteadiniz.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1
# WP-CLI varsa en temiz yol budur
*/5 * * * * cd /var/www/html && wp cron event run --due-now --allow-root > /dev/null 2>&1
WP-CLI yöntemi özellikle çoklu site kurulumlarında veya belirli bir WordPress kurulumu hedeflemeniz gerektiğinde çok daha temiz çalışır. wp cron event run --due-now sadece bekleyen işleri çalıştırır ve gereksiz HTTP isteği oluşturmaz.
Zamanlanmış Görevleri Debug Etme
Geliştirme sürecinde en sinir bozucu şey cron görevinin çalışıp çalışmadığını anlamamaktır. Birkaç pratik yaklaşım:
WP-CLI ile mevcut cron listesini görmek:
# Zamanlanmış tüm görevleri listele
wp cron event list
# Belirli bir görevi hemen çalıştır (test için)
wp cron event run mysite_hourly_task
# Süresi gelmiş tüm görevleri çalıştır
wp cron event run --due-now
Kod tarafında debug için basit ama etkili bir yaklaşım error_log kullanmak. Ama bunu daha organize hale getirin:
<?php
function mysite_cron_log( $message, $level = 'INFO' ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$timestamp = current_time( 'Y-m-d H:i:s' );
error_log( sprintf( '[%s][%s][MyCron] %s', $timestamp, $level, $message ) );
}
}
add_action( 'mysite_hourly_task', 'mysite_do_hourly_task' );
function mysite_do_hourly_task() {
mysite_cron_log( 'Görev başladı.' );
// İş mantığı...
mysite_cron_log( 'Görev tamamlandı.' );
}
/var/log/ altında veya wp-content/debug.log içinde bu logları takip edebilirsiniz.
Cron Görevinde Hafıza ve Zaman Aşımı Yönetimi
Büyük veri kümelerini işleyen cron görevlerinde en sık karşılaştığım sorun PHP memory limit ve execution time aşımıdır. Bunu yönetmek için toplu işleme yaklaşımı kullanın:
<?php
add_action( 'mysite_batch_process', 'mysite_run_batch_process' );
function mysite_run_batch_process() {
$batch_size = 100;
$offset = intval( get_option( 'mysite_batch_offset', 0 ) );
// Limiti biraz genişlet (dikkatli kullanın)
@set_time_limit( 120 );
$items = get_posts( array(
'post_type' => 'product',
'posts_per_page' => $batch_size,
'offset' => $offset,
'post_status' => 'publish',
'fields' => 'ids',
) );
if ( empty( $items ) ) {
// İşlem tamamlandı, offset'i sıfırla
delete_option( 'mysite_batch_offset' );
mysite_cron_log( 'Toplu işlem tamamlandı, offset sıfırlandı.' );
return;
}
foreach ( $items as $post_id ) {
// Her ürün için işlem yap
do_something_with_product( $post_id );
}
// Bir sonraki çalışma için offset'i güncelle
update_option( 'mysite_batch_offset', $offset + $batch_size );
mysite_cron_log( sprintf( '%d. offsetten %d kayıt işlendi.', $offset, count( $items ) ) );
}
Bu yaklaşım sayesinde tüm ürünleri tek seferde işlemeye çalışmak yerine her cron çalışmasında 100’er 100’er ilerlersiniz. Bir sonraki çalışmada kaldığınız yerden devam edersiniz.
Birden Fazla Site Yönetirken Dikkat Edilmesi Gerekenler
Birden fazla WordPress sitesi yönetiyorsanız, özellikle paylaşımlı hosting ortamlarında, WP-Cron görevlerinin birikip sunucuyu boğduğunu görebilirsiniz. Bu durumda bazı pratik önlemler:
- Her plugin için mutlaka deactivation hook ekleyin: Eklentiniz deaktive edildiğinde zamanlamalar temizlenmezse yetim görevler veritabanında birikir.
wp_next_scheduled()kontrolünü atlamamak: Görev zaten varken tekrar eklememek kritik.- Görevlerin üst üste binmesini önlemek: Uzun süren bir görev hala çalışırken bir sonraki tetiklenirse ikisi paralel çalışabilir. Bunun için transient bazlı kilit kullanın:
<?php
add_action( 'mysite_long_running_task', 'mysite_run_long_task' );
function mysite_run_long_task() {
// Kilit var mı kontrol et
if ( get_transient( 'mysite_task_lock' ) ) {
mysite_cron_log( 'Görev zaten çalışıyor, atlandı.', 'WARN' );
return;
}
// Kilidi al (10 dakika için)
set_transient( 'mysite_task_lock', true, 10 * MINUTE_IN_SECONDS );
try {
// Uzun süren iş buraya
mysite_cron_log( 'Uzun görev başladı.' );
// ... işlemler ...
mysite_cron_log( 'Uzun görev tamamlandı.' );
} catch ( Exception $e ) {
mysite_cron_log( 'Hata: ' . $e->getMessage(), 'ERROR' );
} finally {
// Kilidi her durumda serbest bırak
delete_transient( 'mysite_task_lock' );
}
}
Bu transient kilidi yaklaşımı basit ama oldukça etkili. Özellikle harici API çağrısı yapan ve zaman zaman yavaşlayan görevlerde hayat kurtarır.
Action Scheduler: Ciddi İşler İçin Ciddi Araç
WooCommerce ile ciddi bir çalışma yapıyorsanız, Action Scheduler kütüphanesini mutlaka bilmeniz gerekiyor. WooCommerce bu kütüphaneyi kendi içinde kullanıyor ve standalone olarak da kullanılabilir. WP-Cron’a göre çok daha güvenilir, veritabanı tabanlı bir kuyruklama sistemi sunuyor.
Action Scheduler kullanırken temel yaklaşım şu şekilde:
<?php
// Action Scheduler mevcut mu kontrol et
if ( function_exists( 'as_schedule_recurring_action' ) ) {
// Tekrarlayan görev zamanla
if ( false === as_next_scheduled_action( 'mysite_as_hourly_task' ) ) {
as_schedule_recurring_action(
time(),
HOUR_IN_SECONDS,
'mysite_as_hourly_task',
array(),
'mysite-group'
);
}
}
add_action( 'mysite_as_hourly_task', 'mysite_run_as_task' );
function mysite_run_as_task() {
// Bu görev başarısız olursa Action Scheduler otomatik yeniden dener
// ve WordPress admin panelinden geçmişini görebilirsiniz
}
Action Scheduler’ın WP-Cron üzerindeki en büyük avantajı: Başarısız görevleri otomatik tekrar denemesi ve wp-admin üzerinden tüm görev geçmişini görüntüleyebilmeniz.
Sonuç
WP-Cron mekanizması basit görünse de prodüksiyonda doğru yönetilmediğinde ciddi sorunlara yol açar. Görevlerin hiç çalışmaması, duplike çalışması veya sunucuyu boğması hepsi karşılaşabileceğiniz gerçek senaryolar. Bu yazıdaki yaklaşımları benimserseniz çoğu sorunun önüne geçebilirsiniz.
Özet olarak aklınızda kalması gerekenler:
- Prodüksiyon sitelerinde
DISABLE_WP_CRONkullanın ve gerçek sistem cron’u ekleyin, tercihen WP-CLI ile - Her eklenti aktivasyon ve deaktivasyon hook’larını düzgün yönetsin, görevler temizlensin
wp_next_scheduled()kontrolünü hiç atlamayın- Uzun süren görevler için transient kilidi kullanın
- Büyük veri işleme için toplu işleme mantığı kurun
- WooCommerce entegrasyonlarında Action Scheduler’ı değerlendirin
- Debug için her zaman
wp cron event listvewp cron event runkomutlarını kullanın
WP-Cron ile ilgili sorularınız veya farklı senaryolarınız varsa yorumlarda paylaşabilirsiniz.
