[PHP]session_destroy関数を完全解説!セッションを完全に破棄する方法

PHP

こんにちは!今回は、PHPの標準関数であるsession_destroy()について詳しく解説していきます。セッションデータを完全に削除できる、ログアウト処理に必須の関数です!

session_destroy関数とは?

session_destroy()関数は、現在のセッションに関連付けられたすべてのデータを破棄する関数です。

セッションファイル(またはストレージ)からデータを削除し、セッションを完全に終了します。ログアウト処理やセッションのリセットに使用されます!

基本的な構文

session_destroy(): bool
  • 引数: なし
  • 戻り値: 成功時はtrue、失敗時はfalse

重要な注意点

// session_destroy()の動作を理解する

// 1. セッションファイルは削除されるが、$_SESSION配列は残る
session_start();
$_SESSION['user'] = 'alice';
session_destroy();
echo $_SESSION['user'];  // まだ'alice'が出力される!

// 2. セッションクッキーは削除されない
// ブラウザには古いセッションIDが残る

// 3. session_start()後に使用する必要がある
session_start();  // これが必要
session_destroy();  // 正しい

// session_destroy();  // エラー(session_start()がない)

// 4. 完全なクリーンアップには追加手順が必要
session_start();
$_SESSION = [];  // セッション変数をクリア
session_destroy();  // セッションファイルを削除
setcookie(session_name(), '', time() - 3600, '/');  // クッキーを削除

他のセッション関数との違い

// session_destroy() - セッションファイルを削除
session_start();
$_SESSION['data'] = 'value';
session_destroy();  // ファイル削除、$_SESSIONは残る

// session_abort() - 変更を保存せず終了
session_start();
$_SESSION['data'] = 'modified';
session_abort();  // 変更を破棄、元の状態に戻す

// session_write_close() - 保存して終了
session_start();
$_SESSION['data'] = 'modified';
session_write_close();  // 変更を保存して終了

// session_unset() - $_SESSION変数をクリア
session_start();
$_SESSION['data'] = 'value';
session_unset();  // $_SESSIONを空にする、ファイルは残る

// session_reset() - 元の値にリセット
session_start();
$_SESSION['data'] = 'modified';
session_reset();  // 元の値に戻す

基本的な使用例

シンプルなセッション破棄

// セッション開始
session_start();

// データを設定
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'alice';

echo "セッション作成\n";
echo "user_id: {$_SESSION['user_id']}\n";

// セッションを破棄
session_destroy();

echo "セッション破棄後\n";
echo "user_id: {$_SESSION['user_id']}\n";  // まだアクセス可能!

// 新しいセッションを開始すると空
session_start();
echo "新しいセッション\n";
echo "user_id exists: " . (isset($_SESSION['user_id']) ? 'Yes' : 'No') . "\n";  // No

完全なクリーンアップ

session_start();

// セッション変数をすべてクリア
$_SESSION = [];

// セッションクッキーを削除
if (isset($_COOKIE[session_name()])) {
    setcookie(session_name(), '', time() - 3600, '/');
}

// セッションを破棄
session_destroy();

echo "セッションが完全にクリーンアップされました\n";

セッションの再作成

// 古いセッションを破棄
session_start();
session_destroy();

// 新しいセッションを開始
session_start();
$_SESSION['new_data'] = 'fresh';

echo "新しいセッションID: " . session_id() . "\n";

条件付き破棄

session_start();

// 条件に応じてセッションを破棄
$timeout = 3600;  // 1時間
$lastActivity = $_SESSION['last_activity'] ?? time();

if (time() - $lastActivity > $timeout) {
    echo "セッションタイムアウト\n";
    session_destroy();
} else {
    echo "セッション有効\n";
    $_SESSION['last_activity'] = time();
}

実践的な使用例

例1: 完全なログアウトマネージャー

class LogoutManager {
    /**
     * 標準的なログアウト処理
     */
    public function logout() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // セッション情報を記録(ログ用)
        $logData = [
            'session_id' => session_id(),
            'user_id' => $_SESSION['user_id'] ?? null,
            'logout_time' => time()
        ];
        
        // セッション変数をクリア
        $_SESSION = [];
        
        // セッションクッキーを削除
        if (isset($_COOKIE[session_name()])) {
            $params = session_get_cookie_params();
            setcookie(
                session_name(),
                '',
                time() - 3600,
                $params['path'],
                $params['domain'],
                $params['secure'],
                $params['httponly']
            );
        }
        
        // セッションを破棄
        $result = session_destroy();
        
        return [
            'success' => $result,
            'log_data' => $logData,
            'message' => 'Logged out successfully'
        ];
    }
    
    /**
     * セキュアログアウト(追加のクリーンアップ付き)
     */
    public function secureLogout() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // ユーザー情報を記録
        $userId = $_SESSION['user_id'] ?? null;
        $sessionId = session_id();
        
        // 関連するすべてのクッキーを削除
        $this->clearAllSessionCookies();
        
        // セッション変数をクリア
        $_SESSION = [];
        
        // セッションを破棄
        session_destroy();
        
        // セッション再生成(セキュリティ強化)
        session_start();
        session_regenerate_id(true);
        
        // ログアウト記録を新しいセッションに保存
        $_SESSION['logged_out'] = true;
        $_SESSION['logout_time'] = time();
        $_SESSION['previous_user'] = $userId;
        
        session_write_close();
        
        return [
            'success' => true,
            'user_id' => $userId,
            'old_session_id' => $sessionId,
            'new_session_id' => session_id(),
            'message' => 'Secure logout completed'
        ];
    }
    
    /**
     * すべてのセッション関連クッキーをクリア
     */
    private function clearAllSessionCookies() {
        $params = session_get_cookie_params();
        
        // メインセッションクッキー
        setcookie(
            session_name(),
            '',
            time() - 3600,
            $params['path'],
            $params['domain'],
            $params['secure'],
            $params['httponly']
        );
        
        // 追加のセッション関連クッキー(例)
        $additionalCookies = ['remember_me', 'user_token', 'session_token'];
        
        foreach ($additionalCookies as $cookie) {
            if (isset($_COOKIE[$cookie])) {
                setcookie($cookie, '', time() - 3600, '/');
            }
        }
    }
    
    /**
     * 全デバイスからログアウト
     */
    public function logoutAllDevices($userId) {
        // 現在のセッションを破棄
        $this->logout();
        
        // データベースから該当ユーザーのすべてのセッションを削除
        // (実際の実装ではデータベース処理が必要)
        $this->invalidateUserSessions($userId);
        
        return [
            'success' => true,
            'user_id' => $userId,
            'message' => 'Logged out from all devices'
        ];
    }
    
    /**
     * ユーザーのすべてのセッションを無効化(ダミー)
     */
    private function invalidateUserSessions($userId) {
        // データベースでの実装例:
        // DELETE FROM sessions WHERE user_id = ?
        // または
        // UPDATE sessions SET invalidated = 1 WHERE user_id = ?
        
        return true;
    }
    
    /**
     * 部分的ログアウト(特定のデータのみ削除)
     */
    public function partialLogout($keysToKeep = []) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // 保持するデータを退避
        $keepData = [];
        foreach ($keysToKeep as $key) {
            if (isset($_SESSION[$key])) {
                $keepData[$key] = $_SESSION[$key];
            }
        }
        
        // セッションを破棄
        session_destroy();
        
        // 新しいセッションを開始
        session_start();
        
        // 保持データを復元
        foreach ($keepData as $key => $value) {
            $_SESSION[$key] = $value;
        }
        
        return [
            'success' => true,
            'kept_keys' => array_keys($keepData),
            'message' => 'Partial logout completed'
        ];
    }
    
    /**
     * タイムアウトベースのログアウト
     */
    public function checkAndLogoutIfTimeout($timeout = 3600) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $lastActivity = $_SESSION['last_activity'] ?? time();
        $elapsed = time() - $lastActivity;
        
        if ($elapsed > $timeout) {
            // タイムアウト
            $this->logout();
            
            return [
                'timed_out' => true,
                'elapsed' => $elapsed,
                'timeout' => $timeout,
                'message' => 'Session timed out'
            ];
        }
        
        // アクティビティを更新
        $_SESSION['last_activity'] = time();
        
        return [
            'timed_out' => false,
            'elapsed' => $elapsed,
            'remaining' => $timeout - $elapsed
        ];
    }
    
    /**
     * ログアウト後のリダイレクト
     */
    public function logoutAndRedirect($redirectUrl = '/login') {
        $this->logout();
        
        // HTTPヘッダーでリダイレクト
        // header("Location: {$redirectUrl}");
        // exit;
        
        return [
            'success' => true,
            'redirect_url' => $redirectUrl,
            'message' => 'Logout successful, redirecting...'
        ];
    }
}

// 使用例
echo "=== ログアウトマネージャー ===\n";

// セッション初期化
session_start();
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'alice';
$_SESSION['role'] = 'admin';
$_SESSION['last_activity'] = time();
echo "セッション作成: user_id={$_SESSION['user_id']}\n";
session_write_close();

$manager = new LogoutManager();

// 標準ログアウト
echo "\n標準ログアウト:\n";
$result1 = $manager->logout();
echo "  成功: " . ($result1['success'] ? 'Yes' : 'No') . "\n";
echo "  メッセージ: {$result1['message']}\n";

// セッション確認
session_start();
echo "  ログアウト後のuser_id存在: " . (isset($_SESSION['user_id']) ? 'Yes' : 'No') . "\n";
session_write_close();

// セキュアログアウト
session_start();
$_SESSION['user_id'] = 456;
$_SESSION['username'] = 'bob';
session_write_close();

echo "\nセキュアログアウト:\n";
$result2 = $manager->secureLogout();
echo "  旧セッションID: {$result2['old_session_id']}\n";
echo "  新セッションID: {$result2['new_session_id']}\n";

// タイムアウトチェック
session_start();
$_SESSION['user_id'] = 789;
$_SESSION['last_activity'] = time() - 7200;  // 2時間前
session_write_close();

echo "\nタイムアウトチェック:\n";
$result3 = $manager->checkAndLogoutIfTimeout(3600);
echo "  タイムアウト: " . ($result3['timed_out'] ? 'Yes' : 'No') . "\n";
echo "  経過時間: {$result3['elapsed']}秒\n";

// 部分的ログアウト
session_start();
$_SESSION['user_id'] = 999;
$_SESSION['preferences'] = ['theme' => 'dark', 'lang' => 'ja'];
$_SESSION['temp_data'] = 'should be removed';
session_write_close();

echo "\n部分的ログアウト(preferencesを保持):\n";
$result4 = $manager->partialLogout(['preferences']);
echo "  保持されたキー: " . implode(', ', $result4['kept_keys']) . "\n";

session_start();
echo "  preferences存在: " . (isset($_SESSION['preferences']) ? 'Yes' : 'No') . "\n";
echo "  user_id存在: " . (isset($_SESSION['user_id']) ? 'Yes' : 'No') . "\n";

例2: セッションクリーンアップシステム

class SessionCleanupSystem {
    private $sessionPath;
    
    /**
     * クリーンアップシステムを初期化
     */
    public function __construct($sessionPath = null) {
        $this->sessionPath = $sessionPath ?? session_save_path();
        
        if (empty($this->sessionPath)) {
            $this->sessionPath = sys_get_temp_dir();
        }
    }
    
    /**
     * 現在のセッションを完全にクリーンアップ
     */
    public function cleanupCurrentSession() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $sessionId = session_id();
        $sessionFile = $this->getSessionFilePath($sessionId);
        
        // セッション変数をクリア
        $_SESSION = [];
        
        // セッションクッキーを削除
        $this->deleteSessionCookie();
        
        // セッションを破棄
        $destroyed = session_destroy();
        
        // セッションファイルの存在確認
        $fileDeleted = !file_exists($sessionFile);
        
        return [
            'session_id' => $sessionId,
            'session_file' => $sessionFile,
            'destroyed' => $destroyed,
            'file_deleted' => $fileDeleted,
            'cookie_deleted' => true
        ];
    }
    
    /**
     * セッションファイルパスを取得
     */
    private function getSessionFilePath($sessionId) {
        return $this->sessionPath . '/sess_' . $sessionId;
    }
    
    /**
     * セッションクッキーを削除
     */
    private function deleteSessionCookie() {
        if (!isset($_COOKIE[session_name()])) {
            return false;
        }
        
        $params = session_get_cookie_params();
        
        return setcookie(
            session_name(),
            '',
            [
                'expires' => time() - 3600,
                'path' => $params['path'],
                'domain' => $params['domain'],
                'secure' => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'] ?? 'Lax'
            ]
        );
    }
    
    /**
     * 古いセッションファイルをクリーンアップ
     */
    public function cleanupOldSessions($maxAge = 86400) {
        if (!is_dir($this->sessionPath)) {
            return [
                'error' => 'Session path not found',
                'path' => $this->sessionPath
            ];
        }
        
        $files = glob($this->sessionPath . '/sess_*');
        $deleted = 0;
        $failed = 0;
        $cutoff = time() - $maxAge;
        
        foreach ($files as $file) {
            if (filemtime($file) < $cutoff) {
                if (unlink($file)) {
                    $deleted++;
                } else {
                    $failed++;
                }
            }
        }
        
        return [
            'total_files' => count($files),
            'deleted' => $deleted,
            'failed' => $failed,
            'max_age' => $maxAge,
            'cutoff_date' => date('Y-m-d H:i:s', $cutoff)
        ];
    }
    
    /**
     * すべてのセッションファイルを削除
     */
    public function destroyAllSessions() {
        if (!is_dir($this->sessionPath)) {
            return ['error' => 'Session path not found'];
        }
        
        $files = glob($this->sessionPath . '/sess_*');
        $deleted = 0;
        $failed = 0;
        
        foreach ($files as $file) {
            if (unlink($file)) {
                $deleted++;
            } else {
                $failed++;
            }
        }
        
        return [
            'total_files' => count($files),
            'deleted' => $deleted,
            'failed' => $failed
        ];
    }
    
    /**
     * 特定のセッションIDを破棄
     */
    public function destroySessionById($sessionId) {
        $sessionFile = $this->getSessionFilePath($sessionId);
        
        if (!file_exists($sessionFile)) {
            return [
                'success' => false,
                'error' => 'Session file not found',
                'session_id' => $sessionId
            ];
        }
        
        $deleted = unlink($sessionFile);
        
        return [
            'success' => $deleted,
            'session_id' => $sessionId,
            'file' => $sessionFile
        ];
    }
    
    /**
     * セッションファイルの統計情報
     */
    public function getSessionStatistics() {
        if (!is_dir($this->sessionPath)) {
            return ['error' => 'Session path not found'];
        }
        
        $files = glob($this->sessionPath . '/sess_*');
        $totalSize = 0;
        $oldestTime = time();
        $newestTime = 0;
        
        foreach ($files as $file) {
            $totalSize += filesize($file);
            $mtime = filemtime($file);
            
            if ($mtime < $oldestTime) {
                $oldestTime = $mtime;
            }
            if ($mtime > $newestTime) {
                $newestTime = $mtime;
            }
        }
        
        return [
            'total_sessions' => count($files),
            'total_size' => $totalSize,
            'total_size_mb' => round($totalSize / 1024 / 1024, 2),
            'oldest_session' => date('Y-m-d H:i:s', $oldestTime),
            'newest_session' => date('Y-m-d H:i:s', $newestTime),
            'session_path' => $this->sessionPath
        ];
    }
    
    /**
     * セッションファイルをバックアップしてから削除
     */
    public function backupAndDestroy($backupDir) {
        if (!is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }
        
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $sessionId = session_id();
        $sessionFile = $this->getSessionFilePath($sessionId);
        
        if (file_exists($sessionFile)) {
            $backupFile = $backupDir . '/sess_' . $sessionId . '_' . date('YmdHis');
            copy($sessionFile, $backupFile);
        }
        
        $cleanup = $this->cleanupCurrentSession();
        
        return [
            'backed_up' => file_exists($sessionFile),
            'backup_file' => $backupFile ?? null,
            'cleanup' => $cleanup
        ];
    }
}

// 使用例
echo "=== セッションクリーンアップシステム ===\n";

$cleanup = new SessionCleanupSystem();

// 現在のセッションをクリーンアップ
session_start();
$_SESSION['test'] = 'data';
$sessionId = session_id();
echo "セッションID: {$sessionId}\n";
session_write_close();

echo "\n現在のセッションをクリーンアップ:\n";
$result = $cleanup->cleanupCurrentSession();
echo "  破棄成功: " . ($result['destroyed'] ? 'Yes' : 'No') . "\n";
echo "  ファイル削除: " . ($result['file_deleted'] ? 'Yes' : 'No') . "\n";

// セッション統計
echo "\nセッション統計:\n";
$stats = $cleanup->getSessionStatistics();
echo "  総セッション数: {$stats['total_sessions']}\n";
echo "  総サイズ: {$stats['total_size_mb']} MB\n";
echo "  セッションパス: {$stats['session_path']}\n";

// 古いセッションをクリーンアップ
echo "\n古いセッションをクリーンアップ:\n";
$oldCleanup = $cleanup->cleanupOldSessions(3600);  // 1時間より古い
echo "  削除: {$oldCleanup['deleted']}件\n";
echo "  失敗: {$oldCleanup['failed']}件\n";

例3: セキュアセッション破棄システム

class SecureSessionDestroyer {
    private $auditLog = [];
    
    /**
     * 監査ログ付きでセッションを破棄
     */
    public function destroyWithAudit($reason = 'manual') {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // 破棄前の情報を記録
        $auditEntry = [
            'timestamp' => time(),
            'session_id' => session_id(),
            'user_id' => $_SESSION['user_id'] ?? null,
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'reason' => $reason,
            'session_data_keys' => array_keys($_SESSION)
        ];
        
        // セッションを破棄
        $_SESSION = [];
        $this->deleteSessionCookie();
        $destroyed = session_destroy();
        
        $auditEntry['destroyed'] = $destroyed;
        $auditEntry['destroyed_at'] = time();
        
        // 監査ログに記録
        $this->auditLog[] = $auditEntry;
        
        return $auditEntry;
    }
    
    /**
     * セッションクッキーを安全に削除
     */
    private function deleteSessionCookie() {
        $params = session_get_cookie_params();
        
        setcookie(
            session_name(),
            '',
            [
                'expires' => 1,  // 1970/1/1
                'path' => $params['path'],
                'domain' => $params['domain'],
                'secure' => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'] ?? 'Strict'
            ]
        );
    }
    
    /**
     * セッションハイジャック検出時の破棄
     */
    public function destroyOnHijackDetection() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // ハイジャック検出ロジック
        $detected = $this->detectSessionHijack();
        
        if ($detected['is_hijacked']) {
            $this->destroyWithAudit('hijack_detected');
            
            // セキュリティログに記録
            $this->logSecurityIncident('session_hijack', $detected);
            
            return [
                'destroyed' => true,
                'reason' => 'Session hijack detected',
                'details' => $detected
            ];
        }
        
        return [
            'destroyed' => false,
            'safe' => true
        ];
    }
    
    /**
     * セッションハイジャックを検出(簡易版)
     */
    private function detectSessionHijack() {
        $currentIp = $_SERVER['REMOTE_ADDR'] ?? '';
        $currentUserAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        
        $storedIp = $_SESSION['security_ip'] ?? $currentIp;
        $storedUserAgent = $_SESSION['security_user_agent'] ?? $currentUserAgent;
        
        $ipMismatch = ($storedIp !== $currentIp);
        $uaMismatch = ($storedUserAgent !== $currentUserAgent);
        
        return [
            'is_hijacked' => $ipMismatch || $uaMismatch,
            'ip_mismatch' => $ipMismatch,
            'ua_mismatch' => $uaMismatch,
            'stored_ip' => $storedIp,
            'current_ip' => $currentIp
        ];
    }
    
    /**
     * セキュリティインシデントをログに記録
     */
    private function logSecurityIncident($type, $details) {
        $incident = [
            'type' => $type,
            'timestamp' => time(),
            'details' => $details,
            'session_id' => session_id(),
            'user_id' => $_SESSION['user_id'] ?? null
        ];
        
        // 実際の実装ではファイルやDBに記録
        error_log(json_encode($incident));
        
        return true;
    }
    
    /**
     * 権限変更時にセッションを再作成
     */
    public function recreateOnPrivilegeChange($oldRole, $newRole) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // 現在のセッションデータを保存
        $sessionData = $_SESSION;
        
        // 古いセッションを破棄
        $this->destroyWithAudit('privilege_change');
        
        // 新しいセッションを開始
        session_start();
        session_regenerate_id(true);
        
        // データを復元(セキュリティ情報は除外)
        foreach ($sessionData as $key => $value) {
            if (!in_array($key, ['security_ip', 'security_user_agent'])) {
                $_SESSION[$key] = $value;
            }
        }
        
        // 新しいロールを設定
        $_SESSION['role'] = $newRole;
        
        // セキュリティ情報を再設定
        $_SESSION['security_ip'] = $_SERVER['REMOTE_ADDR'] ?? '';
        $_SESSION['security_user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? '';
        $_SESSION['privilege_changed_at'] = time();
        
        return [
            'old_role' => $oldRole,
            'new_role' => $newRole,
            'new_session_id' => session_id()
        ];
    }
    
    /**
     * 複数回のログイン失敗後にセッションを破棄
     */
    public function destroyOnLoginFailure($maxAttempts = 5) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $attempts = $_SESSION['login_attempts'] ?? 0;
        $attempts++;
        
        if ($attempts >= $maxAttempts) {
            // 上限に達したらセッションを破棄
            $this->destroyWithAudit('max_login_attempts');
            
            return [
                'destroyed' => true,
                'reason' => 'Maximum login attempts exceeded',
                'attempts' => $attempts
            ];
        }
        
        $_SESSION['login_attempts'] = $attempts;
        
        return [
            'destroyed' => false,
            'attempts' => $attempts,
            'remaining' => $maxAttempts - $attempts
        ];
    }
    
    /**
     * 監査ログを取得
     */
    public function getAuditLog() {
        return $this->auditLog;
    }
    
    /**
     * 緊急セッション破棄(すべてのユーザー)
     */
    public function emergencyDestroyAll($sessionPath = null) {
        $path = $sessionPath ?? session_save_path();
        
        if (empty($path)) {
            $path = sys_get_temp_dir();
        }
        
        $files = glob($path . '/sess_*');
        $destroyed = 0;
        
        foreach ($files as $file) {
            if (unlink($file)) {
                $destroyed++;
            }
        }
        
        // 監査ログに記録
        $this->auditLog[] = [
            'type' => 'emergency_destroy_all',
            'timestamp' => time(),
            'destroyed_count' => $destroyed,
            'total_files' => count($files)
        ];
        
        return [
            'emergency' => true,
            'destroyed' => $destroyed,
            'total' => count($files)
        ];
    }
}

// 使用例
echo "=== セキュアセッション破棄 ===\n";

$destroyer = new SecureSessionDestroyer();

// 監査ログ付き破棄
session_start();
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'alice';
session_write_close();

echo "監査ログ付き破棄:\n";
$audit = $destroyer->destroyWithAudit('user_logout');
echo "  セッションID: {$audit['session_id']}\n";
echo "  理由: {$audit['reason']}\n";
echo "  破棄成功: " . ($audit['destroyed'] ? 'Yes' : 'No') . "\n";

// ログイン失敗による破棄
session_start();
$_SESSION['login_attempts'] = 4;
session_write_close();

echo "\nログイン失敗チェック:\n";
$failResult = $destroyer->destroyOnLoginFailure(5);
echo "  破棄: " . ($failResult['destroyed'] ? 'Yes' : 'No') . "\n";
echo "  試行回数: {$failResult['attempts']}\n";

// もう一度失敗(上限到達)
session_start();
$failResult2 = $destroyer->destroyOnLoginFailure(5);
echo "  2回目 - 破棄: " . ($failResult2['destroyed'] ? 'Yes' : 'No') . "\n";

// 権限変更時の再作成
session_start();
$_SESSION['user_id'] = 456;
$_SESSION['role'] = 'user';
session_write_close();

echo "\n権限変更時の再作成:\n";
$recreate = $destroyer->recreateOnPrivilegeChange('user', 'admin');
echo "  旧ロール: {$recreate['old_role']}\n";
echo "  新ロール: {$recreate['new_role']}\n";
echo "  新セッションID: {$recreate['new_session_id']}\n";

例4: マルチセッション管理システム

class MultiSessionManager {
    private $sessions = [];
    
    /**
     * 複数のセッションを追跡
     */
    public function trackSession($identifier) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $this->sessions[$identifier] = [
            'session_id' => session_id(),
            'created_at' => time(),
            'data_snapshot' => $_SESSION
        ];
        
        return $this->sessions[$identifier];
    }
    
    /**
     * 特定のセッションを破棄
     */
    public function destroySession($identifier) {
        if (!isset($this->sessions[$identifier])) {
            return [
                'success' => false,
                'error' => 'Session identifier not found'
            ];
        }
        
        $sessionData = $this->sessions[$identifier];
        $currentSessionId = session_id();
        
        // 該当セッションに切り替え
        if ($currentSessionId !== $sessionData['session_id']) {
            session_write_close();
            session_id($sessionData['session_id']);
            session_start();
        }
        
        // セッションを破棄
        $_SESSION = [];
        $destroyed = session_destroy();
        
        // 元のセッションに戻す
        if ($currentSessionId !== $sessionData['session_id']) {
            session_id($currentSessionId);
            session_start();
        }
        
        unset($this->sessions[$identifier]);
        
        return [
            'success' => $destroyed,
            'identifier' => $identifier,
            'session_id' => $sessionData['session_id']
        ];
    }
    
    /**
     * すべての追跡セッションを破棄
     */
    public function destroyAllTracked() {
        $destroyed = 0;
        $failed = 0;
        
        foreach (array_keys($this->sessions) as $identifier) {
            $result = $this->destroySession($identifier);
            
            if ($result['success']) {
                $destroyed++;
            } else {
                $failed++;
            }
        }
        
        return [
            'total' => $destroyed + $failed,
            'destroyed' => $destroyed,
            'failed' => $failed
        ];
    }
    
    /**
     * セッション一覧を取得
     */
    public function listSessions() {
        $list = [];
        
        foreach ($this->sessions as $identifier => $data) {
            $list[] = [
                'identifier' => $identifier,
                'session_id' => $data['session_id'],
                'age' => time() - $data['created_at'],
                'data_keys' => array_keys($data['data_snapshot'])
            ];
        }
        
        return $list;
    }
    
    /**
     * 最も古いセッションを破棄
     */
    public function destroyOldest() {
        if (empty($this->sessions)) {
            return ['success' => false, 'error' => 'No sessions to destroy'];
        }
        
        $oldest = null;
        $oldestTime = time();
        
        foreach ($this->sessions as $identifier => $data) {
            if ($data['created_at'] < $oldestTime) {
                $oldestTime = $data['created_at'];
                $oldest = $identifier;
            }
        }
        
        return $this->destroySession($oldest);
    }
    
    /**
     * 非アクティブなセッションを破棄
     */
    public function destroyInactive($timeout = 3600) {
        $destroyed = [];
        $cutoff = time() - $timeout;
        
        foreach ($this->sessions as $identifier => $data) {
            if ($data['created_at'] < $cutoff) {
                $result = $this->destroySession($identifier);
                if ($result['success']) {
                    $destroyed[] = $identifier;
                }
            }
        }
        
        return [
            'destroyed' => count($destroyed),
            'identifiers' => $destroyed
        ];
    }
}

// 使用例
echo "=== マルチセッション管理 ===\n";

$multiManager = new MultiSessionManager();

// 複数のセッションを作成・追跡
echo "セッション作成:\n";

session_start();
$_SESSION['device'] = 'desktop';
$_SESSION['user_id'] = 111;
$multiManager->trackSession('desktop');
echo "  Desktop セッション追跡\n";
session_write_close();

session_start();
$_SESSION['device'] = 'mobile';
$_SESSION['user_id'] = 111;
$multiManager->trackSession('mobile');
echo "  Mobile セッション追跡\n";
session_write_close();

session_start();
$_SESSION['device'] = 'tablet';
$_SESSION['user_id'] = 111;
$multiManager->trackSession('tablet');
echo "  Tablet セッション追跡\n";
session_write_close();

// セッション一覧
echo "\nセッション一覧:\n";
foreach ($multiManager->listSessions() as $session) {
    echo "  {$session['identifier']}: {$session['session_id']}\n";
}

// 特定のセッションを破棄
echo "\nMobileセッションを破棄:\n";
$result = $multiManager->destroySession('mobile');
echo "  成功: " . ($result['success'] ? 'Yes' : 'No') . "\n";

// 残りのセッション
echo "\n残りのセッション:\n";
foreach ($multiManager->listSessions() as $session) {
    echo "  {$session['identifier']}\n";
}

// すべて破棄
echo "\nすべて破棄:\n";
$destroyAll = $multiManager->destroyAllTracked();
echo "  破棄: {$destroyAll['destroyed']}件\n";
echo "  失敗: {$destroyAll['failed']}件\n";

例5: セッションタイムアウト処理システム

class SessionTimeoutHandler {
    private $idleTimeout;
    private $absoluteTimeout;
    
    /**
     * タイムアウトハンドラーを初期化
     */
    public function __construct($idleTimeout = 1800, $absoluteTimeout = 7200) {
        $this->idleTimeout = $idleTimeout;      // 30分
        $this->absoluteTimeout = $absoluteTimeout;  // 2時間
    }
    
    /**
     * タイムアウトをチェックして必要なら破棄
     */
    public function checkTimeout() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $currentTime = time();
        $lastActivity = $_SESSION['last_activity'] ?? $currentTime;
        $sessionStart = $_SESSION['session_start'] ?? $currentTime;
        
        $idleTime = $currentTime - $lastActivity;
        $totalTime = $currentTime - $sessionStart;
        
        // アイドルタイムアウトチェック
        if ($idleTime > $this->idleTimeout) {
            $this->destroySession('idle_timeout');
            
            return [
                'timed_out' => true,
                'reason' => 'idle_timeout',
                'idle_time' => $idleTime,
                'threshold' => $this->idleTimeout
            ];
        }
        
        // 絶対タイムアウトチェック
        if ($totalTime > $this->absoluteTimeout) {
            $this->destroySession('absolute_timeout');
            
            return [
                'timed_out' => true,
                'reason' => 'absolute_timeout',
                'total_time' => $totalTime,
                'threshold' => $this->absoluteTimeout
            ];
        }
        
        // タイムアウトなし - アクティビティを更新
        $_SESSION['last_activity'] = $currentTime;
        
        if (!isset($_SESSION['session_start'])) {
            $_SESSION['session_start'] = $currentTime;
        }
        
        return [
            'timed_out' => false,
            'idle_time' => $idleTime,
            'total_time' => $totalTime,
            'idle_remaining' => $this->idleTimeout - $idleTime,
            'absolute_remaining' => $this->absoluteTimeout - $totalTime
        ];
    }
    
    /**
     * セッションを破棄
     */
    private function destroySession($reason) {
        $sessionId = session_id();
        
        $_SESSION = [];
        
        if (isset($_COOKIE[session_name()])) {
            setcookie(session_name(), '', time() - 3600, '/');
        }
        
        session_destroy();
        
        // タイムアウト情報を新しいセッションに記録
        session_start();
        $_SESSION['timeout_info'] = [
            'reason' => $reason,
            'timestamp' => time(),
            'old_session_id' => $sessionId
        ];
        session_write_close();
    }
    
    /**
     * スライディングウィンドウ方式
     */
    public function slidingWindow() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $currentTime = time();
        $lastActivity = $_SESSION['last_activity'] ?? $currentTime;
        
        if ($currentTime - $lastActivity > $this->idleTimeout) {
            $this->destroySession('sliding_window_timeout');
            return ['timed_out' => true];
        }
        
        // アクティビティを更新(スライディング)
        $_SESSION['last_activity'] = $currentTime;
        
        return [
            'timed_out' => false,
            'expires_at' => $currentTime + $this->idleTimeout
        ];
    }
    
    /**
     * 警告付きタイムアウト
     */
    public function checkWithWarning($warningThreshold = 300) {
        $check = $this->checkTimeout();
        
        if ($check['timed_out']) {
            return $check;
        }
        
        // 警告しきい値をチェック
        $idleRemaining = $check['idle_remaining'] ?? PHP_INT_MAX;
        $absoluteRemaining = $check['absolute_remaining'] ?? PHP_INT_MAX;
        
        $minRemaining = min($idleRemaining, $absoluteRemaining);
        
        if ($minRemaining < $warningThreshold) {
            $check['warning'] = true;
            $check['warning_message'] = "セッションがあと{$minRemaining}秒で期限切れになります";
        } else {
            $check['warning'] = false;
        }
        
        return $check;
    }
    
    /**
     * セッション延長
     */
    public function extendSession($additionalTime = 1800) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // 最終アクティビティを更新
        $_SESSION['last_activity'] = time();
        
        // オプション: セッション開始時刻も更新(完全リセット)
        // $_SESSION['session_start'] = time();
        
        return [
            'extended' => true,
            'new_expiry' => time() + $this->idleTimeout
        ];
    }
    
    /**
     * タイムアウト設定を動的に変更
     */
    public function adjustTimeout($idleTimeout = null, $absoluteTimeout = null) {
        if ($idleTimeout !== null) {
            $this->idleTimeout = $idleTimeout;
        }
        
        if ($absoluteTimeout !== null) {
            $this->absoluteTimeout = $absoluteTimeout;
        }
        
        return [
            'idle_timeout' => $this->idleTimeout,
            'absolute_timeout' => $this->absoluteTimeout
        ];
    }
}

// 使用例
echo "=== セッションタイムアウト処理 ===\n";

$handler = new SessionTimeoutHandler(60, 180);  // アイドル60秒、絶対180秒

// セッション開始
session_start();
$_SESSION['user_id'] = 123;
$_SESSION['session_start'] = time() - 50;  // 50秒前に開始
$_SESSION['last_activity'] = time() - 50;
session_write_close();

// タイムアウトチェック
echo "タイムアウトチェック:\n";
$check1 = $handler->checkTimeout();
echo "  タイムアウト: " . ($check1['timed_out'] ? 'Yes' : 'No') . "\n";
echo "  アイドル残り: {$check1['idle_remaining']}秒\n";
echo "  絶対残り: {$check1['absolute_remaining']}秒\n";

// アイドルタイムアウトをシミュレート
session_start();
$_SESSION['last_activity'] = time() - 70;  // 70秒前
session_write_close();

echo "\nアイドルタイムアウトシミュレート:\n";
$check2 = $handler->checkTimeout();
echo "  タイムアウト: " . ($check2['timed_out'] ? 'Yes' : 'No') . "\n";
if ($check2['timed_out']) {
    echo "  理由: {$check2['reason']}\n";
    echo "  アイドル時間: {$check2['idle_time']}秒\n";
}

// 警告付きチェック
session_start();
$_SESSION['user_id'] = 456;
$_SESSION['session_start'] = time();
$_SESSION['last_activity'] = time() - 50;  // 残り10秒
session_write_close();

echo "\n警告付きチェック:\n";
$check3 = $handler->checkWithWarning(15);
echo "  タイムアウト: " . ($check3['timed_out'] ? 'Yes' : 'No') . "\n";
echo "  警告: " . ($check3['warning'] ? 'Yes' : 'No') . "\n";
if ($check3['warning']) {
    echo "  {$check3['warning_message']}\n";
}

// セッション延長
echo "\nセッション延長:\n";
$extend = $handler->extendSession();
echo "  延長: " . ($extend['extended'] ? 'Yes' : 'No') . "\n";

例6: セッション移行システム

class SessionMigrationSystem {
    /**
     * 古いセッションから新しいセッションへ移行
     */
    public function migrate($selectiveKeys = null) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $oldSessionId = session_id();
        $oldData = $_SESSION;
        
        // 古いセッションを破棄
        $_SESSION = [];
        session_destroy();
        
        // 新しいセッションを開始
        session_start();
        $newSessionId = session_id();
        
        // データを移行
        if ($selectiveKeys === null) {
            // すべてのデータを移行
            $_SESSION = $oldData;
        } else {
            // 選択的に移行
            foreach ($selectiveKeys as $key) {
                if (isset($oldData[$key])) {
                    $_SESSION[$key] = $oldData[$key];
                }
            }
        }
        
        // 移行情報を記録
        $_SESSION['migration_info'] = [
            'migrated_at' => time(),
            'old_session_id' => $oldSessionId,
            'new_session_id' => $newSessionId
        ];
        
        return [
            'old_session_id' => $oldSessionId,
            'new_session_id' => $newSessionId,
            'migrated_keys' => $selectiveKeys ?? array_keys($oldData)
        ];
    }
    
    /**
     * セキュアな移行(フィルタリング付き)
     */
    public function secureMigrate($blacklist = []) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $oldData = $_SESSION;
        $oldSessionId = session_id();
        
        // 古いセッションを破棄
        $_SESSION = [];
        session_destroy();
        
        // 新しいセッションを開始
        session_start();
        $newSessionId = session_id();
        
        // ブラックリストを除外して移行
        $migrated = 0;
        foreach ($oldData as $key => $value) {
            if (!in_array($key, $blacklist)) {
                $_SESSION[$key] = $value;
                $migrated++;
            }
        }
        
        // セキュリティ情報を更新
        $_SESSION['security_info'] = [
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'migrated_at' => time()
        ];
        
        return [
            'old_session_id' => $oldSessionId,
            'new_session_id' => $newSessionId,
            'migrated_count' => $migrated,
            'excluded_keys' => $blacklist
        ];
    }
    
    /**
     * バージョン間の移行
     */
    public function migrateVersion($fromVersion, $toVersion) {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        $oldData = $_SESSION;
        
        // バージョン間の変換ロジック
        $transformed = $this->transformData($oldData, $fromVersion, $toVersion);
        
        // セッションを再作成
        session_destroy();
        session_start();
        
        $_SESSION = $transformed;
        $_SESSION['version'] = $toVersion;
        $_SESSION['version_migrated_at'] = time();
        
        return [
            'from_version' => $fromVersion,
            'to_version' => $toVersion,
            'transformed_keys' => count($transformed)
        ];
    }
    
    /**
     * データを変換
     */
    private function transformData($data, $fromVersion, $toVersion) {
        // バージョン固有の変換ロジック
        if ($fromVersion === '1.0' && $toVersion === '2.0') {
            // 例: キー名の変更
            if (isset($data['user_name'])) {
                $data['username'] = $data['user_name'];
                unset($data['user_name']);
            }
        }
        
        return $data;
    }
    
    /**
     * ロールバック機能付き移行
     */
    public function migrateWithRollback($backupKey = 'migration_backup') {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        
        // 現在のデータをバックアップ
        $backup = [
            'session_id' => session_id(),
            'data' => $_SESSION,
            'timestamp' => time()
        ];
        
        // 一時的にバックアップを保存(例:ファイル)
        file_put_contents(
            sys_get_temp_dir() . "/{$backupKey}.json",
            json_encode($backup)
        );
        
        // 移行実行
        $result = $this->migrate();
        
        return array_merge($result, [
            'backup_created' => true,
            'backup_key' => $backupKey
        ]);
    }
    
    /**
     * ロールバック実行
     */
    public function rollback($backupKey = 'migration_backup') {
        $backupFile = sys_get_temp_dir() . "/{$backupKey}.json";
        
        if (!file_exists($backupFile)) {
            return [
                'success' => false,
                'error' => 'Backup not found'
            ];
        }
        
        $backup = json_decode(file_get_contents($backupFile), true);
        
        // 現在のセッションを破棄
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_destroy();
        }
        
        // バックアップから復元
        session_id($backup['session_id']);
        session_start();
        $_SESSION = $backup['data'];
        
        // バックアップファイルを削除
        unlink($backupFile);
        
        return [
            'success' => true,
            'restored_session_id' => $backup['session_id'],
            'backup_timestamp' => $backup['timestamp']
        ];
    }
}

// 使用例
echo "=== セッション移行システム ===\n";

$migrator = new SessionMigrationSystem();

// セッション作成
session_start();
$_SESSION = [
    'user_id' => 123,
    'username' => 'alice',
    'email' => 'alice@example.com',
    'role' => 'admin',
    'temp_data' => 'should not migrate'
];
$oldId = session_id();
echo "旧セッションID: {$oldId}\n";
session_write_close();

// 完全移行
echo "\n完全移行:\n";
$result1 = $migrator->migrate();
echo "  新セッションID: {$result1['new_session_id']}\n";
echo "  移行キー数: " . count($result1['migrated_keys']) . "\n";
session_write_close();

// 選択的移行
session_start();
$_SESSION = [
    'user_id' => 456,
    'username' => 'bob',
    'temp' => 'temporary'
];
session_write_close();

echo "\n選択的移行:\n";
$result2 = $migrator->migrate(['user_id', 'username']);
echo "  移行されたキー: " . implode(', ', $result2['migrated_keys']) . "\n";

session_start();
echo "  temp存在: " . (isset($_SESSION['temp']) ? 'Yes' : 'No') . "\n";
session_write_close();

// セキュア移行
session_start();
$_SESSION = [
    'user_id' => 789,
    'sensitive' => 'should exclude',
    'public' => 'can migrate'
];
session_write_close();

echo "\nセキュア移行(sensitiveを除外):\n";
$result3 = $migrator->secureMigrate(['sensitive']);
echo "  移行数: {$result3['migrated_count']}\n";
echo "  除外キー: " . implode(', ', $result3['excluded_keys']) . "\n";

完全なクリーンアップ手順

// セッションを完全にクリーンアップする推奨手順

function completeSessionCleanup() {
    // 1. セッションを開始(まだの場合)
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }
    
    // 2. セッション変数をすべてクリア
    $_SESSION = [];
    
    // 3. セッションクッキーを削除
    if (isset($_COOKIE[session_name()])) {
        $params = session_get_cookie_params();
        setcookie(
            session_name(),
            '',
            [
                'expires' => time() - 3600,
                'path' => $params['path'],
                'domain' => $params['domain'],
                'secure' => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'] ?? 'Lax'
            ]
        );
    }
    
    // 4. セッションを破棄
    session_destroy();
    
    return true;
}

// 使用例
completeSessionCleanup();

まとめ

session_destroy()関数の特徴をまとめると:

できること:

  • セッションファイル(データ)の削除
  • セッションの完全な終了
  • ログアウト処理の実装

重要な注意点:

  • $_SESSION配列は残る(手動でクリアが必要)
  • セッションクッキーは削除されない(手動で削除が必要)
  • session_start()後に使用する必要がある
  • 完全なクリーンアップには追加手順が必要

他の関数との違い:

  • session_destroy(): ファイル削除、$_SESSIONは残る
  • session_abort(): 変更を破棄、元の状態に戻す
  • session_unset(): $_SESSIONをクリア、ファイルは残る
  • session_write_close(): 保存して終了

推奨される使用場面:

  • ユーザーログアウト
  • セッションのリセット
  • セキュリティ上の理由でのセッション破棄
  • セッションタイムアウト処理
  • セッション移行

完全なクリーンアップ:

// 推奨される完全なクリーンアップ
session_start();
$_SESSION = [];  // 変数をクリア

// クッキーを削除
if (isset($_COOKIE[session_name()])) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(), 
        '', 
        time() - 3600,
        $params['path'],
        $params['domain'],
        $params['secure'],
        $params['httponly']
    );
}

session_destroy();  // セッションを破棄

ベストプラクティス:

// 1. ログアウト時
function logout() {
    session_start();
    $_SESSION = [];
    setcookie(session_name(), '', time() - 3600, '/');
    session_destroy();
}

// 2. セキュアなログアウト
function secureLogout() {
    session_start();
    $_SESSION = [];
    session_destroy();
    session_start();  // 新しいセッション
    session_regenerate_id(true);
}

// 3. タイムアウト処理
function checkTimeout($timeout = 1800) {
    session_start();
    $lastActivity = $_SESSION['last_activity'] ?? time();
    
    if (time() - $lastActivity > $timeout) {
        session_destroy();
        return true;  // タイムアウト
    }
    
    $_SESSION['last_activity'] = time();
    return false;
}

関連関数:

  • session_start(): セッション開始
  • session_regenerate_id(): セッションID再生成
  • session_unset(): $_SESSIONをクリア
  • session_abort(): 変更を破棄
  • setcookie(): クッキー設定

session_destroy()は、セッションを完全に終了するための関数です。ログアウト処理では、$_SESSION配列のクリアとセッションクッキーの削除も忘れずに行いましょう!

タイトルとURLをコピーしました