こんにちは!今回は、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配列のクリアとセッションクッキーの削除も忘れずに行いましょう!
