add_action ve add_filter Kullanımı: Öncelik ve Parametre Ayarları

WordPress geliştirme dünyasına girdiğinizde, karşınıza çıkan en temel iki fonksiyon add_action ve add_filter olacaktır. Bu iki fonksiyon, WordPress’in çekirdek mimarisini oluşturan hook sisteminin temel taşlarıdır. Tema geliştiriyorsanız, eklenti yazıyorsanız ya da sadece functions.php üzerinden sitenizi özelleştiriyorsanız, bu iki fonksiyonu doğru anlamak ve kullanmak her şeyi değiştirir.

Bu yazıda sadece teorik bilgi vermeyeceğim. Gerçek senaryolar, sık yapılan hatalar ve öncelik/parametre ayarlarının nasıl çalıştığını pratik örneklerle ele alacağız.

Hook Sistemi Nedir, Neden Bu Kadar Önemli?

WordPress, olay tabanlı (event-driven) bir mimari üzerine kurulmuştur. Çekirdek kod çalışırken belirli noktalarda “buraya bir şey eklemek isteyen var mı?” diye sorar. İşte bu sorulan noktalara hook denir. İki tür hook vardır:

  • Action hooks: Belirli bir olay gerçekleştiğinde kod çalıştırmanızı sağlar. Örneğin sayfa başlığı oluşturulurken, form gönderildiğinde, kullanıcı giriş yaptığında.
  • Filter hooks: Bir veriyi alıp değiştirmenizi ve geri döndürmenizi sağlar. Örneğin bir yazının içeriğini veritabanından çekmeden önce değiştirmek.

Bu ayrım kritik. Action’larda bir şey yaparsınız, filter’larda bir şeyi değiştirirsiniz.

add_action Fonksiyonu: Parametreleri ve Kullanımı

add_action fonksiyonunun tam imzası şu şekildedir:

add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 )

Parametreleri tek tek inceleyelim:

  • $hook_name: Bağlanmak istediğiniz hook’un adı. wp_head, init, wp_footer gibi WordPress’in tanımladığı hooklar ya da diğer eklentilerin tanımladığı özel hooklar olabilir.
  • $callback: Çalıştırılacak fonksiyon. Bir fonksiyon adı string olarak, anonim fonksiyon ya da array($object, 'method') formatında bir sınıf metodu olabilir.
  • $priority: Öncelik değeri. Varsayılan 10‘dur. Düşük sayı, daha erken çalışır. Aynı hook’a birden fazla callback bağlandığında sıralama buna göre belirlenir.
  • $accepted_args: Hook’un callback’e kaç parametre geçireceğini belirtir. Varsayılan 1‘dir. Hook birden fazla argüman geçiriyorsa bunu belirtmeniz gerekir.

Temel add_action Örneği

<?php
// wp_head hook'una basit bir meta etiketi ekleme
function site_ozel_meta_ekle() {
    echo '<meta name="theme-color" content="#2c3e50">';
    echo '<meta name="robots" content="max-image-preview:large">';
}
add_action( 'wp_head', 'site_ozel_meta_ekle' );

Bu örnekte öncelik belirtmedik, dolayısıyla varsayılan 10 kullanılıyor. Çoğu basit durumda bu yeterli.

Öncelik (Priority) Ayarının Gerçek Önemi

Diyelim ki sitenizde hem bir önbellek eklentisi hem de bir güvenlik eklentisi wp_head hook’una bağlı. Hangisinin önce çalıştığı bazen kritik fark yaratır. İşte burada öncelik devreye girer.

<?php
// Bu fonksiyon önce çalışır (priority: 5)
function erken_calistir() {
    // Güvenlik başlıkları gibi kritik şeyler buraya
    echo '<meta http-equiv="Content-Security-Policy" content="default-src 'self'">';
}
add_action( 'wp_head', 'erken_calistir', 5 );

// Bu fonksiyon sonra çalışır (priority: 20)
function gec_calistir() {
    // Analitik kodları, çerez bildirimleri gibi şeyler
    echo '<script>// Analytics kodu buraya gelir</script>';
}
add_action( 'wp_head', 'gec_calistir', 20 );

Öncelik değerleri için genel kabul görmüş pratik kurallar:

  • 1-5: Çok erken çalışması gereken şeyler (güvenlik, kritik başlıklar)
  • 10: Varsayılan, standart işlemler
  • 20-50: Standart işlemlerden sonra çalışması gerekenler
  • 99-9999: En son çalışması gereken şeyler (genellikle override işlemleri)

Birden Fazla Argüman Alan Hook’lar

Bazı hooklar callback fonksiyonuna birden fazla argüman geçirir. save_post hook’u buna güzel bir örnek:

<?php
// save_post hook'u 3 argüman geçirir: post_id, post objesi, update durumu
function yazi_kaydedilince_isle( $post_id, $post, $update ) {
    // Draft kaydetmede çalışma
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }

    // Sadece belirli post tipinde çalış
    if ( 'urun_inceleme' !== $post->post_type ) {
        return;
    }

    // Yazı güncellendiğinde log tut
    if ( $update ) {
        error_log( 'Ürün incelemesi güncellendi: ' . $post_id );
    }
}
// Dikkat: 4. parametre olan accepted_args'ı 3 yapıyoruz
add_action( 'save_post', 'yazi_kaydedilince_isle', 10, 3 );

Accepted_args parametresini unutmak çok sık yapılan bir hatadır. Callback’inizin ikinci ve üçüncü argümanlarına ihtiyacınız varsa ama accepted_args‘ı belirtmediyseniz, bu değerlere erişemezsiniz.

add_filter Fonksiyonu: Veriyi Yakalayıp Değiştirmek

add_filter fonksiyonunun imzası add_action ile neredeyse aynıdır:

add_filter( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 )

Temel fark şudur: Filter callback’leri mutlaka bir değer döndürmek zorundadır. Döndürmeyi unutursanız, filtrelenen değer null olur ve bu ciddi sorunlara yol açar.

Temel add_filter Örneği

<?php
// Yazı excerpt uzunluğunu değiştirme
function ozel_excerpt_uzunlugu( $length ) {
    // Varsayılan 55 kelimeden 30 kelimeye düşürelim
    return 30;
}
add_filter( 'excerpt_length', 'ozel_excerpt_uzunlugu', 999 );

// Excerpt sonundaki "..." yazısını değiştirme
function ozel_excerpt_more( $more ) {
    return ' <a class="devami-oku" href="' . get_permalink() . '">Devamını oku &rarr;</a>';
}
add_filter( 'excerpt_more', 'ozel_excerpt_more' );

Burada excerpt_length filter’ında önceliği 999 yaptık. Neden? Çünkü başka bir eklenti de bu filter’a bağlıysa ve kendi değerini belirliyorsa, bizim değerimizin kazanmasını istiyoruz. Yüksek öncelik = son çalışan = son söz bizim.

WooCommerce ile Gerçek Senaryo: Ürün Fiyatı Filtreleme

WooCommerce projelerinde filter hookları çok sık kullanılır. Üye olan kullanıcılara indirimli fiyat göstermek klasik bir senaryodur:

<?php
// WooCommerce ürün fiyatını filtrele
function uye_indirimi_uygula( $price, $product ) {
    // Kullanıcı giriş yapmamışsa normal fiyat
    if ( ! is_user_logged_in() ) {
        return $price;
    }

    $user = wp_get_current_user();

    // Sadece "premium_uye" rolündeki kullanıcılara indirim
    if ( ! in_array( 'premium_uye', (array) $user->roles ) ) {
        return $price;
    }

    // Fiyatı sayıya çevir, %15 indirim uygula
    $indirimli_fiyat = floatval( $price ) * 0.85;

    return round( $indirimli_fiyat, 2 );
}
// İkinci argüman olan $product nesnesine ihtiyacımız var, accepted_args = 2
add_filter( 'woocommerce_product_get_price', 'uye_indirimi_uygula', 10, 2 );
add_filter( 'woocommerce_product_get_sale_price', 'uye_indirimi_uygula', 10, 2 );

Bu örnek birkaç açıdan önemli. Hem woocommerce_product_get_price hem de woocommerce_product_get_sale_price filter’larına bağlandık. Sadece birine bağlasaydık, indirimli fiyatın indirim uygulanmamış görünmesi gibi tutarsızlıklar çıkabilirdi.

Öncelik Çakışmaları ve Bunları Yönetmek

Büyük bir WordPress sitesinde onlarca eklenti ve tema aynı hook’lara bağlanır. Bu durum bazen beklenmedik davranışlara yol açar.

Tema ve Eklenti Çakışması Senaryosu

Diyelim ki kullandığınız bir eklenti the_content filter’ında içeriğin başına bir reklam kutusu ekliyor:

<?php
// Eklentinin yaptığı (siz bunu değiştiremezsiniz)
function eklenti_reklam_ekle( $content ) {
    $reklam = '<div class="reklam-kutusu">Reklam İçeriği</div>';
    return $reklam . $content;
}
add_filter( 'the_content', 'eklenti_reklam_ekle', 10 );

// Sizin functions.php'de yapacağınız: daha yüksek öncelikle override
function temizlenmiş_icerik( $content ) {
    // Reklam kutusunu içerikten temizle
    $content = preg_replace( '/<div class="reklam-kutusu">.*?</div>/s', '', $content );
    return $content;
}
// Priority 20 ile sonradan çalışıp eklentinin eklediğini temizliyoruz
add_filter( 'the_content', 'temizlenmiş_icerik', 20 );

Bu yaklaşım bazen gerekli olsa da, önce eklentinin ayarlarını incelemek ve resmi yollarla özelleştirmeye çalışmak daha sağlıklıdır. Eklenti güncellendiğinde HTML yapısı değişebilir ve regex’iniz işe yaramaz hale gelebilir.

remove_action ve remove_filter ile Hook Kaldırma

Bazen bir hook’u kaldırmanız gerekir. Bunun için remove_action ve remove_filter fonksiyonları kullanılır. Kritik kural: Kaldırma işlemi, ekleme işlemiyle aynı öncelikte yapılmalıdır.

<?php
// Woocommerce'in varsayılan breadcrumb'ını kaldır
function woo_breadcrumb_kaldir() {
    remove_action( 'woocommerce_before_main_content', 'woocommerce_breadcrumb', 20 );
}
add_action( 'init', 'woo_breadcrumb_kaldir' );

// Kendi breadcrumb'ınızı ekleyin
function ozel_breadcrumb_ekle() {
    // Özel breadcrumb fonksiyonunuz
    echo '<nav class="ozel-breadcrumb">...</nav>';
}
add_action( 'woocommerce_before_main_content', 'ozel_breadcrumb_ekle', 15 );

remove_action ile kaldırırken priority’nin eşleşmesi şart. woocommerce_breadcrumb 20 önceliğiyle eklenmiş, dolayısıyla 20 ile kaldırıyoruz. Farklı bir priority yazarsanız kaldırma işlemi çalışmaz.

Anonim Fonksiyonlar ve Dikkat Edilmesi Gerekenler

Modern PHP ile birlikte anonim fonksiyonlar (lambda/closure) yaygınlaştı. WordPress hook’larında da kullanabilirsiniz:

<?php
// Anonim fonksiyon ile hızlı bir filter
add_filter( 'login_errors', function( $error ) {
    // Güvenlik için login hata mesajlarını gizle
    return '<strong>HATA:</strong> Kullanıcı adı veya şifre hatalı.';
} );

// Admin footer yazısını değiştir
add_filter( 'admin_footer_text', function() {
    return 'Geliştirici: <a href="https://siteniz.com">Şirket Adı</a>';
} );

Ancak anonim fonksiyonların ciddi bir dezavantajı var: remove_action veya remove_filter ile kaldıramazsınız. Bir eklenti geliştiriyorsanız veya başkalarının override edebileceği hooklar yazıyorsanız, her zaman isimlendirilmiş fonksiyonları tercih edin.

Sınıf İçinde Hook Kullanımı

Nesne yönelimli (OOP) kod yazıyorsanız, hook’larınızı sınıf metotlarına bağlamanız gerekir:

<?php
class OzelSiteOzellikleri {

    public function __construct() {
        add_action( 'wp_enqueue_scripts', array( $this, 'script_yukle' ) );
        add_filter( 'body_class', array( $this, 'body_class_ekle' ) );
        add_action( 'save_post', array( $this, 'post_kaydet' ), 10, 2 );
    }

    public function script_yukle() {
        wp_enqueue_style(
            'ozel-stil',
            get_template_directory_uri() . '/assets/css/ozel.css',
            array(),
            '1.0.0'
        );
    }

    public function body_class_ekle( $classes ) {
        if ( is_user_logged_in() ) {
            $classes[] = 'uye-giris-yapmis';
        }

        if ( is_woocommerce() ) {
            $classes[] = 'woo-sayfasi';
        }

        return $classes;
    }

    public function post_kaydet( $post_id, $post ) {
        if ( 'publish' === $post->post_status ) {
            // Cache temizleme veya başka işlemler
            delete_transient( 'anasayfa_son_yazilar' );
        }
    }
}

// Sınıfı başlat
new OzelSiteOzellikleri();

Bu yapı, fonksiyonlarınızın isim çakışmalarına karşı koruma sağlar ve kodu daha organize hale getirir.

Gerçek Dünya Senaryosu: WooCommerce Sipariş Tamamlandığında E-posta ve Stok Güncelleme

<?php
// Sipariş tamamlandığında birden fazla işlemi tetikle
function siparis_tamamlandi_islemleri( $order_id ) {
    if ( ! $order_id ) {
        return;
    }

    $order = wc_get_order( $order_id );

    if ( ! $order ) {
        return;
    }

    // 1. Müşteriye özel teşekkür e-postası
    $musteri_email = $order->get_billing_email();
    $musteri_adi   = $order->get_billing_first_name();

    wp_mail(
        $musteri_email,
        'Siparişiniz için teşekkürler, ' . $musteri_adi . '!',
        'Siparişinizi aldık ve işleme koyduk. Sipariş numaranız: #' . $order_id,
        array( 'Content-Type: text/html; charset=UTF-8' )
    );

    // 2. Sipariş verisini özel tabloya kaydet (raporlama için)
    global $wpdb;
    $wpdb->insert(
        $wpdb->prefix . 'ozel_siparis_rapor',
        array(
            'siparis_id'   => $order_id,
            'toplam_tutar' => $order->get_total(),
            'tarih'        => current_time( 'mysql' ),
        ),
        array( '%d', '%f', '%s' )
    );
}
// priority 10, accepted_args 1 (sadece order_id alıyor)
add_action( 'woocommerce_order_status_completed', 'siparis_tamamlandi_islemleri', 10, 1 );

Hata Ayıklama: Hook’ların Çalışıp Çalışmadığını Test Etme

Bir hook’un çalışıp çalışmadığını test etmek için basit ama etkili bir yöntem:

<?php
// Hook debug helper - sadece geliştirme ortamında kullan
function hook_debug_kontrol() {
    if ( ! WP_DEBUG ) {
        return;
    }

    error_log( '=== wp_head hook çalıştı ===' );
    error_log( 'Mevcut filtreler: ' . print_r(
        $GLOBALS['wp_filter']['wp_head'] ?? 'bulunamadı',
        true
    ) );
}
add_action( 'wp_head', 'hook_debug_kontrol', 0 );

// Belirli bir hook'a kaç callback bağlı olduğunu kontrol et
function hook_sayisi_goster( $hook_adi ) {
    global $wp_filter;

    if ( isset( $wp_filter[$hook_adi] ) ) {
        $toplam = 0;
        foreach ( $wp_filter[$hook_adi]->callbacks as $priority => $callbacks ) {
            $toplam += count( $callbacks );
            error_log( "Priority $priority: " . count( $callbacks ) . " callback" );
        }
        error_log( "Toplam '$hook_adi' callback sayısı: $toplam" );
    }
}

// Admin bar görünürse çalıştır (sadece geliştirici için)
add_action( 'admin_init', function() {
    if ( current_user_can( 'manage_options' ) && isset( $_GET['debug_hook'] ) ) {
        hook_sayisi_goster( sanitize_text_field( $_GET['debug_hook'] ) );
    }
} );

Performans Açısından Hook Kullanımı

Her hook bağlantısı küçük de olsa bir performans maliyeti taşır. Özellikle the_content veya wp_head gibi sık çalışan hook’larda dikkatli olmak gerekir:

  • Koşullu kontroller ekleyin: Sadece belirli sayfa tiplerinde çalışması gereken hook’ları is_single(), is_shop(), is_admin() gibi kontroller ile sınırlayın.
  • Veritabanı sorgularını cache’leyin: Hook callback’leri içinde tekrar eden sorgular yerine transient kullanın.
  • Ağır işlemleri erteyin: shutdown action’ı veya arka plan işlemleri için WP Cron’u tercih edin.
<?php
// Sadece tekil yazılarda çalışan, cache'li bir hook örneği
function ozel_yazi_meta_goster() {
    // Tekil yazı değilse çıkış
    if ( ! is_single() ) {
        return;
    }

    $post_id   = get_the_ID();
    $cache_key = 'ozel_meta_' . $post_id;

    // Cache'de varsa onu kullan
    $meta_data = get_transient( $cache_key );

    if ( false === $meta_data ) {
        // Ağır bir işlem simülasyonu
        $meta_data = get_post_meta( $post_id, 'ozel_veriler', true );
        // 1 saatliğine cache'le
        set_transient( $cache_key, $meta_data, HOUR_IN_SECONDS );
    }

    if ( $meta_data ) {
        echo '<div class="ozel-meta">' . esc_html( $meta_data ) . '</div>';
    }
}
add_action( 'the_post', 'ozel_yazi_meta_goster' );

Sonuç

add_action ve add_filter, WordPress geliştirmenin kalbinde yer alır. Bu iki fonksiyonu gerçekten anladığınızda, bir WordPress sitesini neredeyse sınırsız şekilde özelleştirebilirsiniz.

Pratik olarak aklınızda tutmanız gereken ana noktalar:

  • Action’lar bir şey yapar, filter’lar bir şeyi değiştirir ve döndürür. Filter’da return yazmayı unutmak en yaygın hatadır.
  • Priority düşükse erken çalışır, yüksekse geç çalışır. Override işlemleri için yüksek priority kullanın.
  • accepted_args’ı doğru belirtin. İkinci veya üçüncü argümana ihtiyacınız varsa bu parametreyi mutlaka güncelleyin.
  • remove_action/remove_filter kullanırken priority’yi eşleştirin. Aksi halde kaldırma işlemi çalışmaz.
  • Anonim fonksiyonlar pratiktir ama kaldırılamaz. Eklenti veya ortak kullanılan kod için isimlendirilmiş fonksiyonları tercih edin.
  • Performansı göz önünde bulundurun. Koşullu kontroller ve cache mekanizmalarıyla hook callback’lerinizi hafif tutun.

Hook sistemi, WordPress’i bu kadar esnek ve geniş bir ekosisteme sahip yapan temel unsurdur. Bu sistemi iyi öğrenmek, hem başkalarının kodunu anlamayı hem de kendi çözümlerinizi üretmeyi çok daha kolay hale getirir.

Bir yanıt yazın

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