add_action Kullanımı: WordPress Olaylarına Müdahale

WordPress eklenti geliştirmeye yeni başlayanların en çok kafasının karıştığı konulardan biri, sistemin nasıl “çalışır” sorusudur. Bir fonksiyon yazıyorsunuz, ama bu fonksiyonu kim çağıracak? Ne zaman çalışacak? İşte tam burada add_action devreye giriyor ve WordPress’in kalbini oluşturan olay tabanlı mimarinin kapılarını aralıyor.

WordPress’in Olay Sistemi Nasıl Çalışır?

WordPress, bir sayfa yüklendiğinde onlarca hatta yüzlerce “olay” (event) tetikler. Bu olaylar belirli noktalarda, belirli sırayla çalışır. wp_head, init, wp_footer, save_post bunların yalnızca birkaçıdır. WordPress bu olayları tetiklerken siz de kendi kodunuzu bu olaylara “kanca” (hook) olarak bağlayabilirsiniz.

add_action tam olarak bu işe yarar: bir olayı dinler ve o olay tetiklendiğinde sizin fonksiyonunuzu çalıştırır. Bunu bir nevi olay aboneliği gibi düşünebilirsiniz. Pub/Sub sistemlerine aşinaysanız mantık aynı, sadece WordPress’e özgü bir API ile sarmalanmış.

add_action( 'olay_adi', 'fonksiyon_adi', $oncelik, $parametre_sayisi );

Fonksiyonun dört parametresi var:

  • $hook_name: Dinleyeceğiniz olayın adı. Bir string. Örneğin 'init', 'wp_head', 'save_post'.
  • $callback: Olay tetiklendiğinde çalışacak fonksiyon. String, anonim fonksiyon veya [$nesne, 'metod'] formatında olabilir.
  • $priority: Çalışma önceliği. Varsayılan 10. Düşük sayı daha önce çalışır. Aynı olaya bağlı birden fazla fonksiyon varsa sırayı belirler.
  • $accepted_args: Olaydan kaç parametre alacağınız. Varsayılan 1. Bazı olaylar birden fazla parametre geçirir.

Temel Kullanım: İlk Kancayı Bağlamak

En basit senaryodan başlayalım. init olayı WordPress yüklendiğinde erken aşamada tetiklenir ve genellikle özel post tipleri kaydetmek, kısa kodlar tanımlamak gibi işlemler için kullanılır.

function site_ozelliklerini_kaydet() {
    // Buraya init aşamasında çalışmasını istediğiniz kodlar gelir
    register_post_type( 'kitap', [
        'labels'      => [ 'name' => 'Kitaplar', 'singular_name' => 'Kitap' ],
        'public'      => true,
        'has_archive' => true,
        'supports'    => [ 'title', 'editor', 'thumbnail' ],
    ]);
}
add_action( 'init', 'site_ozelliklerini_kaydet' );

Dikkat ettiyseniz add_action çağrısını fonksiyon tanımının altına yazdım. Bu tercih meselesi, WordPress fonksiyon bildirimlerini hoisting yapar gibi değerlendirmez ama PHP kendi içinde bunu halleder. Önemli olan add_action‘ın WordPress yüklenme sürecinde çalıştırılmasıdır.

Öncelik Parametresi: Kim Önce Çalışır?

Bu parametreyi küçümsemeyin. Gerçek projelerde ciddi hata ayıklama seanslarının sebebi çoğunlukla yanlış öncelik değeridir.

Diyelim ki iki farklı eklenti aynı olayı dinliyor. Birincisi menüye bir öğe ekliyor, ikincisi ise o menüyü başka bir şekilde modifiye ediyor. Sıralama önemli.

// Bu önce çalışır (öncelik: 5)
function ana_menu_olustur() {
    register_nav_menus([
        'ana-menu' => 'Ana Menü',
        'alt-menu' => 'Alt Menü',
    ]);
}
add_action( 'init', 'ana_menu_olustur', 5 );

// Bu daha sonra çalışır (öncelik: 15)
function menu_sonrasi_islemler() {
    // Menüler zaten kayıtlı, şimdi onlarla ilgili başka işlemler yapabiliriz
    do_action( 'ozel_menu_hazir' );
}
add_action( 'init', 'menu_sonrasi_islemler', 15 );

Bir gerçek dünya senaryosu: WooCommerce ile çalışıyorsunuz ve ürün kayıt olayına kanca atıyorsunuz. Ancak WooCommerce’in kendi kancaları da aynı olayda çalışıyor. Eğer önceliğinizi yanlış ayarlarsanız WooCommerce henüz gerekli verileri hazırlamamışken kodunuz çalışmaya başlar ve beklenmedik hatalar alırsınız.

Parametre Geçiren Olaylar

Bazı olaylar tetiklenirken veri de taşır. save_post olayı kaydedilen postun ID’sini, WP_Post nesnesini ve güncelleme mi yeni kayıt mı olduğunu söyleyen bir boolean geçirir.

function post_kaydedildi( $post_id, $post, $guncelleme ) {
    // Otomatik kaydetme ise atla
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // Sadece 'makale' post tipini ele al
    if ( 'makale' !== $post->post_type ) {
        return;
    }

    // Yetki kontrolü
    if ( ! current_user_can( 'edit_post', $post_id ) ) {
        return;
    }

    $durum = $guncelleme ? 'güncellendi' : 'yeni oluşturuldu';
    error_log( "Makale {$post_id} {$durum}." );
}
add_action( 'save_post', 'post_kaydedildi', 10, 3 );

Burada kritik nokta: add_action‘ın dördüncü parametresini 3 veriyoruz çünkü save_post üç parametre geçiriyor ve biz üçünü de kullanmak istiyoruz. Eğer bu değeri atarsanız veya 1 bırakırsanız fonksiyonunuz yalnızca ilk parametreyi alır.

Anonim Fonksiyonlar ve Closure Kullanımı

Modern PHP geliştirmede anonim fonksiyonlar oldukça yaygın. WordPress kancalarında da kullanabilirsiniz, ancak bir önemli kısıtlama var: remove_action ile sonradan kaldıramazsınız.

add_action( 'wp_footer', function() {
    echo '<div class="site-version" style="display:none;">v2.4.1</div>';
});

Bu yaklaşım küçük, tek kullanımlık işlemler için uygundur. Ancak bir eklenti geliştiriyorsanız ve bu kancayı belirli koşullarda kaldırmanız gerekebiliyorsa, anonim fonksiyondan kaçının.

Closure’ların güzel bir özelliği use anahtar kelimesiyle dış değişkenlere erişebilmesidir:

$ayarlar = get_option( 'benim_eklentim_ayarlar' );

add_action( 'wp_enqueue_scripts', function() use ( $ayarlar ) {
    if ( ! empty( $ayarlar['ozel_css'] ) ) {
        wp_add_inline_style( 'benim-tema-css', $ayarlar['ozel_css'] );
    }
});

Sınıf Metodlarını Kanca Olarak Kullanmak

Eklenti geliştirirken nesne yönelimli programlama (OOP) kullanıyorsanız, kancaları sınıf metodlarına bağlamanız gerekecek. İki yol var.

Statik metod:

class BenimEklentim {

    public static function init() {
        add_action( 'wp_enqueue_scripts', [ __CLASS__, 'skriptleri_yukle' ] );
        add_action( 'admin_menu', [ __CLASS__, 'admin_menu_ekle' ] );
    }

    public static function skriptleri_yukle() {
        wp_enqueue_script(
            'benim-skriptim',
            plugin_dir_url( __FILE__ ) . 'js/main.js',
            [ 'jquery' ],
            '1.0.0',
            true
        );
    }

    public static function admin_menu_ekle() {
        add_menu_page(
            'Benim Eklentim',
            'Benim Eklentim',
            'manage_options',
            'benim-eklentim',
            [ __CLASS__, 'admin_sayfasi_goster' ]
        );
    }
}

BenimEklentim::init();

Nesne örneği (instance) ile:

class WooSiparisIsleyici {

    private $bildirim_servisi;

    public function __construct( $bildirim_servisi ) {
        $this->bildirim_servisi = $bildirim_servisi;
    }

    public function kancalari_kaydet() {
        add_action( 'woocommerce_new_order', [ $this, 'yeni_siparis_alindi' ], 10, 2 );
        add_action( 'woocommerce_order_status_completed', [ $this, 'siparis_tamamlandi' ] );
        add_action( 'woocommerce_order_status_cancelled', [ $this, 'siparis_iptal_edildi' ] );
    }

    public function yeni_siparis_alindi( $siparis_id, $siparis ) {
        $musteri = $siparis->get_billing_first_name();
        $tutar   = $siparis->get_total();

        $this->bildirim_servisi->gonder(
            "Yeni sipariş! Müşteri: {$musteri}, Tutar: {$tutar}₺"
        );
    }

    public function siparis_tamamlandi( $siparis_id ) {
        // Tamamlanan siparişlere özel işlemler
        update_post_meta( $siparis_id, '_tamamlanma_zamani', current_time( 'mysql' ) );
    }

    public function siparis_iptal_edildi( $siparis_id ) {
        // Stok güncelleme veya bildirim işlemleri
        do_action( 'benim_eklentim_siparis_iptal', $siparis_id );
    }
}

$isleyici = new WooSiparisIsleyici( new BildirimServisi() );
$isleyici->kancalari_kaydet();

Bu yaklaşım çok daha temiz ve test edilebilir. Bağımlılıkları constructor üzerinden enjekte edebiliyorsunuz ve her metodun tek bir sorumluluğu var.

Gerçek Dünya Senaryosu: Admin Bildirimi ve Zamanlanmış Görev

Diyelim ki her gece yarısı stok durumu kritik seviyenin altına düşen ürünleri kontrol eden ve site yöneticisine e-posta gönderen bir sistem kurmak istiyorsunuz.

// Eklenti aktif edildiğinde zamanlanmış görevi kur
function stok_kontrol_cronunu_kur() {
    if ( ! wp_next_scheduled( 'gece_stok_kontrolu' ) ) {
        wp_schedule_event( strtotime( 'tomorrow midnight' ), 'daily', 'gece_stok_kontrolu' );
    }
}
add_action( 'activate_' . plugin_basename( __FILE__ ), 'stok_kontrol_cronunu_kur' );

// Zamanlanmış görevin kendisi
function kritik_stok_kontrolu() {
    $kritik_esik = get_option( 'kritik_stok_esigi', 5 );

    $args = [
        'post_type'      => 'product',
        'posts_per_page' => -1,
        'meta_query'     => [
            [
                'key'     => '_stock',
                'value'   => $kritik_esik,
                'compare' => '<=',
                'type'    => 'NUMERIC',
            ],
            [
                'key'   => '_manage_stock',
                'value' => 'yes',
            ],
        ],
    ];

    $kritik_urunler = get_posts( $args );

    if ( empty( $kritik_urunler ) ) {
        return;
    }

    $mesaj = "Kritik stok seviyesindeki ürünler:nn";

    foreach ( $kritik_urunler as $urun ) {
        $stok = get_post_meta( $urun->ID, '_stock', true );
        $mesaj .= "- {$urun->post_title}: {$stok} adetn";
    }

    wp_mail(
        get_option( 'admin_email' ),
        '[Stok Uyarısı] Kritik Seviye Altındaki Ürünler',
        $mesaj
    );
}
add_action( 'gece_stok_kontrolu', 'kritik_stok_kontrolu' );

// Eklenti deaktive edildiğinde cron'u temizle
function stok_kontrol_cronunu_temizle() {
    $zaman_damgasi = wp_next_scheduled( 'gece_stok_kontrolu' );
    wp_unschedule_event( $zaman_damgasi, 'gece_stok_kontrolu' );
}
add_action( 'deactivate_' . plugin_basename( __FILE__ ), 'stok_kontrol_cronunu_temizle' );

Bu örnekte birkaç farklı olayı kullandık: eklenti aktivasyon/deaktivasyon olayları ve kendi tanımladığımız özel bir cron olayı. add_action‘ın esnekliği burada açıkça görünüyor.

add_action ile add_filter Arasındaki Fark

Bu soruyu çok alıyorum. Kavramsal olarak ikisi de kanca sistemi kullanır ama amaçları farklı.

  • add_action: Bir şey yap, bir işlem gerçekleştir. Dönüş değeri önemli değil.
  • add_filter: Bir veriyi al, değiştir, geri döndür. Dönüş değeri zorunlu.

Pratik kural: E-posta gönder, dosya yaz, veri tabanına kaydet gibi yan etkiler için add_action. Başlık metnini değiştir, ürün fiyatını hesapla, HTML çıktısını modifiye et gibi işlemler için add_filter.

Aslında WordPress altyapısında ikisi de aynı sistemi kullanır, fark yalnızca semantik ve kullanım konvansiyonundadır. Bir add_action içinden değer dönebilirsiniz ama WordPress onu dikkate almaz.

Kancaları Debug Etmek

Üretim ortamında bir kanca beklendiği gibi çalışmıyorsa ne yaparsınız? İşte birkaç pratik yöntem.

Hangi kancaların tetiklendiğini görmek için:

function tum_kancalari_logla() {
    global $wp_current_filter;

    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        error_log( 'Aktif kanca: ' . implode( ' > ', $wp_current_filter ) );
    }
}
add_action( 'all', 'tum_kancalari_logla' );

'all' özel bir kanca adıdır; her tetiklenen olayı dinler. Debug modunda açabilirsiniz ama üretimde kesinlikle kapatın, log dosyalarınız şişer.

Belirli bir kancaya kaç fonksiyonun bağlı olduğunu görmek için:

function kanca_durumunu_goster( $kanca_adi ) {
    global $wp_filter;

    if ( isset( $wp_filter[ $kanca_adi ] ) ) {
        $kanca = $wp_filter[ $kanca_adi ];
        echo "<pre>";
        echo "'{$kanca_adi}' kancasına bağlı fonksiyonlar:n";
        foreach ( $kanca->callbacks as $oncelik => $fonksiyonlar ) {
            echo "Öncelik {$oncelik}:n";
            foreach ( $fonksiyonlar as $fonksiyon ) {
                if ( is_array( $fonksiyon['function'] ) ) {
                    echo "  - " . get_class( $fonksiyon['function'][0] ) . "::" . $fonksiyon['function'][1] . "n";
                } elseif ( is_string( $fonksiyon['function'] ) ) {
                    echo "  - " . $fonksiyon['function'] . "n";
                }
            }
        }
        echo "</pre>";
    }
}

// Örnek kullanım - admin sayfasında
add_action( 'admin_footer', function() {
    if ( current_user_can( 'manage_options' ) && isset( $_GET['debug_hooks'] ) ) {
        kanca_durumunu_goster( 'wp_head' );
        kanca_durumunu_goster( 'init' );
    }
});

Sık Yapılan Hatalar

Tecrübelerimden derlediğim kritik noktalar:

  • Kancayı çok erken veya çok geç bağlamak: admin_init yerine init kullanmak admin-only fonksiyonların ön yüzde de çalışmasına sebep olabilir.
  • Sonsuz döngü: Bir kanca içinde aynı kancayı tetikleyen bir fonksiyon çağırmak. Özellikle save_post içinde wp_update_post çağrısı klasik bir örnektir. remove_action ile kancayı geçici olarak kaldırıp tekrar ekleyin.
  • Parametre sayısını unutmak: Üç parametre alan bir olayı ikinci parametresini kullanmak için accepted_args değerini 2 olarak belirtmeyi atlamak.
  • Nonce kontrolü yapmamak: save_post gibi olaylarda güvenlik kontrollerini atlamak ciddi güvenlik açıklarına yol açar.
  • Anonim fonksiyonları kaldırmaya çalışmak: remove_action( 'init', function(){} ) çalışmaz. Eğer kancayı kaldırmanız gerekebilecekse isimli fonksiyon kullanın.

Sonuç

add_action, WordPress eklenti geliştirmenin adeta omurgasıdır. Basit bir API gibi görünür ama altında yatan olay tabanlı mimariyi kavramak, hem kaliteli eklentiler yazmanızı hem de başkalarının yazdığı kodları anlamanızı kolaylaştırır.

Özetlemek gerekirse: öncelik parametresine dikkat edin, parametre sayısını doğru verin, OOP kullanıyorsanız nesne metodlarını doğru şekilde referans alın ve her zaman güvenlik kontrollerini ihmal etmeyin. Bu dört noktayı aklınızda tuttuğunuzda add_action ile neredeyse her türlü özelleştirmeyi güvenli ve sürdürülebilir bir şekilde gerçekleştirebilirsiniz.

WordPress’in sunduğu yüzlerce hazır kancayı keşfetmek için WordPress Developer Reference sitesi iyi bir başlangıç noktasıdır. Ama dürüst olalım: en iyi öğrenme yöntemi bir özellik geliştirmek, hangi kancayı nerede kullanacağınızı araştırmak ve gerçek bir proje üzerinde denemektir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir