Php İle Nesne Yönelimli Yapıda Dinamik Kategori Ağacı Nasıl Yapılır?

Php İle Nesne Yönelimli Yapıda Dinamik Kategori Ağacı Nasıl Yapılır?
Php İle Nesne Yönelimli Yapıda Dinamik Kategori Ağacı Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu uygulamayı hayata geçirmek için sisteminizde PHP 8.2 veya daha güncel bir sürümün yüklü olması önerilir. Veritabanı işlemleri için PDO (PHP Data Objects) kütüphanesini kullanacağız; bu, veritabanı bağımsızlığı ve güvenlik açısından en iyi pratiktir.

  • PHP 8.2+ (Readonly properties ve constructor promotion özellikleri için).
  • MySQL veya MariaDB veritabanı sunucusu.
  • Temel düzeyde SQL bilgisi.
  • Composer (isteğe bağlı, ancak PSR standartları için önerilir).

Veritabanı Tasarımı: Adjacency List Modeli

Dinamik kategori ağacı için en yaygın ve yönetilebilir yöntem "Adjacency List" (Bitişik Liste) modelidir. Bu modelde her kategori, bir üst kategorisini işaret eden parent_id sütununa sahiptir. Eğer bir kategori ana kategori ise, parent_id değeri NULL olur.

CREATE TABLE categories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    parent_id INT DEFAULT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE CASCADE
);

Yukarıdaki SQL kodu, kategoriler tablosunu oluşturur. FOREIGN KEY kısıtlaması, veri bütünlüğünü korumak için kritiktir. Bir üst kategori silindiğinde, ona bağlı alt kategorilerin de silinmesini (veya düzenlenmesini) bu kısıtlama ile kontrol ederiz.

Kategori Sınıfının Oluşturulması

Nesne yönelimli yapıda, kategorileri temsil eden bir sınıf oluşturmak, verileri daha yönetilebilir kılar. Bu sınıf, veritabanından gelen ham veriyi işleyerek ağaç yapısına uygun hale getirmemize yardımcı olacaktır.

class Category {
    public function __construct(
        public int $id,
        public string $name,
        public ?int $parentId,
        public array $children = []
    ) {}
}

Bu sınıf, PHP 8.0 ile gelen constructor promotion özelliğini kullanır. public array $children özelliği, o kategoriye ait alt kategorileri tutacak olan dizidir. Bu, ağaç yapısını oluştururken kullanacağımız ana taşıyıcıdır.

Özyinelemeli (Recursive) Ağaç Oluşturma Mantığı

Veritabanından gelen düz listeyi, iç içe geçmiş bir ağaç yapısına dönüştürmek için özyinelemeli bir fonksiyon kullanmalıyız. Bu fonksiyon, her kategoriyi kontrol eder ve varsa alt kategorilerini kendi içine ekler.

function buildTree(array $elements, ?int $parentId = null): array {
    $branch = [];
    foreach ($elements as $element) {
        if ($element['parent_id'] == $parentId) {
            $children = buildTree($elements, $element['id']);
            if ($children) {
                $element['children'] = $children;
            }
            $branch[] = $element;
        }
    }
    return $branch;
}

Bu fonksiyon, $elements dizisini tarar ve parent_id eşleşmesi durumunda alt dalları oluşturur. Bu yöntem, veritabanına her düğüm için sorgu atmaktan (N+1 problemi) kaçınmamızı sağlar; tüm veriyi bir kerede çekip PHP tarafında işlemek çok daha performanslıdır.

Dinamik Veri Çekme ve İşleme

Şimdi veritabanından verileri çekip, yukarıdaki fonksiyonu kullanarak ağacı oluşturalım. Güvenlik için PDO kullanırken mutlaka prepared statements (hazırlanmış ifadeler) kullanmalısınız.

$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->query("SELECT * FROM categories");
$categories = $stmt->fetchAll(PDO::FETCH_ASSOC);

$tree = buildTree($categories);
print_r($tree);

Bu kod bloğu, veritabanındaki tüm kategorileri çeker ve buildTree fonksiyonuna gönderir. Sonuç, iç içe geçmiş bir dizi (array) yapısıdır. Bu yapıyı ön yüzde bir ul/li listesi olarak kolayca gösterebilirsiniz.

Kategori Ağacı Yöntemleri Karşılaştırması

Yöntem Avantaj Dezavantaj
Adjacency List Basit yapı, kolay CRUD işlemleri. Derin hiyerarşilerde yavaş olabilir.
Nested Sets Okuma performansı çok yüksek. Ekleme/Silme işlemleri karmaşıktır.
Path Enumeration Sorgulaması çok hızlıdır. Veri bütünlüğünü sağlamak zordur.
Kritik Güvenlik Uyarısı: Veritabanı işlemlerinde asla kullanıcıdan gelen verileri doğrudan sorguya dahil etmeyin. SQL Injection saldırılarına karşı her zaman PDO prepared statements kullanın. Ayrıca, üretim ortamında hata mesajlarını kullanıcıya göstermeyin; display_errors ayarını Off yapın.

Sıkça Sorulan Sorular

Ağaç yapısı çok derinleşirse performans sorunu yaşar mıyım?

Adjacency List modelinde PHP tarafında yapılan özyineleme, bellekte sınırlı bir yük oluşturur. Ancak binlerce kategoriniz varsa, veriyi önbelleğe almak (Redis veya Memcached) en iyi pratik olacaktır.

Neden veritabanında her düğüm için sorgu atmamalıyım?

Buna N+1 problemi denir. Her alt kategori için ayrı bir SQL sorgusu atmak, veritabanı sunucusunu gereksiz yorar ve sayfa yüklenme süresini ciddi oranda artırır.

Kategorileri sürükle-bırak yöntemiyle nasıl güncellerim?

Bunun için veritabanında position veya order sütunu tutmanız ve bir JavaScript kütüphanesi (SortableJS gibi) ile parent_id değerini güncelleyen bir API uç noktası yazmanız gerekir.

Bu yapıya "aktif/pasif" durumu nasıl eklenir?

Veritabanına is_active (tinyint) sütunu ekleyip, buildTree fonksiyonunda if ($element['is_active']) kontrolü yaparak pasif kategorileri ağaca dahil etmeyebilirsiniz.

Ağacı HTML olarak nasıl render ederim?

Özyinelemeli bir fonksiyon daha yazarak, diziyi döngüye sokup her seviyede

    ve
  • etiketlerini string olarak birleştirebilirsiniz.

    Kategori Ağacı İçin Önbellekleme (Caching) Stratejileri

    Dinamik kategori ağaçları, veritabanı üzerinde her sayfa yüklenişinde özyinelemeli sorgular çalıştırdığı için yüksek trafikli projelerde darboğaz oluşturabilir. Bu durumu aşmak için en etkili yöntem, ağaç yapısını oluşturduktan sonra sonucu bir süreliğine belleğe (Redis veya Memcached) veya dosya sistemine kaydetmektir.

    Aşağıdaki örnekte, oluşturulan ağaç yapısının 3600 saniye boyunca bir JSON dosyası olarak nasıl saklanacağını görebilirsiniz:

    
    public function getCachedTree($cacheFile = 'category_tree.json') {
        if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
            return json_decode(file_get_contents($cacheFile), true);
        }
    
        $tree = $this->buildTree(); // Önceki bölümlerde oluşturduğumuz metod
        file_put_contents($cacheFile, json_encode($tree));
        return $tree;
    }
    

    SEO Dostu URL Yapısı ve Slug Yönetimi

    Kategorilerin sadece ID değerleri ile değil, anlamlı metinlerle (slug) çağrılması SEO açısından kritiktir. Bir kategorinin tam yolunu (path) oluşturmak için, veritabanında her kategoriye ait bir slug sütunu tutmalı ve ağaç yapısını oluştururken bu slug değerlerini birleştirmelisiniz.

    Örneğin, "Elektronik > Telefon > Akıllı Telefon" hiyerarşisi için URL yapısını /kategori/elektronik/telefon/akilli-telefon şeklinde oluşturmak, arama motorlarının içeriği daha iyi indekslemesini sağlar.

    
    // Kategori nesnesi içinde slug oluşturma mantığı
    public function generateFullSlug($categoryId, $allCategories) {
        $path = [];
        $current = $categoryId;
        
        while ($current != 0) {
            $cat = $allCategories[$current];
            array_unshift($path, $cat['slug']);
            $current = $cat['parent_id'];
        }
        
        return implode('/', $path);
    }
    

    Hata Ayıklama (Debugging) ve Döngü Kontrolü

    Özyinelemeli yapılarda en sık karşılaşılan sorun "sonsuz döngü" (infinite loop) hatasıdır. Bu durum genellikle bir kategorinin yanlışlıkla kendi kendisinin üst kategorisi olarak atanmasıyla oluşur. Veri girişlerinde bu durumu engellemek için bir "Döngü Kontrolü" (Cycle Detection) mekanizması kurmalısınız.

    Veritabanına yeni bir kategori eklemeden veya güncellemeden önce, seçilen üst kategorinin, güncellenen kategorinin bir alt dalı olup olmadığını kontrol etmelisiniz:

    
    public function isDescendant($parentId, $childId) {
        if ($parentId == $childId) return true;
        
        $parent = $this->getCategoryById($parentId);
        if ($parent['parent_id'] == 0) return false;
        
        return $this->isDescendant($parent['parent_id'], $childId);
    }
    
    // Kullanım:
    if ($this->isDescendant($newParentId, $currentId)) {
        throw new Exception("Döngüsel referans hatası: Bir kategori kendi alt kategorisinin içine taşınamaz.");
    }
    

    Bu kontroller, veritabanı bütünlüğünü korur ve uygulamanızın beklenmedik hatalar vermesini engeller. Özellikle yönetici panellerinde bu tip validasyonlar, sistemin uzun vadeli sağlığı için hayati önem taşır.

    Sonuç

    PHP ile dinamik bir kategori ağacı oluşturmak, başlangıçta karmaşık görünse de doğru modelleme ve özyinelemeli fonksiyonlar ile oldukça yönetilebilir bir süreçtir. Adjacency List modeli, küçük ve orta ölçekli projeler için en ideal yaklaşımdır. Bu rehberde öğrendiğiniz OOP prensipleri ve PDO kullanımı, projelerinizin güvenliğini ve sürdürülebilirliğini artıracaktır.

    Bir sonraki adım olarak, oluşturduğunuz bu ağacı bir önbellekleme mekanizması (Cache) ile birleştirerek performansınızı optimize etmeyi deneyebilirsiniz. Ayrıca, kategorilere özel SEO dostu URL yapıları (slug) ekleyerek kullanıcı deneyimini daha ileri bir seviyeye taşıyabilirsiniz.

Bu yazıya tepkinizi paylaşın:
Zeynep Kaya

Hobi projeleri ve kendin yap (DIY) içerikleri üzerine uzmanlaşmış bir içerik editörüyüm. Adım adım rehberlerle okuyucuların teknik becerilerini geliştirmelerine yardımcı oluyorum.

Yorumlar (0)

Yorum Yaz