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

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

Gereksinimler ve Ön Hazırlık

Projeye başlamadan önce geliştirme ortamınızın güncel olduğundan emin olmalısınız. Aşağıdaki araç ve sürümler, bu makaledeki kodların sorunsuz çalışması için zorunludur:

  • PHP 8.2 veya üzeri: Tip tanımlamaları (type hinting) ve modern özellikler için gereklidir.
  • MySQL 8.0 veya MariaDB 10.6+: Veri saklama ve sorgulama işlemleri için.
  • PDO (PHP Data Objects): Veritabanı etkileşimlerinde SQL Injection saldırılarını önlemek için standarttır.
  • Composer: Proje bağımlılıklarını yönetmek için önerilir (bu projede saf PHP kullanacağız ancak PSR standartlarına uyacağız).

Veritabanı Şemasını Tasarlama

Arama geçmişini tutmak için verimli bir tablo yapısına ihtiyacımız var. Kullanıcı kimliği, aranan terim ve arama zamanı gibi verileri saklayacağımız search_history tablosunu oluşturalım.

CREATE TABLE search_history (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    search_term VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX(user_id),
    INDEX(created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Bu tablo yapısında user_id ve created_at sütunlarına indeks ekleyerek, kullanıcı bazlı sorguların performansını artırdık. utf8mb4 karakter seti, Türkçe karakterlerin doğru saklanması için kritiktir.

SearchHistory Sınıfını Oluşturma

Nesne yönelimli yapıda, veritabanı işlemlerini bir sınıf içerisinde toplamak kodun tekrar kullanılabilirliğini artırır. SearchHistory sınıfı, arama kaydetme ve listeleme işlemlerini yönetecektir.

class SearchHistory {
    private PDO $db;

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

    public function add(int $userId, string $term): void {
        $term = trim($term);
        if (empty($term)) return;

        $stmt = $this->db->prepare("INSERT INTO search_history (user_id, search_term) VALUES (?, ?)");
        $stmt->execute([$userId, $term]);
    }
}

Yukarıdaki kodda, PDO nesnesini kurucu metot (constructor) üzerinden enjekte ediyoruz. add metodu, SQL Injection riskine karşı hazırlıklı ifadeler (prepared statements) kullanır. trim() fonksiyonu ile boşlukları temizleyerek veritabanı kirliliğini önleriz.

Dinamik Arama Geçmişi Listeleme

Kullanıcının geçmiş aramalarını listelerken, aynı terimlerin tekrar etmemesi ve en son yapılan aramaların en üstte görünmesi gerekir. Bunun için DISTINCT ve ORDER BY ifadelerini kullanacağız.

public function getRecent(int $userId, int $limit = 5): array {
    $stmt = $this->db->prepare("
        SELECT DISTINCT search_term 
        FROM search_history 
        WHERE user_id = ? 
        ORDER BY created_at DESC 
        LIMIT ?
    ");
    $stmt->execute([$userId, $limit]);
    return $stmt->fetchAll(PDO::FETCH_COLUMN);
}

Bu metot, veritabanından kullanıcıya özel en son 5 aramayı çeker. PDO::FETCH_COLUMN kullanımı, veriyi doğrudan bir dizi olarak döndürdüğü için işlenmesini kolaylaştırır.

Güvenlik Uyarıları ve En İyi Pratikler

Kritik Güvenlik Uyarısı: Kullanıcıdan alınan her veri "kirli" kabul edilmelidir. Arama terimlerini ekrana yazdırırken mutlaka htmlspecialchars() kullanarak XSS (Cross-Site Scripting) saldırılarını engelleyin. Ayrıca, veritabanı bağlantı bilgilerini asla kod içerisinde açık metin olarak tutmayın; .env dosyaları veya güvenli bir yapılandırma dosyası kullanın.

Aşağıdaki tablo, arama geçmişi yönetimi için kullanılan yöntemlerin karşılaştırmasını sunmaktadır:

Yöntem Avantaj Dezavantaj
Veritabanı (MySQL) Kalıcı, ölçeklenebilir Sunucu yükü artabilir
Session/Cookie Hızlı, sunucu yükü yok Tarayıcı silinince kaybolur

Uygulama Entegrasyonu ve Test Etme

Şimdi oluşturduğumuz sınıfı bir arama sayfasında nasıl kullanacağımızı görelim. Kullanıcı bir arama yaptığında veritabanına kaydedeceğiz ve hemen ardından geçmişi ekrana basacağız.

// Veritabanı bağlantısı
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');

$history = new SearchHistory($pdo);
$userId = 1; // Oturumdan alınan kullanıcı ID

if (isset($_GET['q'])) {
    $history->add($userId, $_GET['q']);
}

$recentSearches = $history->getRecent($userId);

foreach ($recentSearches as $term) {
    echo "" . htmlspecialchars($term) . "
"; }

Bu kod bloğu, kullanıcının arama terimini alır, kaydeder ve geçmiş listesini dinamik olarak linkler halinde ekrana döker. urlencode kullanımı, arama terimindeki özel karakterlerin URL'de bozulmamasını sağlar.

Sıkça Sorulan Sorular

Arama geçmişi veritabanını nasıl temizleyebilirim?

Kullanıcıya "Geçmişi Sil" butonu vererek, DELETE FROM search_history WHERE user_id = ? sorgusunu çalıştıran bir metot ekleyebilirsiniz.

Çok fazla arama olursa veritabanı yavaşlar mı?

Evet, binlerce kayıt performans kaybına yol açar. Belirli bir sayıdan (örneğin 50) fazla kaydı olan kullanıcılar için eski kayıtları silen bir "temizlik" mekanizması kurmalısınız.

Arama geçmişini anonim kullanıcılar için yapabilir miyim?

Evet, ancak user_id yerine tarayıcı çerezlerini (cookie) veya IP adresini (dikkatli kullanılmalı) baz almanız gerekir.

Neden Session yerine Veritabanı tercih etmeliyim?

Session verileri sunucu belleğinde tutulur ve oturum kapandığında silinir. Veritabanı ise kullanıcının cihaz değiştirmesi durumunda bile arama geçmişinin korunmasını sağlar.

Arama terimlerini nasıl daha iyi filtreleyebilirim?

Kötü niyetli karakterleri temizlemek için strip_tags() fonksiyonunu veya düzenli ifadeleri (regex) kullanarak arama terimlerini onaylayabilirsiniz.

Arama Geçmişi İçin Performans Optimizasyonu ve İndeksleme

Veritabanınızdaki arama geçmişi tablosu, kullanıcı trafiğiniz arttıkça hızla büyüyecektir. Milyonlarca satıra ulaşan bir tabloda SELECT sorguları çalıştırmak, uygulamanızın yanıt süresini ciddi oranda düşürür. Bu sorunu aşmak için veritabanı seviyesinde indeksleme stratejileri uygulamalısınız.

İndeksleme Stratejisi

Arama geçmişi tablosunda en sık kullanılan sorgular, belirli bir kullanıcıya ait son aramaları getirmek veya popüler aramaları listelemektir. Bu nedenle, user_id ve created_at sütunlarına bileşik (composite) bir indeks eklemek, sorgu performansını dramatik şekilde artıracaktır.

ALTER TABLE search_history ADD INDEX idx_user_date (user_id, created_at DESC);
ALTER TABLE search_history ADD INDEX idx_term_count (search_term);

Ayrıca, tablonuzun boyutunu kontrol altında tutmak için belirli bir süreden (örneğin 30 gün) eski kayıtları otomatik olarak silen bir Cron Job tanımlamanız önerilir. Bu, veritabanının "taze" kalmasını sağlar.

Gelişmiş Hata Ayıklama (Debugging) Teknikleri

Arama geçmişi modülünde yaşanabilecek hataları tespit etmek için sınıf yapınıza bir loglama mekanizması eklemek, geliştirme sürecinizi kolaylaştırır. Özellikle veritabanı bağlantı hataları veya geçersiz karakter girişleri, uygulamanın çökmesine neden olabilir.

Özel Hata Yönetimi Sınıfı

SearchHistory sınıfınız içerisinde try-catch bloklarını kullanarak hataları yakalayın ve bunları bir hata günlüğü dosyasına yazdırın.

public function saveSearch($userId, $term) {
    try {
        $stmt = $this->db->prepare("INSERT INTO search_history (user_id, search_term) VALUES (?, ?)");
        return $stmt->execute([$userId, $term]);
    } catch (PDOException $e) {
        error_log("Arama geçmişi kaydedilemedi: " . $e->getMessage());
        return false;
    }
}

İleri İpucu: Geliştirme ortamında (development), hataları doğrudan tarayıcıya yansıtmak yerine, error_reporting(E_ALL) ayarını aktif ederek log dosyalarınızı düzenli olarak inceleyin. Bu, kullanıcı deneyimini bozmadan arka planda neler olup bittiğini anlamanızı sağlar.

Dağıtım (Deployment) Öncesi Kontrol Listesi

Projenizi canlıya almadan önce aşağıdaki teknik kontrolleri tamamladığınızdan emin olun:

  • Veritabanı Bağlantı Bilgileri: Veritabanı kullanıcı adınızın ve şifrenizin .env dosyası gibi güvenli bir alanda saklandığından emin olun.
  • SQL Injection Testi: Hazırlanan ifadelerin (Prepared Statements) tüm arama girişlerinde kullanıldığını tekrar doğrulayın.
  • Karakter Seti: Veritabanı ve tablo karşılaştırmasının utf8mb4 olduğundan emin olun; bu, Türkçe karakterlerin (ç, ğ, ı, ö, ş, ü) sorunsuz kaydedilmesini sağlar.
  • Önbellekleme: Çok sık yapılan aramalar için Redis veya Memcached kullanarak veritabanı yükünü azaltmayı değerlendirin.

Sonuç

PHP ile nesne yönelimli yapıda dinamik bir arama geçmişi oluşturmak, uygulamanızın kullanıcı etkileşimini artırmanın en etkili yollarından biridir. Bu rehberde, veritabanı tasarımından sınıf yapısına ve güvenlik önlemlerine kadar uçtan uca bir çözüm sunduk. Bir sonraki adımda, bu arama geçmişine "popüler aramalar" özelliğini ekleyerek, veritabanında COUNT() sorguları ile en çok aranan terimleri analiz eden bir istatistik modülü geliştirebilirsiniz.

Yasal Uyarı: Bu rehberdeki kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) kullanmadan önce mutlaka kodlarınızı güvenlik testlerinden geçirin ve veritabanı bağlantılarınızın şifreli olduğundan emin olun. Yazılım güvenliği sorumluluğu geliştiriciye aittir.

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

Mutfak pratikleri ve ev düzeni konularında yıllardır içerik üretiyorum. Okuyuculara zaman kazandıran, uygulanabilir ve sürdürülebilir yaşam tarzı önerileri sunuyorum.

Yorumlar (0)

Yorum Yaz