Php İle Nesne Yönelimli Yapıda Dinamik Site İçi Arama İndeksi Nasıl Yapılır?

Php İle Nesne Yönelimli Yapıda Dinamik Site İçi Arama İndeksi Nasıl Yapılır?
Php İle Nesne Yönelimli Yapıda Dinamik Site İçi Arama İndeksi Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu uygulamayı hayata geçirmek için sunucunuzda PHP 8.2+ sürümünün yüklü olması ve PDO (PHP Data Objects) eklentisinin aktif olması gerekir. Veritabanı yönetimi için MySQL veya MariaDB kullanacağız. İndeksleme mantığını kurarken, verilerin tutarlılığı için Transaction (işlem) yönetimini kullanacağız.

  • PHP 8.2 veya üzeri
  • MySQL 8.0+ veya MariaDB 10.5+
  • PDO veritabanı sürücüsü
  • Composer (isteğe bağlı, ancak PSR-4 standartları için önerilir)

Veritabanı Şemasını Tasarlama

Dinamik bir arama indeksi için iki temel tabloya ihtiyacımız var: Birincisi ana içeriklerin tutulduğu "icerikler" tablosu, ikincisi ise arama motorunun hızlıca sorgulayacağı "arama_indeksi" tablosudur. İndeks tablosu, kelimeleri ve bu kelimelerin hangi içeriklere ait olduğunu tutan bir "ters indeks" (inverted index) mantığıyla çalışacaktır.

CREATE TABLE icerikler (
    id INT AUTO_INCREMENT PRIMARY KEY,
    baslik VARCHAR(255) NOT NULL,
    icerik TEXT NOT NULL,
    olusturulma_tarihi TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE arama_indeksi (
    id INT AUTO_INCREMENT PRIMARY KEY,
    kelime VARCHAR(50) NOT NULL,
    icerik_id INT NOT NULL,
    frekans INT DEFAULT 1,
    INDEX(kelime),
    FOREIGN KEY (icerik_id) REFERENCES icerikler(id) ON DELETE CASCADE
);

Bu yapı, kelime bazlı hızlı erişim sağlar. `arama_indeksi` tablosundaki `kelime` sütununa indeks eklemek, arama hızını dramatik şekilde artırır.

Nesne Yönelimli Arama İndeksleyici Sınıfı

Sistemin temelini oluşturacak olan SearchIndexer sınıfı, içerikleri analiz edip indeks tablosuna yazan mantığı barındıracaktır. Bu sınıf, veriyi temizleme (sanitization) ve kelimelere ayırma (tokenization) işlemlerini üstlenir.

class SearchIndexer {
    private PDO $db;

    public function __construct(PDO $db) {
        $this->db = $db;
    }

    public function indexContent(int $id, string $text): void {
        $words = str_word_count(strtolower($text), 1);
        $wordCounts = array_count_values($words);

        foreach ($wordCounts as $word => $count) {
            if (strlen($word) > 2) { // 2 karakterden kısa kelimeleri indeksleme
                $stmt = $this->db->prepare("INSERT INTO arama_indeksi (kelime, icerik_id, frekans) VALUES (?, ?, ?)");
                $stmt->execute([$word, $id, $count]);
            }
        }
    }
}

Bu sınıf, verilen metni küçük harflere çevirir, kelimelere böler ve her bir kelimenin metin içinde kaç kez geçtiğini hesaplayarak veritabanına kaydeder.

Arama Motoru Mantığını Geliştirme

İndeksleme yapıldıktan sonra, kullanıcının girdiği terimi sorgulayacak olan SearchEngine sınıfını oluşturmalıyız. Bu sınıf, arama terimini alır ve indeks tablosunda eşleşen içerikleri getirir.

class SearchEngine {
    private PDO $db;

    public function __construct(PDO $db) {
        $this->db = $db;
    }

    public function search(string $query): array {
        $query = strtolower($query);
        $stmt = $this->db->prepare("SELECT i.baslik, i.id FROM icerikler i 
                                    JOIN arama_indeksi ai ON i.id = ai.icerik_id 
                                    WHERE ai.kelime = ? ORDER BY ai.frekans DESC");
        $stmt->execute([$query]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

Burada ORDER BY ai.frekans DESC ifadesi, aranan kelimenin içerikte en çok geçtiği makaleleri en üstte listelememizi sağlar. Bu, arama sonuçlarının alaka düzeyini artırır.

Güvenlik ve Performans Karşılaştırması

Arama motoru geliştirirken dikkat etmeniz gereken temel farklar aşağıdadır:

Yöntem Avantaj Dezavantaj
LIKE %sorgu% Basit kurulum Çok yavaş, indeks kullanmaz
Ters İndeks (Bu rehber) Çok hızlı, ölçeklenebilir Ek depolama alanı gerektirir
Full-Text Indexing Dahili MySQL özelliği Daha karmaşık yapılandırma

Kritik Güvenlik Uyarısı: Kullanıcıdan gelen arama verilerini asla doğrudan SQL sorgusuna dahil etmeyin. Yukarıdaki örneklerde olduğu gibi her zaman PDO "prepared statements" (hazırlanmış ifadeler) kullanarak SQL Injection saldırılarına karşı koruma sağlayın. Ayrıca, arama sonuçlarını ekrana basarken htmlspecialchars() fonksiyonunu kullanarak XSS saldırılarını engelleyin.

Dinamik Güncelleme Mekanizması

İçerik güncellendiğinde indeksin de güncellenmesi gerekir. Bunun için deleteOldIndex metodunu indexContent metodundan önce çağırmalısınız.

public function reindex(int $id, string $text): void {
    $this->db->prepare("DELETE FROM arama_indeksi WHERE icerik_id = ?")->execute([$id]);
    $this->indexContent($id, $text);
}

Bu metot, önce eski indeksleri temizler, ardından yeni metni analiz ederek güncel veriyi yazar. Bu, veritabanı tutarlılığını korumak için hayati önem taşır.

Sıkça Sorulan Sorular

Arama indeksleme neden veritabanı sorgusundan daha hızlıdır?

Veritabanı sorgularında (LIKE gibi) tüm satırlar taranır. İndeksleme yönteminde ise sadece kelime eşleşmesi kontrol edilir ve B-Tree indeksleri sayesinde arama süresi logaritmik olarak düşer.

Çok fazla kelime veritabanını şişirir mi?

Evet, büyük ölçekli sitelerde indeks tablosu hızla büyüyebilir. Bu durumda "stop-words" (ve, veya, bir, ile gibi gereksiz kelimeler) listesi oluşturarak bunları indekslememek depolama alanından tasarruf sağlar.

Bu yapı ile "fuzzy search" (yakın arama) yapılabilir mi?

Bu temel yapı tam eşleşme sağlar. Yakın arama için Levenshtein mesafesi gibi algoritmalar veya veritabanının sunduğu Full-Text Search özelliklerini eklemek gerekebilir.

PHP 8.2 ile gelen özellikler bu yapıyı nasıl etkiler?

PHP 8.2'nin sunduğu "readonly" sınıflar ve geliştirilmiş tip tanımlamaları, arama motoru sınıflarınızın daha güvenli ve hata payı düşük olmasını sağlar.

İndeksleme işlemini ne zaman tetiklemeliyim?

İndeksleme işlemini içerik kaydedildiği veya güncellendiği anda tetiklemek en doğrusudur. Ancak çok büyük içeriklerde bunu "queue" (kuyruk) yapısıyla arka planda çalıştırmak daha performanslıdır.

Gelişmiş İndeksleme İçin "Stop-Words" Filtresi Uygulaması

İndeksleme yaparken "ve", "veya", "bir", "bu" gibi dilde çok sık geçen ancak arama sonuçlarına anlamlı bir katkı sağlamayan kelimeleri (stop-words) filtrelemek, veritabanı boyutunu ciddi oranda düşürür ve sorgu hızını artırır. Aşağıdaki örnekte, indeksleme sınıfımıza basit bir filtreleme mekanizmasını nasıl entegre edeceğinizi görebilirsiniz.


class Indexer {
    private array $stopWords = ['ve', 'veya', 'bir', 'bu', 'için', 'ile'];

    public function tokenize(string $text): array {
        $words = explode(' ', mb_strtolower($text));
        return array_filter($words, function($word) {
            return strlen($word) > 2 && !in_array($word, $this->stopWords);
        });
    }
}

Arama Sonuçlarını Sıralama (Ranking) Algoritması

Basit bir arama motoru sadece eşleşen sonuçları getirir; profesyonel bir arama motoru ise en alakalı olanı en üste taşır. Bunun için TF-IDF (Term Frequency-Inverse Document Frequency) mantığına dayalı bir ağırlıklandırma sistemi kullanabilirsiniz. Bir kelime, bir dokümanda ne kadar sık geçiyorsa, o doküman o kelime için daha değerlidir.

Ağırlıklı Puanlama Stratejisi

Arama sonuçlarını sıralarken şu kriterleri birleştirerek bir puanlama tablosu oluşturabilirsiniz:

  • Başlık Ağırlığı: Başlıkta geçen kelimeler içerikte geçenlerden daha yüksek puan almalıdır (Örn: x3 çarpanı).
  • Kelime Sıklığı: İndeks tablosundaki frequency sütunu, toplam puana eklenmelidir.
  • Güncellik: İçeriğin oluşturulma tarihi, puanı normalize etmek için kullanılabilir.

// Basit bir puanlama mantığı örneği
public function calculateRank(int $frequency, bool $inTitle): float {
    $score = $frequency * 1.0;
    if ($inTitle) {
        $score *= 3.0;
    }
    return $score;
}

İndeksleme Süreçlerinde Hata Ayıklama ve Loglama

Dinamik indeksleme arka planda (cron job ile) çalıştığında, hata oluştuğunda bunu fark etmek zordur. İndeksleme sınıfınıza mutlaka bir Logger arayüzü ekleyerek, hangi dokümanın indekslenemediğini veya hangi kelimelerin hatalı işlendiğini takip etmelisiniz.

Hata Tipi Olası Sebep Çözüm
Memory Limit Exceeded Çok büyük metinlerin tek seferde işlenmesi Metni parçalara (chunk) bölerek işle
Deadlock Aynı anda okuma ve yazma yapılması İşlem sırasında tabloyu kilitle veya kuyruk yapısı kullan

İleri İpuçları: İndeksleme Performansını Artırma

Veritabanı tablonuz büyüdükçe arama hızı düşebilir. Bunu engellemek için şu yöntemleri değerlendirin:

  1. Partitioning: İndeks tablonuzu tarih bazlı veya ID aralıklarına göre bölümlere ayırın.
  2. Covering Index: Sorgularınızda sadece document_id ve score sütunlarını çekiyorsanız, veritabanında bu sütunları kapsayan bir "Composite Index" oluşturun.
  3. Redis Entegrasyonu: Çok sık aranan kelimeleri veritabanına gitmeden önce Redis üzerinde önbelleğe (cache) alarak veritabanı yükünü %90 oranında azaltabilirsiniz.

Sonuç

PHP ile nesne yönelimli bir arama indeksi kurmak, projenizin ölçeklenebilirliğini doğrudan artırır. Bu rehberde öğrendiğiniz ters indeksleme mantığı, profesyonel arama motorlarının çalışma prensibinin temelidir. Bir sonraki adım olarak, indeksleme sürecine "stop-words" filtresi ekleyerek indeks tablonuzun boyutunu optimize etmeyi deneyebilirsiniz. Yazılım geliştirme sürecinde güvenlik her zaman önceliğiniz olmalı; bu nedenle hazırladığımız sınıfları her zaman girdi doğrulama (validation) süreçleriyle desteklemeyi unutmayın.

Yasal Uyarı: Bu rehberde sunulan kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) kullanmadan önce, veritabanı bağlantı bilgilerini güvenli bir şekilde (env dosyaları ile) saklamalı ve uygulamanızı kapsamlı bir güvenlik testinden geçirmelisiniz.

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

Mutfak pratikleri ve organizasyonel hayat tüyoları üzerine uzman bir içerik üreticisiyim. Günlük rutinleri optimize eden yöntemleri, anlaşılır ve uygulanabilir rehberler haline getiriyorum.

Yorumlar (0)

Yorum Yaz