WordPress Özel Hook Tanımlama: do_action Kullanımı

WordPress geliştirme dünyasında bir noktadan sonra hazır hook’lar yetmez olmaya başlar. Temanın belirli bir noktasında özel bir işlem tetiklemek, eklentiler arasında iletişim kurmak ya da functions.php’yi daha modüler hale getirmek istediğinde do_action() devreye girer. Bu yazıda özel hook tanımlamanın ne anlama geldiğini, nasıl çalıştığını ve gerçek dünya senaryolarında nasıl kullanıldığını ele alacağız.

do_action Nedir ve Neden Kullanılır?

WordPress’in hook sistemi iki ana bileşenden oluşur: action hook’lar ve filter hook’lar. do_action() fonksiyonu, bir action hook’u tetikleyen fonksiyondur. Yani sen bir noktada “burada bir şey olabilir” diye işaret koyarsın, başka biri de add_action() ile o noktaya kendi kodunu bağlar.

WordPress core’un kendi hook’ları zaten binlerce nokta sunar. Ancak özel tema geliştirme, müşteriye özel eklentiler ya da karmaşık WooCommerce yapılandırmaları söz konusu olduğunda kendi hook’larını tanımlamak hem esneklik hem de sürdürülebilirlik açısından büyük fark yaratır.

Temel mantık şu şekilde çalışır:

  • Bir yerde do_action('hook_adi') çağırırsın
  • Başka bir yerde add_action('hook_adi', 'fonksiyon_adi') ile o hook’a bağlanırsın
  • WordPress o noktaya geldiğinde kayıtlı tüm fonksiyonları sırayla çalıştırır

Bu yapının güzelliği şuradan gelir: theme’i ve eklentileri birbirinden bağımsız tutabilirsin. Birini değiştirdiğinde diğerinin bozulma riski azalır.

Temel Sözdizimi

// Hook'u tanımlayan taraf (tema veya eklenti)
do_action( string $hook_name, mixed $arg1, mixed $arg2, ... );

// Hook'a bağlanan taraf
add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1 );

Parametreler:

  • $hook_name: Hook’un benzersiz adı. Benzersiz olmasına dikkat et, çakışma sıkıntı çıkarır
  • $arg1, $arg2…: Hook’a geçirmek istediğin ek veriler
  • $callback: Hook tetiklendiğinde çalışacak fonksiyon
  • $priority: Hangi öncelikle çalışacağı. Küçük sayı önce çalışır, varsayılan 10
  • $accepted_args: Callback fonksiyonun kaç parametre kabul ettiği

İlk Özel Hook Örneği: Sayfa Başlığından Sonra

En basit kullanım senaryosundan başlayalım. Diyelim ki temanın header.php dosyasında sayfa başlığından hemen sonra bir hook noktası oluşturmak istiyorsun:

<?php
// header.php içinde
?>
<header class="site-header">
    <div class="container">
        <h1 class="site-title"><?php echo get_bloginfo('name'); ?></h1>
        
        <?php
        // Özel hook noktası tanımlıyoruz
        do_action('tema_header_sonrasi');
        ?>
        
        <nav class="main-navigation">
            <?php wp_nav_menu(array('theme_location' => 'primary')); ?>
        </nav>
    </div>
</header>

Şimdi functions.php’de bu hook’a bir şey bağlayalım:

<?php
// functions.php içinde
function tema_header_duyuru_ekle() {
    $duyuru = get_option('site_duyuru_metni', '');
    
    if (!empty($duyuru)) {
        echo '<div class="header-duyuru">' . esc_html($duyuru) . '</div>';
    }
}
add_action('tema_header_sonrasi', 'tema_header_duyuru_ekle', 10);

Bu yaklaşımın avantajı şu: duyuruyu kaldırmak istediğinde header.php’ye dokunmana gerek yok. Sadece add_action satırını kaldır ya da yorum satırı yap.

Parametreli Hook Tanımlama

Gerçek gücü parametrelerle gönderince ortaya çıkıyor. Özellikle WooCommerce ürün sayfalarında ya da kullanıcı işlemlerinde hook’a bağlam bilgisi geçirmek kritik önem taşır.

<?php
// Sipariş tamamlandıktan sonra özel hook tetikleme
function ozel_siparis_tamamlandi_hook($siparis_id) {
    $siparis = wc_get_order($siparis_id);
    $musteri_id = $siparis->get_customer_id();
    $toplam_tutar = $siparis->get_total();
    
    // Hook'u sipariş ve müşteri bilgileriyle tetikle
    do_action('tema_siparis_tamamlandi', $siparis_id, $musteri_id, $toplam_tutar, $siparis);
}
add_action('woocommerce_order_status_completed', 'ozel_siparis_tamamlandi_hook', 10, 1);

Bu hook’a bağlanan farklı fonksiyonlar yazalım:

<?php
// Müşteriye özel indirim kuponu oluştur
function siparis_sonrasi_kupon_olustur($siparis_id, $musteri_id, $tutar) {
    // Sadece belirli tutarın üzerindeki siparişler için
    if ($tutar < 500) {
        return;
    }
    
    $kupon_kodu = 'TESEKKUR-' . strtoupper(wp_generate_password(6, false));
    
    $kupon = array(
        'post_title'   => $kupon_kodu,
        'post_status'  => 'publish',
        'post_author'  => 1,
        'post_type'    => 'shop_coupon',
    );
    
    $kupon_id = wp_insert_post($kupon);
    
    update_post_meta($kupon_id, 'discount_type', 'percent');
    update_post_meta($kupon_id, 'coupon_amount', '10');
    update_post_meta($kupon_id, 'customer_email', array(
        get_userdata($musteri_id)->user_email
    ));
    update_post_meta($kupon_id, 'expiry_date', date('Y-m-d', strtotime('+30 days')));
    
    // Kuponu sipariş meta'sına kaydet
    update_post_meta($siparis_id, '_hediye_kupon_kodu', $kupon_kodu);
}
// Dikkat: accepted_args parametresi 3 çünkü 3 parametre kullanıyoruz
add_action('tema_siparis_tamamlandi', 'siparis_sonrasi_kupon_olustur', 10, 3);

// Loglama için başka bir fonksiyon aynı hook'a bağlansın
function siparis_log_kaydet($siparis_id, $musteri_id, $tutar) {
    $log = sprintf(
        '[%s] Siparis tamamlandi - ID: %d, Musteri: %d, Tutar: %.2f TL',
        date('Y-m-d H:i:s'),
        $siparis_id,
        $musteri_id,
        $tutar
    );
    
    error_log($log);
}
add_action('tema_siparis_tamamlandi', 'siparis_log_kaydet', 20, 3);

Burada iki farklı fonksiyon aynı hook’a farklı önceliklerle bağlandı. Kupon oluşturma önce (10), loglama sonra (20) çalışır.

Eklenti ile Tema Arasında İletişim Kurma

Gerçek dünyada en sık karşılaşılan senaryo budur. Özel bir eklenti yazdın, temanın belirli noktalarında işlem yapması gerekiyor ama eklentiyi temaya bağımlı hale getirmek istemiyorsun.

<?php
// Temanın single.php dosyasında
get_header();
?>

<main class="content">
    <?php
    while (have_posts()) :
        the_post();
        
        // İçerik öncesi hook
        do_action('tema_icerik_oncesi', get_the_ID(), get_post_type());
        ?>
        
        <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
            <h1><?php the_title(); ?></h1>
            
            <?php
            // İçerik içi üst hook
            do_action('tema_makale_ust', get_the_ID());
            ?>
            
            <div class="entry-content">
                <?php the_content(); ?>
            </div>
            
            <?php
            // İçerik alt hook
            do_action('tema_makale_alt', get_the_ID());
            ?>
        </article>
        
        <?php
        // İçerik sonrası hook
        do_action('tema_icerik_sonrasi', get_the_ID(), get_post_type());
    
    endwhile;
    ?>
</main>

<?php get_footer(); ?>

Şimdi bu hook’ları kullanan eklenti kodunu yazalım:

<?php
/*
Plugin Name: Ozel Icerik Eklentisi
Description: Tema hook'larini kullanan ornek eklenti
*/

class OzelIcerikEklentisi {
    
    public function __construct() {
        add_action('tema_makale_ust', array($this, 'okuma_suresi_goster'));
        add_action('tema_makale_alt', array($this, 'yazar_kutusu_goster'));
        add_action('tema_icerik_sonrasi', array($this, 'ilgili_icerikler'), 10, 2);
    }
    
    public function okuma_suresi_goster($post_id) {
        $icerik = get_post_field('post_content', $post_id);
        $kelime_sayisi = str_word_count(strip_tags($icerik));
        $dakika = ceil($kelime_sayisi / 200);
        
        echo '<div class="okuma-suresi">';
        echo '<span class="icon">&#9201;</span>';
        printf('%d dakika okuma süresi', $dakika);
        echo '</div>';
    }
    
    public function yazar_kutusu_goster($post_id) {
        $yazar_id = get_post_field('post_author', $post_id);
        $yazar_adi = get_the_author_meta('display_name', $yazar_id);
        $yazar_biyografi = get_the_author_meta('description', $yazar_id);
        
        if (empty($yazar_biyografi)) {
            return;
        }
        
        echo '<div class="yazar-kutusu">';
        echo get_avatar($yazar_id, 80);
        echo '<div class="yazar-bilgi">';
        echo '<h4>' . esc_html($yazar_adi) . '</h4>';
        echo '<p>' . esc_html($yazar_biyografi) . '</p>';
        echo '</div>';
        echo '</div>';
    }
    
    public function ilgili_icerikler($post_id, $post_type) {
        // Sadece makale tipinde çalışsın
        if ($post_type !== 'post') {
            return;
        }
        
        $kategoriler = wp_get_post_categories($post_id);
        
        if (empty($kategoriler)) {
            return;
        }
        
        $sorgu = new WP_Query(array(
            'post_type'      => 'post',
            'posts_per_page' => 3,
            'post__not_in'   => array($post_id),
            'category__in'   => $kategoriler,
            'orderby'        => 'rand',
        ));
        
        if ($sorgu->have_posts()) {
            echo '<div class="ilgili-yazilar"><h3>İlgili Yazılar</h3><ul>';
            while ($sorgu->have_posts()) {
                $sorgu->the_post();
                echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
            }
            echo '</ul></div>';
            wp_reset_postdata();
        }
    }
}

new OzelIcerikEklentisi();

Eklenti ve tema tamamen birbirinden bağımsız. Temayı değiştirirsen eklenti çalışmayı durdurur ama hata vermez. Yeni temaya hook’ları eklediğinde eklenti tekrar devreye girer.

do_action_ref_array ile Referans Geçirme

Bazı senaryolarda büyük veri yapılarını birden fazla hook’a geçirmek gerekir ve referans kullanmak performans açısından mantıklı olabilir:

<?php
// Büyük veri setini hook'lardan geçirme
function urun_veri_isle($urun_id) {
    $urun_verisi = array(
        'id'         => $urun_id,
        'stok'       => get_post_meta($urun_id, '_stock', true),
        'fiyat'      => get_post_meta($urun_id, '_price', true),
        'meta_cache' => array(),
        'log'        => array(),
    );
    
    // Referans olarak geç - büyük dizilerde bellek tasarrufu sağlar
    do_action_ref_array('tema_urun_veri_hazir', array(&$urun_verisi, $urun_id));
    
    // Hook'lardan geçtikten sonra değişmiş olabilir
    return $urun_verisi;
}

// Bu hook'a bağlanan fonksiyon veriyi değiştirebilir
function urun_verisine_ek_bilgi_ekle(&$veri, $urun_id) {
    $veri['meta_cache']['kategori'] = wp_get_post_terms($urun_id, 'product_cat', array('fields' => 'names'));
    $veri['log'][] = 'Kategori bilgisi eklendi: ' . date('H:i:s');
}
add_action('tema_urun_veri_hazir', 'urun_verisine_ek_bilgi_ekle', 10, 2);

Koşullu Hook Yapısı: Kullanıcı Rolüne Göre

Gerçek projelerde hook’ların her zaman koşulsuz tetiklenmesi gerekmez. Kullanıcı rolüne, sayfa tipine ya da eklenti durumuna göre farklı hook noktaları oluşturabilirsin:

<?php
// Kullanıcı rolüne göre farklı hook tetikle
function kullanici_paneli_icerigi() {
    $kullanici = wp_get_current_user();
    
    if (!is_user_logged_in()) {
        do_action('tema_misafir_paneli');
        return;
    }
    
    // Temel hook her giriş yapmış kullanıcı için
    do_action('tema_kullanici_paneli', $kullanici->ID);
    
    // Role özel hook'lar
    if (in_array('administrator', $kullanici->roles)) {
        do_action('tema_admin_paneli', $kullanici->ID);
    }
    
    if (in_array('shop_manager', $kullanici->roles) || in_array('administrator', $kullanici->roles)) {
        do_action('tema_magaza_yonetici_paneli', $kullanici->ID);
    }
    
    // WooCommerce müşterisi mi?
    if (wc_customer_bought_product($kullanici->user_email, $kullanici->ID, 0)) {
        do_action('tema_musteri_paneli', $kullanici->ID);
    }
}

// Kullanım örnekleri
add_action('tema_misafir_paneli', function() {
    echo '<div class="giris-davet"><a href="' . wp_login_url() . '">Giriş Yap</a></div>';
});

add_action('tema_admin_paneli', function($kullanici_id) {
    $bekleyen_yorumlar = wp_count_comments()->moderated;
    if ($bekleyen_yorumlar > 0) {
        echo '<div class="admin-uyari">Onay bekleyen ' . $bekleyen_yorumlar . ' yorum var.</div>';
    }
}, 10, 1);

Hook Varlığını Kontrol Etme ve has_action Kullanımı

Özellikle eklenti geliştirirken bir hook’a herhangi bir şeyin bağlı olup olmadığını kontrol etmek önemlidir:

<?php
function tema_sidebar_goster() {
    // Bu hook'a herhangi bir şey bağlı mı?
    if (has_action('tema_sidebar_widget_alani')) {
        echo '<aside class="sidebar">';
        do_action('tema_sidebar_widget_alani');
        echo '</aside>';
    } else {
        // Hook'a hiçbir şey bağlı değilse varsayılan sidebar
        echo '<aside class="sidebar">';
        if (is_active_sidebar('primary-sidebar')) {
            dynamic_sidebar('primary-sidebar');
        }
        echo '</aside>';
    }
}

// Hook kaçıncı öncelikle kaydedilmiş, kontrol et
function hook_oncelik_kontrol($hook_adi, $fonksiyon_adi) {
    $oncelik = has_action($hook_adi, $fonksiyon_adi);
    
    if ($oncelik === false) {
        return 'Fonksiyon bu hook'a kayıtlı değil';
    }
    
    return sprintf('Fonksiyon %d önceliğiyle kayıtlı', $oncelik);
}

// Hook'u sonradan kaldırma
function kosullu_hook_kaldir() {
    if (is_page('iletisim')) {
        remove_action('tema_sayfa_alt', 'yorum_formu_goster');
    }
}
add_action('wp', 'kosullu_hook_kaldir');

Gerçek Dünya Senaryosu: E-ticaret Bildirim Sistemi

Son olarak, WooCommerce’de gerçekten işe yarar bir senaryo gösterelim. Farklı kanallardan bildirim göndermek istiyorsun: email, SMS, admin paneli bildirimi. Her kanal bağımsız olmalı ve istersen birini kapatabilmelisin.

<?php
// Ana bildirim orkestratörü
class BildirimOrkestratoru {
    
    public function __construct() {
        // Sipariş durumu değişikliklerinde hook'u tetikle
        add_action('woocommerce_order_status_changed', array($this, 'durum_degisikligi_isleme'), 10, 4);
    }
    
    public function durum_degisikligi_isleme($siparis_id, $eski_durum, $yeni_durum, $siparis) {
        $bildirim_verisi = array(
            'siparis_id'    => $siparis_id,
            'eski_durum'    => $eski_durum,
            'yeni_durum'    => $yeni_durum,
            'musteri_email' => $siparis->get_billing_email(),
            'musteri_adi'   => $siparis->get_billing_first_name(),
            'tutar'         => $siparis->get_total(),
            'zaman'         => current_time('mysql'),
        );
        
        // Genel durum değişikliği hook'u
        do_action('tema_siparis_durum_degisti', $bildirim_verisi);
        
        // Duruma özel hook'lar
        $gecerli_durumlar = array('processing', 'completed', 'cancelled', 'refunded', 'on-hold');
        
        if (in_array($yeni_durum, $gecerli_durumlar)) {
            $ozel_hook = 'tema_siparis_' . str_replace('-', '_', $yeni_durum);
            do_action($ozel_hook, $bildirim_verisi);
        }
    }
}

new BildirimOrkestratoru();

// Email bildirim modülü
class EmailBildirimModulu {
    
    public function __construct() {
        add_action('tema_siparis_completed', array($this, 'tamamlandi_emaili_gonder'), 10, 1);
        add_action('tema_siparis_cancelled', array($this, 'iptal_emaili_gonder'), 10, 1);
    }
    
    public function tamamlandi_emaili_gonder($veri) {
        $konu = 'Siparişiniz tamamlandı - #' . $veri['siparis_id'];
        $mesaj = sprintf(
            'Merhaba %s, %s TL tutarındaki siparişiniz teslim edildi.',
            $veri['musteri_adi'],
            $veri['tutar']
        );
        
        wp_mail($veri['musteri_email'], $konu, $mesaj);
    }
    
    public function iptal_emaili_gonder($veri) {
        $konu = 'Siparişiniz iptal edildi - #' . $veri['siparis_id'];
        $mesaj = sprintf(
            'Merhaba %s, siparişiniz maalesef iptal edilmiştir.',
            $veri['musteri_adi']
        );
        
        wp_mail($veri['musteri_email'], $konu, $mesaj);
    }
}

// Admin panel bildirim modülü
class AdminBildirimModulu {
    
    public function __construct() {
        add_action('tema_siparis_durum_degisti', array($this, 'admin_bildirimi_kaydet'), 10, 1);
    }
    
    public function admin_bildirimi_kaydet($veri) {
        $mevcut_bildirimler = get_option('admin_siparis_bildirimleri', array());
        
        $mevcut_bildirimler[] = array(
            'mesaj' => sprintf(
                'Sipariş #%d durumu %s -> %s olarak değişti',
                $veri['siparis_id'],
                $veri['eski_durum'],
                $veri['yeni_durum']
            ),
            'zaman' => $veri['zaman'],
            'okundu' => false,
        );
        
        // Sadece son 50 bildirimi tut
        if (count($mevcut_bildirimler) > 50) {
            $mevcut_bildirimler = array_slice($mevcut_bildirimler, -50);
        }
        
        update_option('admin_siparis_bildirimleri', $mevcut_bildirimler);
    }
}

new EmailBildirimModulu();
new AdminBildirimModulu();

Bu yapıda SMS modülü eklemek istersen sadece yeni bir sınıf oluşturup hook’a bağlarsın. Mevcut koda hiç dokunmazsın.

Hook İsimlendirme Kuralları

Özel hook isimlerinde bazı pratik kurallar işleri kolaylaştırır:

  • Prefix kullan: tema_, eklenti_adi_, sirket_ gibi prefix’ler çakışmayı önler
  • Anlamlı isimler: tema_makale_sonrasi açık, hook1 anlamsız
  • Alt çizgi kullan: tema_urun_eklendi okunabilir, temauruneklendi değil
  • Fiil ile bitir: _oncesi, _sonrasi, _gonderildi, _baslatildi kalıpları hook’un zamanını belirtir
  • Grupla: İlgili hook’ları aynı prefix altında topla: magaza_siparis_oncesi, magaza_siparis_sonrasi

Performans Notları

Hook sistemi WordPress’in kalbi olsa da yanlış kullanım sorun çıkarabilir:

  • Döngü içinde do_action çağırmak kaçınılmaz olabilir ama mümkünse döngü dışına al
  • Her hook’a bağlanan fonksiyon veritabanı sorgusu yapıyorsa önbelleğe alma mekanizması ekle
  • do_action ve add_action çağrılarını hata ayıklamak için Query Monitor eklentisi biçilmiş kaftan
  • Asla kullanılmayan hook’ları canlı sistemde bırakma, performansı etkilemese de kodu kirletir

Sonuç

do_action() ile özel hook tanımlamak, WordPress geliştirmede bir olgunluk adımıdır. Temayı ve eklentileri birbirinden koparır, kodun test edilebilirliğini artırır ve müşteri isteklerine esnek şekilde yanıt vermeni sağlar.

Yazı boyunca gördüğümüz gibi temel kullanımdan karmaşık bildirim sistemlerine kadar her senaryoda bu pattern işe yarar. Özellikle büyük projelerde “bunu nereye yazsam” sorusunun cevabı çoğu zaman yeni bir hook noktası tanımlamak olur.

Gerçek projede uygulamaya başlarken küçük adımlarla git: önce tek bir hook noktası ekle, ona bir fonksiyon bağla, çalıştığını doğrula. Sistem oturdukça daha karmaşık yapılara geçebilirsin. Hook sistemi bir kez içselleşince WordPress’e bakış açın tamamen değişiyor.

Bir yanıt yazın

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