Custom Taxonomy ile WordPress Sınıflandırma Sistemi Geliştirme
WordPress’i kutudan çıktığı haliyle kullananlar için “kategori” ve “etiket” yeterli görünür. Ama bir noktadan sonra bu iki şeyin yetersiz kaldığını fark edersiniz; özellikle de özel içerik tipleriyle (custom post type) çalışmaya başladığınızda. İşte tam o anda custom taxonomy devreye giriyor ve içerik sınıflandırma oyununu tamamen değiştiriyor.
Ben bir e-ticaret sitesi projesiyle uğraşırken bu ihtiyacı fark ettim. WooCommerce’in kendi ürün kategorisi yapısı vardı ama biz aynı zamanda ürünleri “marka”, “malzeme tipi” ve “sertifikasyon durumu” gibi birbirinden bağımsız boyutlarda sınıflandırmamız gerekiyordu. Hiyerarşik mi hiyerarşik değil mi, public mi private mi… Bütün bu kararları vermek zorunda kaldık. O süreçten öğrendiklerimi bu yazıda paylaşacağım.
Custom Taxonomy Nedir ve Ne Zaman Gerekir?
Taxonomy, WordPress’in içerikleri gruplamak için kullandığı sınıflandırma mekanizmasıdır. WordPress çekirdekte iki taxonomy zaten gelir: category (hiyerarşik) ve post_tag (hiyerarşik değil). Custom taxonomy ise bunların yanına kendi ihtiyaçlarınıza göre eklediğiniz özel sınıflandırma sistemleridir.
Ne zaman ihtiyaç duyarsınız?
- Bir film arşivi sitesinde yazıları “tür”, “yönetmen” ve “ülke” olarak ayrı ayrı sınıflandırmak istediğinizde
- Bir haber sitesinde makalelere “kaynak” taxonomy’si eklemek istediğinizde
- WooCommerce ürünlerini “renk”, “materyal” veya “koleksiyon” gibi özel gruplamalarla organize etmek istediğinizde
- Bir iş ilanı sisteminde pozisyonları “sektör”, “çalışma modeli” ve “deneyim seviyesi” ile filtrelemek istediğinizde
Buradaki kritik nokta şu: Eğer sınıflandırmanız birden fazla boyut içeriyorsa ve bu boyutlar birbirinden bağımsız anlam taşıyorsa, ayrı taxonomy’ler kullanmalısınız. Hepsini kategoriye tıkmak ileride veri modelinizi karmaşık bir hale getirir.
register_taxonomy() Fonksiyonu ile Temel Kayıt
Her şey register_taxonomy() fonksiyonuyla başlar. Bu fonksiyon init hook’una bağlanarak çağrılmalıdır. Doğrudan tema dosyalarına yazmak yerine bunu her zaman bir eklenti içinde yapın; çünkü tema değiştiğinde taxonomy tanımlarınız da gider.
<?php
/**
* Plugin Name: Özel Sınıflandırma Sistemi
* Description: Siteye özel taxonomy yapısı ekler.
* Version: 1.0.0
*/
add_action( 'init', 'ozs_taxonomy_kaydet' );
function ozs_taxonomy_kaydet() {
// Marka taxonomy'si (hiyerarşik - kategori gibi)
$marka_etiketler = array(
'name' => 'Markalar',
'singular_name' => 'Marka',
'search_items' => 'Marka Ara',
'all_items' => 'Tüm Markalar',
'parent_item' => 'Üst Marka',
'parent_item_colon' => 'Üst Marka:',
'edit_item' => 'Markayı Düzenle',
'update_item' => 'Markayı Güncelle',
'add_new_item' => 'Yeni Marka Ekle',
'new_item_name' => 'Yeni Marka Adı',
'menu_name' => 'Markalar',
);
$marka_args = array(
'hierarchical' => true,
'labels' => $marka_etiketler,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'marka' ),
'show_in_rest' => true,
);
register_taxonomy( 'marka', array( 'product', 'post' ), $marka_args );
}
Burada dikkat etmeniz gereken birkaç parametre var:
- hierarchical:
trueolunca kategori gibi davranır, üst-alt ilişkisi kurabilirsiniz.falseolunca etiket gibi davranır. - show_admin_column: Yazı listesi ekranında taxonomy sütunu gösterir, gerçekten işe yarıyor.
- show_in_rest: Gutenberg ve REST API erişimi için şart. Bunu unutmayın.
- rewrite: URL yapısını belirler. Çoklu dil sitelerinde bu slug’ı dikkatlice seçin.
- query_var:
WP_Queryiçinde bu taxonomy adıyla sorgulama yapmanızı sağlar.
Hiyerarşik ve Düz Taxonomy Farkı
Bu ayrımı anlamadan doğru taxonomy tasarımı yapamazsınız. Pratik bir örnek üzerinden gidelim.
Bir teknoloji blogu yönetiyorsunuz. Makalelerinizi “işletim sistemi” ve “konu etiketleri” ile sınıflandırmak istiyorsunuz.
“İşletim sistemi” için hiyerarşik yapı mantıklıdır: Linux > Ubuntu > Ubuntu 22.04 gibi bir ağaç kurabilirsiniz. “Konu etiketleri” içinse hiyerarşi anlamsız olur; “performans”, “güvenlik”, “izleme” gibi düz bir liste yeterlidir.
<?php
add_action( 'init', 'tech_blog_taxonomies' );
function tech_blog_taxonomies() {
// İşletim Sistemi - hiyerarşik
register_taxonomy(
'isletim_sistemi',
'post',
array(
'hierarchical' => true,
'label' => 'İşletim Sistemleri',
'rewrite' => array( 'slug' => 'os' ),
'show_in_rest' => true,
)
);
// Konu Etiketleri - düz (flat)
register_taxonomy(
'konu_etiketi',
'post',
array(
'hierarchical' => false,
'label' => 'Konu Etiketleri',
'rewrite' => array( 'slug' => 'konu' ),
'show_in_rest' => true,
)
);
}
Burada önemli bir detay: Eğer bir taxonomy’yi birden fazla post type’a bağlayacaksanız, sonradan register_taxonomy_for_object_type() fonksiyonunu kullanabilirsiniz. Bu özellikle farklı eklentiler arası entegrasyonda işe yarar.
<?php
// Sonradan başka bir post type'a bağlama
add_action( 'init', 'taxonomy_baglanti_ekle', 20 );
function taxonomy_baglanti_ekle() {
register_taxonomy_for_object_type( 'isletim_sistemi', 'tutorial' );
}
WP_Query ile Taxonomy Sorguları
Taxonomy’leri tanımlamak yeterli değil; onları sorgulama bilgisi de kritik. Özellikle karmaşık filtreleme senaryolarında tax_query parametresi çok güçlü bir araç haline geliyor.
<?php
// Tek taxonomy, tek term sorgusu
$args = array(
'post_type' => 'product',
'tax_query' => array(
array(
'taxonomy' => 'marka',
'field' => 'slug',
'terms' => 'samsung',
),
),
);
$query = new WP_Query( $args );
Birden fazla taxonomy’yi aynı anda sorgulamak istediğinizde işler biraz karmaşıklaşıyor. Müşterinin hem “Samsung” markasından hem de “Akıllı Telefon” kategorisinden ürünler istediği bir filtreleme senaryosu düşünün:
<?php
// Çoklu taxonomy AND ilişkisiyle
$args = array(
'post_type' => 'product',
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'marka',
'field' => 'slug',
'terms' => array( 'samsung', 'apple' ),
'operator' => 'IN',
),
array(
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => array( 12, 15 ),
'operator' => 'IN',
),
),
);
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
echo '<h2>' . get_the_title() . '</h2>';
}
wp_reset_postdata();
}
operator parametresi için seçenekler:
- IN: Belirtilen term’lerden herhangi birinde olanları getirir
- NOT IN: Belirtilen term’lerde olmayanları getirir
- AND: Belirtilen tüm term’lerde aynı anda olanları getirir
- EXISTS: O taxonomy’ye atanmış herhangi bir term’i olan yazıları getirir
- NOT EXISTS: O taxonomy’ye hiç atanmamış yazıları getirir
Term Meta ile Taxonomy’yi Güçlendirme
Bir taxonomy term’ine sadece ad ve açıklama eklemek yetmez bazen. Örneğin “Marka” taxonomy’nize logo URL’si, resmi web sitesi adresi veya kuruluş yılı gibi ek bilgiler eklemek isteyebilirsiniz. Term meta bunun için var.
<?php
// Term kayıt/güncelleme sırasında meta alanı kaydet
add_action( 'marka_add_form_fields', 'marka_meta_alanlari_ekle' );
add_action( 'marka_edit_form_fields', 'marka_meta_alanlari_duzenle', 10, 2 );
function marka_meta_alanlari_ekle( $taxonomy ) {
?>
<div class="form-field">
<label for="marka_website">Resmi Website</label>
<input type="url" name="marka_website" id="marka_website" value="">
<p>Markanın resmi web sitesi URL'si</p>
</div>
<?php
}
function marka_meta_alanlari_duzenle( $term, $taxonomy ) {
$website = get_term_meta( $term->term_id, 'marka_website', true );
?>
<tr class="form-field">
<th scope="row"><label for="marka_website">Resmi Website</label></th>
<td>
<input type="url" name="marka_website" id="marka_website"
value="<?php echo esc_url( $website ); ?>">
</td>
</tr>
<?php
}
// Meta alanını kaydetme işlemi
add_action( 'created_marka', 'marka_meta_kaydet' );
add_action( 'edited_marka', 'marka_meta_kaydet' );
function marka_meta_kaydet( $term_id ) {
if ( isset( $_POST['marka_website'] ) ) {
update_term_meta(
$term_id,
'marka_website',
esc_url_raw( $_POST['marka_website'] )
);
}
}
Bu yapıyı kullanırken bir projesinde başıma gelen bir sorunu paylaşayım. Term meta’yı sorgularda kullanmak istediğimizde performans ciddi bir sorun olabilir. Eğer büyük bir veritabanınız varsa, term meta üzerinden filtreleme yapmak yerine termmeta tablosuna doğrudan sorgu atmaktan kaçının; bunun yerine verileri önbelleğe alın ya da taxonomy yapınızı yeniden düşünün.
Gerçek Dünya Senaryosu: İş İlanı Sistemi
Teoriden pratiğe geçelim. Bir iş ilanı sitesi için taxonomy yapısı tasarlayacağız. Bu sistemde ilanları birden fazla boyutta sınıflandırmamız gerekiyor.
<?php
/**
* Plugin Name: İş İlanı Taxonomy Sistemi
* Version: 1.0.0
*/
add_action( 'init', 'is_ilani_taxonomies_kaydet' );
function is_ilani_taxonomies_kaydet() {
$post_types = array( 'is_ilani' );
// Sektör - hiyerarşik
register_taxonomy(
'sektor',
$post_types,
array(
'hierarchical' => true,
'labels' => array(
'name' => 'Sektörler',
'singular_name' => 'Sektör',
'add_new_item' => 'Yeni Sektör Ekle',
),
'show_ui' => true,
'show_admin_column' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'sektor' ),
)
);
// Çalışma Modeli - düz
register_taxonomy(
'calisma_modeli',
$post_types,
array(
'hierarchical' => false,
'labels' => array(
'name' => 'Çalışma Modelleri',
'singular_name' => 'Çalışma Modeli',
),
'show_ui' => true,
'show_admin_column' => true,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'calisma-modeli' ),
)
);
// Deneyim Seviyesi - hiyerarşik değil ama sıralı
register_taxonomy(
'deneyim_seviyesi',
$post_types,
array(
'hierarchical' => false,
'labels' => array(
'name' => 'Deneyim Seviyeleri',
'singular_name' => 'Deneyim Seviyesi',
),
'show_ui' => true,
'show_admin_column' => false,
'show_in_rest' => true,
'rewrite' => array( 'slug' => 'deneyim' ),
)
);
}
Bu sistemde başlangıçta “deneyim_seviyesi” taxonomy’sini de hiyerarşik yaptım ama sonradan geri aldım. Çünkü “Junior > Mid-Level > Senior” gibi görünen bir hiyerarşi aslında doğrusal bir skala, ağaç değil. Bu tip seviyeleri ayrı term’ler olarak düz taxonomy’de tutmak çok daha doğru.
Taxonomy Arşiv Sayfalarını Özelleştirme
Custom taxonomy oluşturdunuzda WordPress otomatik olarak bir arşiv sayfası URL’i oluşturur. Ama bu sayfaların şablonunu kontrol etmek için tema dosyalarına veya template_include filter’ına ihtiyacınız var.
<?php
// Taxonomy şablonunu programatik kontrol etme
add_filter( 'template_include', 'ozs_taxonomy_template' );
function ozs_taxonomy_template( $template ) {
if ( is_tax( 'marka' ) ) {
$ozel_template = plugin_dir_path( __FILE__ ) . 'templates/taxonomy-marka.php';
if ( file_exists( $ozel_template ) ) {
return $ozel_template;
}
}
if ( is_tax( array( 'sektor', 'calisma_modeli' ) ) ) {
$ozel_template = plugin_dir_path( __FILE__ ) . 'templates/taxonomy-is-ilani.php';
if ( file_exists( $ozel_template ) ) {
return $ozel_template;
}
}
return $template;
}
Bu yaklaşımla tema bağımsız bir eklenti içinde şablon yönetimi yapabilirsiniz. Müşteri temayı değiştirdiğinde taxonomy arşiv sayfalarınız etkilenmez.
Taxonomy Terimlerini Programatik Ekleme ve Yönetme
Bazen içe aktarma işlemlerinde veya eklenti kurulumunda taxonomy term’lerini otomatik oluşturmanız gerekir. wp_insert_term() ve wp_set_object_terms() bu iş için temel fonksiyonlarınız olacak.
<?php
// Eklenti aktifleştirildiğinde varsayılan term'leri ekle
register_activation_hook( __FILE__, 'ozs_varsayilan_termleri_ekle' );
function ozs_varsayilan_termleri_ekle() {
$calisma_modelleri = array(
'Tam Zamanlı' => 'tam-zamanli',
'Yarı Zamanlı' => 'yari-zamanli',
'Uzaktan' => 'uzaktan',
'Hibrit' => 'hibrit',
'Freelance' => 'freelance',
'Staj' => 'staj',
);
foreach ( $calisma_modelleri as $ad => $slug ) {
if ( ! term_exists( $slug, 'calisma_modeli' ) ) {
wp_insert_term(
$ad,
'calisma_modeli',
array( 'slug' => $slug )
);
}
}
// İş ilanına taxonomy atama örneği
// wp_set_object_terms( $post_id, array( 'uzaktan', 'tam-zamanli' ), 'calisma_modeli' );
}
wp_set_object_terms() ile atama yaparken dikkat edin: dördüncü parametre $append. Bu false olursa mevcut tüm atamalar silinir ve sadece yeniler eklenir. true yaparsanız mevcutların üzerine ekler. Bunu yanlış kullanmak veri kaybına neden olur; çok kez başkasının kodunda bu hatayı gördüm.
Performans ve Önbellekleme
Taxonomy sorguları yoğun sitelerde gerçek bir darboğaz olabilir. Birkaç pratik öneri:
Transient ile term listesi önbellekleme:
<?php
function ozs_marka_listesini_al() {
$cache_key = 'ozs_marka_listesi_v1';
$markalar = get_transient( $cache_key );
if ( false === $markalar ) {
$markalar = get_terms( array(
'taxonomy' => 'marka',
'orderby' => 'name',
'order' => 'ASC',
'hide_empty' => false,
'fields' => 'id=>name',
) );
// Yeni marka eklendiğinde veya düzenlendiğinde cache temizle
set_transient( $cache_key, $markalar, 12 * HOUR_IN_SECONDS );
}
return $markalar;
}
// Cache temizleme hook'ları
add_action( 'created_marka', 'ozs_marka_cache_temizle' );
add_action( 'edited_marka', 'ozs_marka_cache_temizle' );
add_action( 'delete_marka', 'ozs_marka_cache_temizle' );
function ozs_marka_cache_temizle() {
delete_transient( 'ozs_marka_listesi_v1' );
}
Bu pattern özellikle dropdown menüler veya filtreleme arayüzleri için çok işe yarıyor. Her sayfa yüklemesinde aynı term listesini veritabanından çekmek gereksiz.
Bunun dışında şunu da söyleyeyim: get_terms() sorgularında fields parametresini kullanın. Eğer sadece ID’ye ihtiyacınız varsa 'fields' => 'ids' deyin, tüm nesneyi çekmeyin. Büyük taxonomy yapılarında bellek kullanımında ciddi fark yaratıyor.
Yaygın Hatalar ve Kaçınma Yolları
Birkaç yıllık deneyimde tekrar eden hatalara bakacak olursak:
Flush rewrite rules sorunu: Taxonomy kaydettikten sonra permalink’ler çalışmıyorsa, bir kez admin panel > Ayarlar > Kalıcı Bağlantılar sayfasına girip kaydet butonuna basmak sorunu çözer. Canlı ortamda bunu otomatik yapmak için:
<?php
register_activation_hook( __FILE__, 'ozs_rewrite_flush' );
function ozs_rewrite_flush() {
ozs_taxonomy_kaydet(); // taxonomy'yi önce kaydet
flush_rewrite_rules();
}
Taxonomy ismini rezerve kelimelerle çakıştırma: author, date, category, tag, type, feed, trackback, attachment, comments gibi isimler rezervedir, bunları taxonomy adı olarak kullanmayın. Bunu birden fazla projede gördüm, belirsiz 404 hatalarına neden oldu.
show_in_rest olmadan Gutenberg kullanmak: Eğer Gutenberg editörde taxonomy kutusunu görmek istiyorsanız show_in_rest => true zorunlu. Bu parametre olmadan blok editörde taxonomy paneli çıkmaz ve içerik editörleri çıldırır.
Sonuç
Custom taxonomy, WordPress’in en çok göz ardı edilen ama en güçlü özelliklerinden biri. İçerik mimarisini düzgün kurduğunuzda hem yönetici paneli daha kullanışlı hale geliyor hem de sorgularınız çok daha temiz ve performanslı oluyor.
Benim önerim şu: Bir proje başlarken içerik sınıflandırma ihtiyacını kağıda dökün. “Bu içerikler kaç farklı boyutta gruplandırılabilir?” sorusunu sorun. Her bağımsız boyut için ayrı bir taxonomy düşünün. Hiyerarşi gerçekten mantıklıysa hierarchical: true yapın, yoksa düz bırakın. Term meta’yı da abartmayın; gerçekten o alana ihtiyacınız varsa kullanın, her şeyi taxonomy’e doldurmaya çalışmayın.
Bu yapıyı doğru kurduğunuzda, altı ay sonra projeye döndüğünüzde veya başka bir geliştirici koda baktığında “bu ne yapmaya çalışıyor?” diye sormak zorunda kalmaz. Ve bu, iyi bir sistem tasarımının en önemli göstergesidir.
