• Çar. May 27th, 2026

Fatih ŞAHİN

Not defterim.

Kullanıcı bir sekmeyi açık bırakmış, birkaç dakika sonra geri dönmüş ve karşısında login ekranı. Ya da daha kötüsü: uzun bir form doldurmuş, kaydet butonuna basmış ve veriler uçup gitmiş. Bu sorunun arkasında birden fazla neden yatıyor. Benim daha önceleri amatör projelerimde saç baş yolduğum bir konu : )


PHP Session Neden Beklenmedik Şekilde Sona Erer?

PHP’nin varsayılan session süresi çok kısa

PHP’nin session.gc_maxlifetime ayarı varsayılan olarak 1440 saniye, yani tam olarak 24 dakika. Sunucuda bu değeri php.ini veya kod içinden değiştirebilirsiniz, ama shared hosting ortamlarında işler karmaşıklaşır.

Shared hosting’de kontrol elinizde değil

Shared hosting kullanıyorsanız, sunucu üzerindeki PHP ayarları hosting sağlayıcısı tarafından belirlenir. ini_set('session.gc_maxlifetime', 86400) yazsanız bile, gc (garbage collector) başka bir kullanıcının isteğiyle tetiklendiğinde sizin session dosyalarınızı da silip götürebilir. Bu, shared hosting’de oldukça yaygın bir durumdur.

Session dosyaları disk tabanlıdır

PHP varsayılan olarak session verilerini sunucunun /tmp dizinine dosya olarak kaydeder. Bu dosyalar:

  • İşletim sistemi tarafından temizlenebilir,
  • Birden fazla sunucu (load balancer) kullanıyorsanız paylaşılamaz,
  • Disk dolduğunda sorun çıkarır.

Çözüm: Session’ları Veritabanında Saklayın

PHP, SessionHandlerInterface arayüzü sayesinde session’ların nerede saklanacağını tamamen özelleştirmenize olanak tanır. Veritabanını kullandığınızda:

  • Session süresi üzerinde tam kontrol sizde olur,
  • Shared hosting’in gc mekanizması sizi etkileyemez,
  • Birden fazla sunucu aynı session’ı paylaşabilir,
  • Session verilerini raporlayabilir, izleyebilirsiniz.

Adım 1: Veritabanı Tablosunu Oluşturun

Önce session verilerini tutacak tabloyu oluşturun:

CREATE TABLE IF NOT EXISTS php_sessions (
    session_id   VARCHAR(128) NOT NULL,
    session_data MEDIUMTEXT   NOT NULL,
    expires_at   DATETIME     NOT NULL,
    created_at   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (session_id),
    INDEX idx_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

MEDIUMTEXT kullanıyoruz çünkü session içinde büyük veriler (sepet içeriği, izinler vb.) bulunabilir. INDEX idx_expires ise süresi dolmuş kayıtları temizleme sorgusunu hızlandırır.


Adım 2: Session Handler Sınıfını Yazın

<?php

class DbSessionHandler implements SessionHandlerInterface
{
    private PDO $db;

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

    // Session başlatılırken çağrılır — veritabanı bağlantımız zaten hazır
    public function open(string $path, string $name): bool
    {
        return true;
    }

    // Session kapatılırken çağrılır
    public function close(): bool
    {
        return true;
    }

    // Session verisi okunur
    public function read(string $id): string
    {
        $stmt = $this->db->prepare(
            "SELECT session_data
             FROM php_sessions
             WHERE session_id = ? AND expires_at > NOW()"
        );
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);

        return $row ? $row['session_data'] : '';
    }

    // Session verisi yazılır (her istek sonunda otomatik çağrılır)
    public function write(string $id, string $data): bool
    {
        $expires = date('Y-m-d H:i:s', time() + SESSION_LIFETIME_SECONDS);

        $stmt = $this->db->prepare(
            "INSERT INTO php_sessions (session_id, session_data, expires_at)
             VALUES (?, ?, ?)
             ON DUPLICATE KEY UPDATE
                session_data = VALUES(session_data),
                expires_at   = VALUES(expires_at)"
        );

        return $stmt->execute([$id, $data, $expires]);
    }

    // Session yok edildiğinde (logout veya session_destroy())
    public function destroy(string $id): bool
    {
        $stmt = $this->db->prepare(
            "DELETE FROM php_sessions WHERE session_id = ?"
        );
        return $stmt->execute([$id]);
    }

    // Süresi dolmuş session'ları temizle
    public function gc(int $max_lifetime): int|false
    {
        $stmt = $this->db->prepare(
            "DELETE FROM php_sessions WHERE expires_at < NOW()"
        );
        $stmt->execute();
        return $stmt->rowCount();
    }
}

Bu sınıfın kilit noktası write() metodundaki ON DUPLICATE KEY UPDATE yapısıdır. Her istekte session verisi güncellenirken expires_at de yenilenir. Böylece kullanıcı aktif olduğu sürece oturumu hiçbir zaman sona ermez.


Adım 3: Handler’ı Aktif Edin

Session handler’ı session_start()‘tan önce kaydetmeniz şart:

<?php

// Sabit tanımla — tek yerden yönetin
if (!defined('SESSION_LIFETIME_SECONDS')) {
    define('SESSION_LIFETIME_SECONDS', 30 * 24 * 60 * 60); // 30 gün
}

// Veritabanı bağlantısı
$db = new PDO(
    "mysql:host=localhost;dbname=veritabani_adi;charset=utf8mb4",
    "kullanici",
    "sifre",
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

// Handler'ı kaydet (session_start'tan ÖNCE!)
$handler = new DbSessionHandler($db);
session_set_save_handler($handler, true);

// Çerez ayarlarını yapılandır
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
session_set_cookie_params([
    'lifetime' => SESSION_LIFETIME_SECONDS,
    'path'     => '/',
    'secure'   => $secure,
    'httponly' => true,
    'samesite' => 'Lax',
]);

// Artık session'ı başlatabilirsiniz
session_start();

Bu kadar. Bundan sonra $_SESSION ile her zamanki gibi çalışmaya devam edebilirsiniz.


Adım 4: Hareketsizlik Kontrolü (Opsiyonel ama Önerilen)

“30 gün boyunca oturum açık kalsın” derken iki farklı davranış söz konusu olabilir:

A) Kullanıcı aktif olduğu sürece süre uzasın, 30 gün boyunca hiç gelmezse kapansın:
Yukarıdaki write() metodu bunu zaten yapıyor. Her istekte expires_at güncelleniyor.

B) Kullanıcı X saat hiçbir şey yapmazsa kapansın:
Bunun için checkSession() fonksiyonuna hareketsizlik kontrolü ekleyin:

function checkSession(int $inactivity_limit = 3600): void
{
    if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
        header('Location: login.php');
        exit();
    }

    $now           = time();
    $last_activity = (int)($_SESSION['last_activity'] ?? $now);

    // Hareketsizlik süresi aşıldıysa oturumu kapat
    if ($now - $last_activity > $inactivity_limit) {
        session_unset();
        session_destroy();
        header('Location: login.php?msg=timeout');
        exit();
    }

    // Her istekte aktivite zamanını güncelle
    $_SESSION['last_activity'] = $now;
}

Kullanımı:

// Varsayılan 1 saat hareketsizlik kontrolü
checkSession();

// Ya da özel süre — 8 saat
checkSession(8 * 3600);

Adım 5: Süresi Dolmuş Session’ları Temizleme

PHP’nin gc() mekanizması otomatik çalışır ama production ortamında bunu kendiniz kontrol etmek daha güvenilirdir. Bir cron job ile günlük temizlik yapabilirsiniz:

<?php
// cleanup_sessions.php — günde bir kez çalıştırın
require_once 'db.php';

$stmt = $db->prepare("DELETE FROM php_sessions WHERE expires_at < NOW()");
$stmt->execute();
$silinen = $stmt->rowCount();

echo date('Y-m-d H:i:s') . " — $silinen süresi dolmuş session silindi.\n";

Cron tanımı (her gece 03:00’da):

0 3 * * * php /var/www/html/cleanup_sessions.php >> /var/log/session_cleanup.log

Dosya Yapısı ve Sıralama

Projedeki dosya yapısı şöyle olmalı:

sistem/
├── db.php               ← Bağlantı + SESSION_LIFETIME_SECONDS sabiti
├── DbSessionHandler.php ← Session handler sınıfı
└── funcs.php            ← checkSession() ve diğer fonksiyonlar

db.php içinde sıralama kritik — her adım bir öncekine bağlı:

<?php
// 1. Sabiti tanımla
define('SESSION_LIFETIME_SECONDS', 30 * 24 * 60 * 60);

// 2. Veritabanına bağlan
$db = new PDO(...);

// 3. Handler sınıfını yükle
require_once __DIR__ . '/DbSessionHandler.php';

// 4. Handler'ı kaydet (session_start ÖNCESİNDE!)
session_set_save_handler(new DbSessionHandler($db), true);

// 5. Çerez parametrelerini ayarla
session_set_cookie_params([...]);

funcs.php başında:

<?php
require_once __DIR__ . '/db.php'; // handler zaten kayıtlı

if (session_status() === PHP_SESSION_NONE) {
    session_start(); // Artık veritabanından okuyup yazıyor
}

Bonus: Aktif Oturumları Listeleyin

Veritabanı kullandığınız için artık aktif oturumları sorgulayabilirsiniz:

function aktif_oturumlari_getir(PDO $db): array
{
    $stmt = $db->query(
        "SELECT session_id, expires_at, created_at
         FROM php_sessions
         WHERE expires_at > NOW()
         ORDER BY expires_at DESC"
    );
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

Bu, güvenlik paneli, “aktif kullanıcılar” ekranı veya şüpheli oturumları sonlandırma gibi özellikler için kapı açar. Kodlar PHP 8.1+ ile uyumludur.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir