WordPress’te Custom Field ile Post Meta Verisi Yönetimi
WordPress geliştirme dünyasında en çok göz ardı edilen ama aslında en güçlü araçlardan biri post meta verisidir. Bir ürünün garanti süresi, bir yazının okuma süresi, bir etkinliğin tarihi ya da bir müşterinin özel notları… Bunların hepsini WordPress’in yerleşik meta sistemiyle yönetebilirsin. Ancak bu sistemi doğru kullanmak, özellikle functions.php üzerinden custom field’ları düzgün entegre etmek biraz pratik gerektiriyor. Bu yazıda gerçek dünya senaryoları üzerinden post meta yönetimini enine boyuna ele alacağız.
Post Meta Sistemi Nasıl Çalışır?
WordPress, her post için wp_postmeta tablosunda anahtar-değer çiftleri saklar. post_id, meta_key ve meta_value sütunlarından oluşan bu tablo, inanılmaz derecede esnek bir yapı sunar. Sorun şu ki bu esneklik beraberinde bazı karmaşıklıklar getiriyor.
Temel fonksiyonlar şunlar:
- add_post_meta(): Yeni bir meta kaydı ekler, aynı anahtar için birden fazla kayıt oluşturabilir
- update_post_meta(): Varsa günceller, yoksa ekler, tek kayıt tutar
- get_post_meta(): Meta değerini getirir
- delete_post_meta(): Meta kaydını siler
Çoğu sysadmin bu dört fonksiyonu bilir ama aralarındaki farkları ve ne zaman hangisini kullanacağını tam kavrayamamış olabilir. Bunu birazdan örneklerle netleştireceğiz.
Temel CRUD İşlemleri
Önce temeli sağlam atalım. Aşağıdaki örnek, post meta ile yapabileceğin dört temel işlemi gösteriyor:
<?php
// Meta ekle (aynı key için birden fazla kayıt oluşur)
add_post_meta($post_id, 'garanti_suresi', '2 yil');
// Meta güncelle veya ekle (tek kayıt tutar, ÖNERİLEN)
update_post_meta($post_id, 'garanti_suresi', '3 yil');
// Meta oku
$garanti = get_post_meta($post_id, 'garanti_suresi', true);
// true = tek değer döner (string)
// false veya boş = dizi döner
// Meta sil
delete_post_meta($post_id, 'garanti_suresi');
// Belirli bir değere sahip kaydı sil
delete_post_meta($post_id, 'garanti_suresi', '3 yil');
?>
Dikkat etmen gereken önemli bir nokta: get_post_meta() fonksiyonunun üçüncü parametresi. true geçersen string alırsın, false veya boş bırakırsan dizi alırsın. Bunu karıştırınca kod beklenmedik şekilde davranır.
Custom Meta Box Oluşturma
Şimdi gerçek senaryoya geçelim. Bir e-ticaret sitesinde ürün sayfalarına “Teknik Özellikler” adında bir meta box eklemek istiyorsun. Bu box içinde garanti süresi, ağırlık ve üretim yeri bilgileri girecek.
<?php
// functions.php'ye ekle
function teknik_ozellikler_meta_box_ekle() {
add_meta_box(
'teknik_ozellikler', // Meta box ID
'Teknik Özellikler', // Başlık
'teknik_ozellikler_callback', // Callback fonksiyon
'product', // Post type (WooCommerce ürünü)
'normal', // Konum (normal, side, advanced)
'high' // Öncelik
);
}
add_action('add_meta_boxes', 'teknik_ozellikler_meta_box_ekle');
function teknik_ozellikler_callback($post) {
// Nonce güvenlik alanı - ZORUNLU
wp_nonce_field('teknik_ozellikler_nonce_action', 'teknik_ozellikler_nonce');
// Mevcut değerleri getir
$garanti = get_post_meta($post->ID, '_garanti_suresi', true);
$agirlik = get_post_meta($post->ID, '_urun_agirligi', true);
$uretim_yeri = get_post_meta($post->ID, '_uretim_yeri', true);
echo '<p>';
echo '<label for="garanti_suresi"><strong>Garanti Süresi:</strong></label><br>';
echo '<input type="text" id="garanti_suresi" name="garanti_suresi" value="' . esc_attr($garanti) . '" style="width:100%">';
echo '</p>';
echo '<p>';
echo '<label for="urun_agirligi"><strong>Ağırlık (kg):</strong></label><br>';
echo '<input type="number" id="urun_agirligi" name="urun_agirligi" value="' . esc_attr($agirlik) . '" step="0.01" style="width:100%">';
echo '</p>';
echo '<p>';
echo '<label for="uretim_yeri"><strong>Üretim Yeri:</strong></label><br>';
echo '<input type="text" id="uretim_yeri" name="uretim_yeri" value="' . esc_attr($uretim_yeri) . '" style="width:100%">';
echo '</p>';
}
?>
Meta box’ı gösterdik, şimdi kaydetmek gerekiyor. Bu kısım çok önemli çünkü güvenlik doğrulamalarını atlamak ciddi açık oluşturur:
<?php
function teknik_ozellikler_kaydet($post_id) {
// Nonce doğrulama
if (!isset($_POST['teknik_ozellikler_nonce'])) {
return;
}
if (!wp_verify_nonce($_POST['teknik_ozellikler_nonce'], 'teknik_ozellikler_nonce_action')) {
return;
}
// Otomatik kayıt kontrolü
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
// Yetki kontrolü
if (!current_user_can('edit_post', $post_id)) {
return;
}
// Verileri temizle ve kaydet
if (isset($_POST['garanti_suresi'])) {
update_post_meta(
$post_id,
'_garanti_suresi',
sanitize_text_field($_POST['garanti_suresi'])
);
}
if (isset($_POST['urun_agirligi'])) {
update_post_meta(
$post_id,
'_urun_agirligi',
floatval($_POST['urun_agirligi'])
);
}
if (isset($_POST['uretim_yeri'])) {
update_post_meta(
$post_id,
'_uretim_yeri',
sanitize_text_field($_POST['uretim_yeri'])
);
}
}
add_action('save_post', 'teknik_ozellikler_kaydet');
?>
Meta key’lerin başına alt çizgi _ koymak önemli bir konvansiyon. Bu şekilde WordPress’in varsayılan “Custom Fields” kutusunda bu alanlar görünmez ve kullanıcılar yanlışlıkla değiştiremiyor.
Meta Veriye Göre Sorgu Yapmak
Post meta’nın gerçek gücü sorgulamada ortaya çıkıyor. Diyelim ki garanti süresi 2 yıldan fazla olan tüm ürünleri listelemek istiyorsun:
<?php
function garantili_urunleri_getir($min_garanti_yil = 2) {
$args = array(
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => '_garanti_suresi',
'value' => $min_garanti_yil,
'compare' => '>=',
'type' => 'NUMERIC'
)
),
'meta_key' => '_garanti_suresi',
'orderby' => 'meta_value_num',
'order' => 'DESC'
);
$query = new WP_Query($args);
if ($query->have_posts()) {
$urunler = array();
while ($query->have_posts()) {
$query->the_post();
$urunler[] = array(
'id' => get_the_ID(),
'baslik' => get_the_title(),
'garanti' => get_post_meta(get_the_ID(), '_garanti_suresi', true)
);
}
wp_reset_postdata();
return $urunler;
}
return array();
}
// Kullanım
$garantili = garantili_urunleri_getir(3);
foreach ($garantili as $urun) {
echo $urun['baslik'] . ' - ' . $urun['garanti'] . ' yıl garanti<br>';
}
?>
meta_query ile yapabileceğin karşılaştırma tipleri:
- =: Eşittir
- !=: Eşit değil
- >: Büyüktür
- >=: Büyük eşittir
- <: Küçüktür
- <=: Küçük eşittir
- LIKE: Benzer (SQL LIKE)
- NOT LIKE: Benzer değil
- IN: Dizi içinde
- NOT IN: Dizi dışında
- BETWEEN: İki değer arasında
- EXISTS: Anahtar mevcut
- NOT EXISTS: Anahtar mevcut değil
Çoklu Meta Değerleri Yönetimi
Bazen bir ürünün birden fazla kategoride fiyatı olabilir ya da bir yazının birden fazla yazarı. İşte burada add_post_meta() devreye giriyor:
<?php
// Aynı key için birden fazla değer ekleme
function coklu_yazar_ekle($post_id, $yazar_adi) {
// Önce bu yazarın zaten eklenip eklenmediğini kontrol et
$mevcut_yazarlar = get_post_meta($post_id, '_ek_yazar');
// false geçince dizi döner
if (!in_array($yazar_adi, $mevcut_yazarlar)) {
add_post_meta($post_id, '_ek_yazar', sanitize_text_field($yazar_adi));
}
}
// Tüm ek yazarları getir
function tum_yazarlari_getir($post_id) {
return get_post_meta($post_id, '_ek_yazar');
// Üçüncü parametre false olduğundan dizi döner
}
// Belirli bir yazarı sil
function yazar_sil($post_id, $yazar_adi) {
delete_post_meta($post_id, '_ek_yazar', $yazar_adi);
}
// Tüm ek yazarları sil
function tum_yazarlari_sil($post_id) {
delete_post_meta($post_id, '_ek_yazar');
}
?>
Seri Meta Verisi Saklama
Karmaşık veri yapılarını tek bir meta key altında saklamak için serileştirme kullanılır. Örneğin bir etkinlik için birden fazla konuşmacı bilgisi:
<?php
function etkinlik_konusmacilari_kaydet($post_id, $konusmaci_listesi) {
// Dizi olarak güvenli şekilde kaydet
if (!is_array($konusmaci_listesi)) {
return false;
}
// Her konuşmacı için veri temizleme
$temiz_liste = array_map(function($konusmaci) {
return array(
'ad' => sanitize_text_field($konusmaci['ad'] ?? ''),
'sirket' => sanitize_text_field($konusmaci['sirket'] ?? ''),
'email' => sanitize_email($konusmaci['email'] ?? ''),
'biyografi' => sanitize_textarea_field($konusmaci['biyografi'] ?? '')
);
}, $konusmaci_listesi);
// WordPress otomatik serialize eder
update_post_meta($post_id, '_konusmaci_listesi', $temiz_liste);
return true;
}
function etkinlik_konusmacilari_getir($post_id) {
$liste = get_post_meta($post_id, '_konusmaci_listesi', true);
// Boş kontrolü
if (empty($liste) || !is_array($liste)) {
return array();
}
return $liste;
}
// Kullanım örneği
$konusmaci_verisi = array(
array(
'ad' => 'Ahmet Yılmaz',
'sirket' => 'TechCorp',
'email' => '[email protected]',
'biyografi' => '10 yıllık yazılım geliştirme deneyimi'
),
array(
'ad' => 'Zeynep Kaya',
'sirket' => 'DataSoft',
'email' => '[email protected]',
'biyografi' => 'Veri bilimi uzmanı'
)
);
etkinlik_konusmacilari_kaydet(get_the_ID(), $konusmaci_verisi);
// Okuma
$konusmaci_listesi = etkinlik_konusmacilari_getir(get_the_ID());
foreach ($konusmaci_listesi as $konusmaci) {
echo '<strong>' . esc_html($konusmaci['ad']) . '</strong>';
echo ' - ' . esc_html($konusmaci['sirket']) . '<br>';
}
?>
Serileştirilmiş veriyi asla doğrudan SQL sorgusunda meta_query ile kullanma. WordPress bu verinin içinde arama yapamaz. Bu tür verileri filtrelemek gerekiyorsa ayrı meta key’lerde tutman gerekiyor.
WooCommerce ile Ürün Meta Entegrasyonu
WooCommerce ürünlerine özel meta eklemek biraz farklı çalışıyor. Ürün düzenleme sayfasındaki sekmelere entegre etmek için:
<?php
// WooCommerce ürün veri sekmelerine alan ekleme
function woo_ozel_alan_ekle() {
global $post;
echo '<div class="options_group">';
// Metin alanı
woocommerce_wp_text_input(array(
'id' => '_teknik_not',
'label' => 'Teknik Not',
'description' => 'Dahili kullanım için teknik notlar',
'desc_tip' => true,
'placeholder' => 'Buraya teknik not giriniz...'
));
// Seçim kutusu
woocommerce_wp_select(array(
'id' => '_urun_sinifi',
'label' => 'Ürün Sınıfı',
'options' => array(
'' => 'Seçiniz',
'A' => 'A Sınıfı',
'B' => 'B Sınıfı',
'C' => 'C Sınıfı'
)
));
// Onay kutusu
woocommerce_wp_checkbox(array(
'id' => '_ozel_siparis',
'label' => 'Özel Sipariş Ürünü',
'description' => 'Bu ürün özel sipariş gerektiriyor'
));
echo '</div>';
}
add_action('woocommerce_product_options_general_product_data', 'woo_ozel_alan_ekle');
// WooCommerce ürün kaydetme
function woo_ozel_alan_kaydet($post_id) {
// Teknik not
if (isset($_POST['_teknik_not'])) {
update_post_meta(
$post_id,
'_teknik_not',
sanitize_textarea_field($_POST['_teknik_not'])
);
}
// Ürün sınıfı
if (isset($_POST['_urun_sinifi'])) {
$gecerli_siniflar = array('A', 'B', 'C');
$sinif = sanitize_text_field($_POST['_urun_sinifi']);
if (in_array($sinif, $gecerli_siniflar)) {
update_post_meta($post_id, '_urun_sinifi', $sinif);
}
}
// Onay kutusu (checked ise 'yes', değilse 'no')
$ozel_siparis = isset($_POST['_ozel_siparis']) ? 'yes' : 'no';
update_post_meta($post_id, '_ozel_siparis', $ozel_siparis);
}
add_action('woocommerce_process_product_meta', 'woo_ozel_alan_kaydet');
?>
Meta Önbelleğe Alma ve Performans
Çok sayıda post meta okuyorsan performans ciddi sorun olabilir. WordPress’in yerleşik önbellek sistemini doğru kullanmak şart:
<?php
// Kötü yaklaşım: Her döngüde ayrı sorgu
function meta_ozellik_listesi_kotü($post_ids) {
foreach ($post_ids as $post_id) {
// Her seferinde veritabanı sorgusu - N+1 problemi
$garanti = get_post_meta($post_id, '_garanti_suresi', true);
echo $garanti;
}
}
// İyi yaklaşım: Toplu önbellek yükleme
function meta_ozellik_listesi_iyi($post_ids) {
// Tüm post meta'yı tek seferde önbelleğe al
update_meta_cache('post', $post_ids);
foreach ($post_ids as $post_id) {
// Artık veritabanına gitmez, önbellekten okur
$garanti = get_post_meta($post_id, '_garanti_suresi', true);
echo $garanti;
}
}
// WP_Query zaten bunu yapıyor, ama manuel sorgularda dikkat et
// update_post_meta_cache parametresi ile de kontrol edebilirsin
$args = array(
'post_type' => 'product',
'posts_per_page' => 50,
'update_post_meta_cache' => true, // Varsayılan zaten true
'update_post_term_cache' => false // Term verisi gerekmiyorsa kapat
);
$sorgu = new WP_Query($args);
?>
Performansla ilgili dikkat etmen gereken noktalar:
- update_meta_cache() fonksiyonu çok sayıda post için meta’yı tek sorguda yükler
- WP_Query kullanırken
update_post_meta_cacheparametresi varsayılan olaraktruegelir - Serileştirilmiş büyük verileri
meta_queryile sorgulamak imkansız, bunu tasarım aşamasında düşün - Sık sorgulanan meta key’ler için veritabanında index olmadığını unutma, büyük sitelerde performans sorunu çıkabilir
Meta Verisi Doğrulama ve Temizleme
Güvenlik açısından bakıldığında, meta verisini hem kaydederken hem de okurken dikkatli olmak gerekiyor:
<?php
// Güvenli meta yardımcı sınıfı
class PostMetaYardimci {
// Sayısal değer kaydet ve doğrula
public static function sayisal_meta_kaydet($post_id, $meta_key, $deger, $min = null, $max = null) {
$deger = floatval($deger);
if ($min !== null && $deger < $min) {
$deger = $min;
}
if ($max !== null && $deger > $max) {
$deger = $max;
}
return update_post_meta($post_id, $meta_key, $deger);
}
// Listedeki değerlerden biri olmalı
public static function secimli_meta_kaydet($post_id, $meta_key, $deger, $gecerli_degerler) {
if (!in_array($deger, $gecerli_degerler, true)) {
return false;
}
return update_post_meta($post_id, $meta_key, sanitize_text_field($deger));
}
// JSON veri kaydet
public static function json_meta_kaydet($post_id, $meta_key, $veri) {
if (!is_array($veri) && !is_object($veri)) {
return false;
}
$json = wp_json_encode($veri);
if ($json === false) {
return false;
}
return update_post_meta($post_id, $meta_key, $json);
}
// JSON veri oku
public static function json_meta_getir($post_id, $meta_key, $varsayilan = array()) {
$json = get_post_meta($post_id, $meta_key, true);
if (empty($json)) {
return $varsayilan;
}
$veri = json_decode($json, true);
return $veri !== null ? $veri : $varsayilan;
}
}
// Kullanım
PostMetaYardimci::sayisal_meta_kaydet($post_id, '_fiyat_katsayisi', $_POST['katsayi'], 0.1, 10.0);
PostMetaYardimci::secimli_meta_kaydet($post_id, '_durum', $_POST['durum'], array('aktif', 'pasif', 'beklemede'));
?>
Gerçek Dünya Senaryosu: Kiralık Konut İlanları
Şimdi her şeyi bir araya getirelim. Diyelim ki kiralık konut ilan sitesi yapıyorsun. Her ilana oda sayısı, kira bedeli, depozito, evcil hayvan kabul durumu ve müsaitlik tarihi gibi bilgiler eklemen gerekiyor:
<?php
// İlan meta box'ını kayıt et
function kiralik_ilan_meta_kayit() {
add_meta_box(
'kiralik_detaylar',
'Kiralık Konut Detayları',
'kiralik_detaylar_gorunum',
'ilan',
'normal',
'high'
);
}
add_action('add_meta_boxes', 'kiralik_ilan_meta_kayit');
function kiralik_detaylar_gorunum($post) {
wp_nonce_field('kiralik_nonce_aksiyon', 'kiralik_nonce_alani');
$oda_sayisi = get_post_meta($post->ID, '_oda_sayisi', true);
$kira = get_post_meta($post->ID, '_kira_bedeli', true);
$depozito = get_post_meta($post->ID, '_depozito', true);
$evcil_hayvan = get_post_meta($post->ID, '_evcil_hayvan', true);
$musait_tarih = get_post_meta($post->ID, '_musait_tarih', true);
$ozellikler = get_post_meta($post->ID, '_ozellikler', true);
?>
<table style="width:100%;border-collapse:collapse;">
<tr>
<td style="padding:8px;"><label>Oda Sayısı:</label><br>
<input type="number" name="oda_sayisi" value="<?php echo esc_attr($oda_sayisi); ?>" min="0" max="20" style="width:100%"></td>
<td style="padding:8px;"><label>Kira Bedeli (TL):</label><br>
<input type="number" name="kira_bedeli" value="<?php echo esc_attr($kira); ?>" min="0" style="width:100%"></td>
</tr>
<tr>
<td style="padding:8px;"><label>Depozito (TL):</label><br>
<input type="number" name="depozito" value="<?php echo esc_attr($depozito); ?>" min="0" style="width:100%"></td>
<td style="padding:8px;"><label>Müsaitlik Tarihi:</label><br>
<input type="date" name="musait_tarih" value="<?php echo esc_attr($musait_tarih); ?>" style="width:100%"></td>
</tr>
<tr>
<td style="padding:8px;" colspan="2">
<label>Evcil Hayvan:</label>
<select name="evcil_hayvan" style="width:100%">
<option value="hayir" <?php selected($evcil_hayvan, 'hayir'); ?>>Hayır</option>
<option value="evet" <?php selected($evcil_hayvan, 'evet'); ?>>Evet</option>
<option value="kucuk" <?php selected($evcil_hayvan, 'kucuk'); ?>>Küçük Hayvanlar</option>
</select>
</td>
</tr>
</table>
<?php
}
function kiralik_detaylar_kaydet($post_id) {
if (!isset($_POST['kiralik_nonce_alani']) ||
!wp_verify_nonce($_POST['kiralik_nonce_alani'], 'kiralik_nonce_aksiyon')) {
return;
}
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
if (get_post_type($post_id) !== 'ilan') return;
$alanlar = array(
'_oda_sayisi' => array('tip' => 'int', 'min' => 0, 'max' => 20),
'_kira_bedeli' => array('tip' => 'float', 'min' => 0),
'_depozito' => array('tip' => 'float', 'min' => 0),
'_evcil_hayvan' => array('tip' => 'secim', 'secenekler' => array('hayir', 'evet', 'kucuk')),
'_musait_tarih' => array('tip' => 'tarih')
);
$post_anahtarlari = array(
'_oda_sayisi' => 'oda_sayisi',
'_kira_bedeli' => 'kira_bedeli',
'_depozito' => 'depozito',
'_evcil_hayvan' => 'evcil_hayvan',
'_musait_tarih' => 'musait_tarih'
);
foreach ($post_anahtarlari as $meta_key => $post_key) {
if (!isset($_POST[$post_key])) continue;
$deger = $_POST[$post_key];
$kural = $alanlar[$meta_key];
switch ($kural['tip']) {
case 'int':
$deger = intval($deger);
if (isset($kural['min']) && $deger < $kural['min']) $deger = $kural['min'];
if (isset($kural['max']) && $deger > $kural['max']) $deger = $kural['max'];
break;
case 'float':
$deger = floatval($deger);
if (isset($kural['min']) && $deger < $kural['min']) $deger = $kural['min'];
break;
case 'secim':
if (!in_array($deger, $kural['secenekler'])) continue 2;
$deger = sanitize_text_field($deger);
break;
case 'tarih':
// YYYY-MM-DD formatı doğrulama
if (!preg_match('/^d{4}-d{2}-d{2}$/', $deger)) continue 2;
break;
}
update_post_meta($post_id, $meta_key, $deger);
}
}
add_action('save_post_ilan', 'kiralik_detaylar_kaydet');
?>
Sonuç
Post meta yönetimi, WordPress geliştirmenin belkemiği. Doğru kullandığında inanılmaz esnek ve güçlü bir yapı elde ediyorsun, yanlış kullandığında ise performans sorunları ve güvenlik açıkları kaçınılmaz oluyor.
Özetlemek gerekirse:
- update_post_meta() kullan, add_post_meta() sadece gerçekten çoklu değer gerektiğinde
- Meta key’leri alt çizgi ile başlat, dahili alanların görünmesini engelliyor
- Her zaman nonce doğrulama, yetki kontrolü ve veri temizleme yap
- Serileştirilmiş veri içinde
meta_queryile arama yapma - Toplu işlemlerde update_meta_cache() ile N+1 sorununu önle
- WooCommerce için kendi helper fonksiyonlarını kullan, standart meta box API’si yerine
Bu temeli kavradıktan sonra Advanced Custom Fields gibi eklentilerin aslında perde arkasında ne yaptığını da anlıyorsun. Ve ihtiyacın olduğunda o eklentiye bağımlı kalmadan kendi çözümünü yazabiliyorsun. Sysadmin kafasıyla düşününce, ne kadar az bağımlılık o kadar az sorun demek.
