[PHP]session_unset()完全解説|セッション変数の削除方法とsession_destroy()との違いを徹底解説

PHP

はじめに

PHPでセッションを扱うとき、「セッションに保存したデータを消したい」という場面は頻繁に訪れます。ログアウト処理・一時データのクリア・ウィザードフォームのリセットなど、目的に応じて正しい関数を選ぶことが重要です。

session_unset()$_SESSION スーパーグローバル変数に登録されているすべてのセッション変数を削除する関数です。「セッション変数を空にする」操作であり、セッションIDやセッションファイル自体は残ります。session_destroy() との違いをしっかり理解して使い分けることで、意図しないデータ漏洩やセッション管理のミスを防げます。


関数の概要

項目内容
関数名session_unset()
所属PHP セッション関数
導入バージョンPHP 4以降
PHP 8.x対応済み
戻り値true(成功) / false(失敗)

構文

session_unset(): bool

パラメータはありません。呼び出すだけで $_SESSION 内のすべての変数が削除されます。


session_unset() が行うこと・行わないこと

項目動作
$_SESSION の全変数を削除する✅ 行う
セッションIDを削除する❌ 行わない(IDは維持)
セッションファイルを削除する❌ 行わない(ファイルは残る)
セッションを終了する❌ 行わない(まだ ACTIVE のまま)
Cookie を削除する❌ 行わない(Cookie は残る)

$_SESSION = [] との違い

session_unset()$_SESSION = [] はどちらも $_SESSION を空にしますが、以下の違いがあります。

方法動作推奨度
session_unset()PHP 関数として正式に全変数を登録解除する✅ 推奨
$_SESSION = []$_SESSION に空配列を代入する✅ 同等(PHP 5.4以降)
unset($_SESSION)$_SESSION 変数自体を削除する(セッションデータは残る)❌ 非推奨・意図しない動作の原因に

⚠️ unset($_SESSION) は絶対に使わないでください。 $_SESSION 変数自体を削除しても、セッションデータのストレージからの削除にはならず、次のリクエストでデータが復元されてしまいます。


session_unset() vs session_destroy() vs session_reset() 比較

関数$_SESSIONセッションIDストレージCookieユースケース
session_unset()空にする維持維持(空で書き込み)維持ログアウト前のデータ削除・部分クリア
session_destroy()維持($_SESSION は残る)削除ファイル削除維持セッション完全破棄
session_reset()元の状態に戻す維持変更なし維持リクエスト中の変更をロールバック
$_SESSION = []空にする維持維持維持session_unset() と実質同等

完全なログアウト処理には session_unset()session_destroy() → Cookie削除 の3点セットが必要です。


基本的な使い方

<?php
session_start();

// セッションにデータをセット
$_SESSION['user_id']  = 42;
$_SESSION['username'] = 'alice';
$_SESSION['cart']     = ['item_a', 'item_b'];

var_dump(count($_SESSION)); // int(3)

// すべてのセッション変数を削除
session_unset();

var_dump(count($_SESSION)); // int(0)
var_dump(session_id());     // まだIDは残っている

特定のキーだけ削除したい場合

session_unset() は全削除なので、特定のキーだけ削除するには unset() を使います

<?php
session_start();
$_SESSION['user_id']  = 42;
$_SESSION['temp_data'] = 'temporary';

// 特定のキーだけ削除
unset($_SESSION['temp_data']);

var_dump(isset($_SESSION['user_id']));   // true(残っている)
var_dump(isset($_SESSION['temp_data'])); // false(削除された)

実践的なクラスベースの使用例

例1:完全ログアウト処理クラス

<?php
/**
 * session_unset() を核とした完全なログアウト処理クラス
 * セッション変数削除・セッション破棄・Cookie削除を確実に行う
 */
class LogoutHandler
{
    /**
     * 完全なログアウト処理を実行する
     * session_unset() → session_destroy() → Cookie削除 の3ステップ
     */
    public function logout(): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            echo "セッションが開始されていません\n";
            return;
        }

        $sessionName = session_name();
        $userId      = $_SESSION['user_id'] ?? null;

        // ステップ1: セッション変数をすべて削除
        session_unset();
        echo "ステップ1: セッション変数を削除しました\n";

        // ステップ2: セッションファイルを削除
        session_destroy();
        echo "ステップ2: セッションを破棄しました\n";

        // ステップ3: セッションCookieを削除(ブラウザ側)
        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(
                $sessionName, '',
                [
                    'expires'  => time() - 86400,
                    'path'     => $params['path'],
                    'domain'   => $params['domain'],
                    'secure'   => $params['secure'],
                    'httponly' => $params['httponly'],
                    'samesite' => $params['samesite'],
                ]
            );
            echo "ステップ3: Cookie を削除しました\n";
        }

        echo "ログアウト完了(ユーザーID: {$userId})\n";
    }

    /**
     * ソフトログアウト:セッション変数だけ消してIDは残す
     * 同一ブラウザでの即座な再ログインを前提とする場面で使用
     */
    public function softLogout(): void
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            return;
        }

        session_unset(); // データだけ消す
        echo "ソフトログアウト: セッション変数を削除しました(IDは維持)\n";
        echo "残っているセッションID: " . session_id() . "\n";
    }
}

session_start();
$_SESSION['user_id']   = 42;
$_SESSION['username']  = 'alice';
$_SESSION['last_page'] = '/dashboard';

$handler = new LogoutHandler();
$handler->logout();

/*
出力例:
ステップ1: セッション変数を削除しました
ステップ2: セッションを破棄しました
ステップ3: Cookie を削除しました
ログアウト完了(ユーザーID: 42)
*/

例2:フラッシュメッセージ管理クラス

<?php
/**
 * フラッシュメッセージ(1リクエストだけ保持するメッセージ)管理クラス
 * session_unset() の代わりに unset() でキー単位の細粒度な削除を行う
 */
class FlashMessageManager
{
    private const SESSION_KEY = '_flash';
    private const PREV_KEY    = '_flash_prev';

    public function __construct()
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        $this->rotateFlash();
    }

    /**
     * リクエスト開始時に前のフラッシュを「読み取り済み」領域に移動し、
     * 古い読み取り済みデータは削除する
     */
    private function rotateFlash(): void
    {
        // 前のリクエストのフラッシュを削除(既に表示済み)
        if (isset($_SESSION[self::PREV_KEY])) {
            unset($_SESSION[self::PREV_KEY]); // キー単位の削除
        }

        // 現在のフラッシュを「前のリクエスト分」として移動
        if (isset($_SESSION[self::SESSION_KEY])) {
            $_SESSION[self::PREV_KEY] = $_SESSION[self::SESSION_KEY];
            unset($_SESSION[self::SESSION_KEY]);
        }
    }

    /**
     * フラッシュメッセージを追加する(次のリクエストで表示・消去される)
     */
    public function add(string $type, string $message): void
    {
        $_SESSION[self::SESSION_KEY][$type][] = $message;
    }

    /**
     * 現在のリクエストで表示できるフラッシュメッセージを取得する
     */
    public function get(string $type = ''): array
    {
        $messages = $_SESSION[self::PREV_KEY] ?? [];
        if ($type !== '') {
            return $messages[$type] ?? [];
        }
        return $messages;
    }

    /**
     * すべてのフラッシュメッセージを即座にクリアする
     */
    public function clearAll(): void
    {
        unset($_SESSION[self::SESSION_KEY], $_SESSION[self::PREV_KEY]);
        echo "フラッシュメッセージをすべて削除しました\n";
    }

    public function hasMessages(string $type = ''): bool
    {
        $messages = $this->get($type);
        return !empty($messages);
    }
}

session_start();

$flash = new FlashMessageManager();
$flash->add('success', '保存しました');
$flash->add('success', 'メール送信完了');
$flash->add('error',   'バリデーションエラーがあります');

// 次のリクエストで get() → 自動削除される仕組み
// $flash->clearAll(); // 即時全削除も可能

例3:ウィザードフォームのステップ管理クラス

<?php
/**
 * 複数ステップのフォームでセッションを使ってデータを引き継ぎ、
 * キャンセルや完了時に session_unset() でデータを消すクラス
 */
class WizardSessionManager
{
    private const KEY = 'wizard';

    public function __construct(private readonly string $wizardId)
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
    }

    public function saveStep(int $step, array $data): void
    {
        $_SESSION[self::KEY][$this->wizardId]["step{$step}"] = $data;
        $_SESSION[self::KEY][$this->wizardId]['current']     = $step;
        echo "ステップ{$step} 保存: " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n";
    }

    public function getStep(int $step): array
    {
        return $_SESSION[self::KEY][$this->wizardId]["step{$step}"] ?? [];
    }

    public function getCurrentStep(): int
    {
        return $_SESSION[self::KEY][$this->wizardId]['current'] ?? 0;
    }

    /**
     * ウィザード完了:データを取り出してセッションから削除
     */
    public function complete(): array
    {
        $data = $_SESSION[self::KEY][$this->wizardId] ?? [];
        unset($_SESSION[self::KEY][$this->wizardId]); // このウィザード分だけ削除
        echo "ウィザード完了: セッションからデータを削除しました\n";
        return $data;
    }

    /**
     * キャンセル:このウィザードのデータだけを削除
     */
    public function cancel(): void
    {
        unset($_SESSION[self::KEY][$this->wizardId]);
        echo "ウィザードキャンセル\n";
    }

    /**
     * セッション内の全ウィザードデータを削除(session_unset は全削除なので
     * ここでは専用キーの unset を使う)
     */
    public function clearAll(): void
    {
        unset($_SESSION[self::KEY]);
        echo "全ウィザードデータを削除しました\n";
    }

    /**
     * セッション全体をリセットする場合(別の用途で session_unset を呼ぶ例)
     */
    public function resetWholeSession(): void
    {
        session_unset(); // $_SESSION を完全に空にする
        echo "セッション変数を全削除しました(session_unset)\n";
    }
}

session_start();
$wizard = new WizardSessionManager('purchase');
$wizard->saveStep(1, ['name' => '山田太郎', 'email' => 'yamada@example.com']);
$wizard->saveStep(2, ['address' => '神戸市', 'zip' => '650-0000']);
$wizard->saveStep(3, ['payment' => 'credit_card']);

$result = $wizard->complete();
echo "収集データ: " . json_encode($result, JSON_UNESCAPED_UNICODE) . "\n";

/*
出力例:
ステップ1 保存: {"name":"山田太郎","email":"yamada@example.com"}
ステップ2 保存: {"address":"神戸市","zip":"650-0000"}
ステップ3 保存: {"payment":"credit_card"}
ウィザード完了: セッションからデータを削除しました
収集データ: {"step1":{...},"step2":{...},"step3":{...},"current":3}
*/

例4:セッションデータの選択的クリアクラス

<?php
/**
 * セッション変数を「カテゴリ」で分類して管理するクラス
 * session_unset() の全削除ではなく、カテゴリ単位での削除を実現する
 */
class CategorizedSessionManager
{
    private const CATEGORIES = ['auth', 'cart', 'ui', 'temp'];

    public function __construct()
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
    }

    /**
     * カテゴリ付きでセッション変数をセットする
     */
    public function set(string $category, string $key, mixed $value): void
    {
        $this->assertCategory($category);
        $_SESSION[$category][$key] = $value;
    }

    public function get(string $category, string $key, mixed $default = null): mixed
    {
        $this->assertCategory($category);
        return $_SESSION[$category][$key] ?? $default;
    }

    /**
     * 指定カテゴリのデータをすべて削除
     */
    public function clearCategory(string $category): void
    {
        $this->assertCategory($category);
        unset($_SESSION[$category]);
        echo "カテゴリ '{$category}' のセッションデータを削除しました\n";
    }

    /**
     * temp カテゴリのみ削除(リクエスト終了時の一時データ掃除)
     */
    public function clearTemp(): void
    {
        $this->clearCategory('temp');
    }

    /**
     * ログアウト時:auth と cart を削除(ui 設定は残す)
     */
    public function clearOnLogout(): void
    {
        $this->clearCategory('auth');
        $this->clearCategory('cart');
        $this->clearCategory('temp');
        echo "ログアウト: auth・cart・temp を削除しました(UI設定は維持)\n";
    }

    /**
     * 全カテゴリを削除(session_unset() と同等)
     */
    public function clearAll(): void
    {
        session_unset();
        echo "全セッション変数を削除しました(session_unset)\n";
    }

    public function dump(): void
    {
        echo "=== セッションデータ ===\n";
        foreach (self::CATEGORIES as $cat) {
            $count = count($_SESSION[$cat] ?? []);
            printf("  %-6s: %d 件\n", $cat, $count);
        }
    }

    private function assertCategory(string $category): void
    {
        if (!in_array($category, self::CATEGORIES, true)) {
            throw new \InvalidArgumentException("未定義のカテゴリ: {$category}");
        }
    }
}

session_start();
$manager = new CategorizedSessionManager();
$manager->set('auth', 'user_id', 42);
$manager->set('auth', 'role', 'admin');
$manager->set('cart', 'items', ['A', 'B', 'C']);
$manager->set('ui', 'theme', 'dark');
$manager->set('temp', 'csrf_token', bin2hex(random_bytes(16)));

$manager->dump();
$manager->clearOnLogout();
$manager->dump();

/*
出力例:
=== セッションデータ ===
  auth  : 2 件
  cart  : 1 件
  ui    : 1 件
  temp  : 1 件
ログアウト: auth・cart・temp を削除しました(UI設定は維持)
=== セッションデータ ===
  auth  : 0 件
  cart  : 0 件
  ui    : 1 件
  temp  : 0 件
*/

例5:テスト環境でのセッションリセットユーティリティクラス

<?php
/**
 * PHPUnit などのテスト環境でセッションをリセットするユーティリティクラス
 * テストケース間でセッション状態を確実にクリアする
 */
class SessionTestHelper
{
    private static array $snapshots = [];

    /**
     * テスト開始前にセッションをクリーンな状態にする
     */
    public static function setUp(array $initialData = []): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_unset();   // 前のテストのデータを削除
        } else {
            session_start([
                'name'            => 'TestSession',
                'cookie_httponly' => true,
                'use_strict_mode' => true,
            ]);
        }

        // 初期データをセット
        foreach ($initialData as $key => $value) {
            $_SESSION[$key] = $value;
        }

        echo "テストセットアップ: セッションをリセットしました\n";
        echo "  初期データ数: " . count($initialData) . " 件\n";
    }

    /**
     * 現在のセッション状態をスナップショットとして保存する
     */
    public static function snapshot(string $label): void
    {
        self::$snapshots[$label] = $_SESSION;
        echo "スナップショット [{$label}]: " . count($_SESSION) . " 件の変数\n";
    }

    /**
     * スナップショットを復元する
     */
    public static function restore(string $label): void
    {
        if (!isset(self::$snapshots[$label])) {
            throw new \RuntimeException("スナップショット '{$label}' が存在しません");
        }
        session_unset();
        $_SESSION = self::$snapshots[$label];
        echo "スナップショット [{$label}] を復元しました\n";
    }

    /**
     * テスト終了後のクリーンアップ
     */
    public static function tearDown(): void
    {
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_unset();
            echo "テストクリーンアップ: session_unset() 完了\n";
        }
        self::$snapshots = [];
    }

    public static function assertEmpty(string $message = ''): void
    {
        if (!empty($_SESSION)) {
            throw new \AssertionError(
                ($message ?: 'セッションが空であることを期待しましたが、'
                    . count($_SESSION) . ' 件のデータが残っています')
            );
        }
        echo "✅ assertEmpty: PASS\n";
    }

    public static function assertHas(string $key, string $message = ''): void
    {
        if (!isset($_SESSION[$key])) {
            throw new \AssertionError($message ?: "キー '{$key}' がセッションに存在しません");
        }
        echo "✅ assertHas('{$key}'): PASS\n";
    }
}

// テスト利用例
SessionTestHelper::setUp(['user_id' => 1, 'role' => 'admin']);
SessionTestHelper::snapshot('初期状態');

$_SESSION['cart'] = ['item_1', 'item_2'];
SessionTestHelper::assertHas('cart');

SessionTestHelper::restore('初期状態');
SessionTestHelper::assertHas('user_id');

SessionTestHelper::tearDown();
SessionTestHelper::assertEmpty();

例6:セッション変数のライフサイクル管理クラス

<?php
/**
 * セッション変数に有効期限を設定し、期限切れのものだけを
 * 自動削除するライフサイクル管理クラス
 * session_unset() ではなく unset() を使ってキー単位で制御する
 */
class SessionLifecycleManager
{
    private const EXPIRY_KEY = '_expiry';

    public function __construct()
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }
        $this->purgeExpired();
    }

    /**
     * 有効期限付きでセッション変数をセットする
     */
    public function setWithTtl(string $key, mixed $value, int $ttl): void
    {
        $_SESSION[$key] = $value;
        $_SESSION[self::EXPIRY_KEY][$key] = time() + $ttl;
        echo "セット: {$key} (TTL={$ttl}秒, 期限=" . date('H:i:s', time() + $ttl) . ")\n";
    }

    /**
     * 通常のセット(有効期限なし)
     */
    public function set(string $key, mixed $value): void
    {
        $_SESSION[$key] = $value;
        unset($_SESSION[self::EXPIRY_KEY][$key]); // 期限情報を削除
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $_SESSION[$key] ?? $default;
    }

    /**
     * 期限切れのセッション変数を削除する
     */
    private function purgeExpired(): int
    {
        $expiry  = $_SESSION[self::EXPIRY_KEY] ?? [];
        $now     = time();
        $deleted = 0;

        foreach ($expiry as $key => $expiresAt) {
            if ($expiresAt <= $now) {
                unset($_SESSION[$key]);
                unset($_SESSION[self::EXPIRY_KEY][$key]);
                $deleted++;
                echo "期限切れ削除: {$key}\n";
            }
        }

        return $deleted;
    }

    /**
     * 特定のキーを即座に削除する
     */
    public function forget(string $key): void
    {
        unset($_SESSION[$key], $_SESSION[self::EXPIRY_KEY][$key]);
    }

    /**
     * 全セッション変数を削除する(session_unset の活用)
     */
    public function forgetAll(): void
    {
        session_unset();
        echo "全セッション変数を削除しました\n";
    }

    public function getExpiryInfo(): array
    {
        return array_map(
            fn($ts) => date('Y-m-d H:i:s', $ts),
            $_SESSION[self::EXPIRY_KEY] ?? []
        );
    }

    public function printStatus(): void
    {
        echo "=== セッション変数ステータス ===\n";
        $expiry = $_SESSION[self::EXPIRY_KEY] ?? [];
        foreach ($_SESSION as $key => $value) {
            if ($key === self::EXPIRY_KEY) continue;
            $exp = isset($expiry[$key])
                ? '期限: ' . date('H:i:s', $expiry[$key])
                : '期限なし';
            printf("  %-20s: %s\n", $key, $exp);
        }
    }
}

session_start();
$lifecycle = new SessionLifecycleManager();

$lifecycle->set('user_id', 42);
$lifecycle->setWithTtl('csrf_token', bin2hex(random_bytes(16)), 300); // 5分
$lifecycle->setWithTtl('otp_code', '123456', 60);                     // 1分

$lifecycle->printStatus();

/*
出力例:
セット: csrf_token (TTL=300秒, 期限=12:05:00)
セット: otp_code (TTL=60秒, 期限=12:01:00)
=== セッション変数ステータス ===
  user_id             : 期限なし
  csrf_token          : 期限: 12:05:00
  otp_code            : 期限: 12:01:00
*/

関連関数との比較(再掲・詳細版)

関数・操作$_SESSIONセッションIDストレージ用途
session_unset()全変数削除(空に)維持維持ログアウト前のデータ消去
unset($_SESSION['key'])指定キーのみ削除維持維持特定変数の削除
$_SESSION = []全変数削除(空に)維持維持session_unset() と同等
session_destroy()そのまま(空にはしない)削除削除セッション完全破棄
session_reset()元の状態に戻す維持変更なしリクエスト中の変更ロールバック
unset($_SESSION)変数自体を破壊維持維持❌ 非推奨・バグの原因

よくある落とし穴

<?php
// ❌ NG:unset($_SESSION) で変数ごと消すとセッションデータが残る
session_start();
$_SESSION['user_id'] = 42;
unset($_SESSION); // $SESSION 変数は消えるがストレージのデータは残る
// 次のリクエストで $SESSION['user_id'] = 42 が復元されてしまう!

// ✅ OK:session_unset() で変数を削除してから session_destroy()
session_start();
session_unset();
session_destroy();
<?php
// ❌ よくある誤解:session_unset() だけでログアウトできると思っている
session_start();
session_unset(); // データは消えるが…
// セッションID・ファイル・Cookie はまだ残っている
// → 攻撃者が同じIDを使い続けられる

// ✅ 完全なログアウトには3点セットが必要
session_unset();                  // ① データ削除
session_destroy();                 // ② ファイル削除
setcookie(session_name(), '', [    // ③ Cookie削除
    'expires'  => time() - 86400,
    'path'     => '/',
    'httponly' => true,
    'secure'   => true,
    'samesite' => 'Lax',
]);
<?php
// ❌ 注意:セッション未開始で session_unset() を呼んでも false を返すだけ
$result = session_unset();
var_dump($result); // bool(false)

// ✅ session_start() 後に呼ぶ
session_start();
$result = session_unset();
var_dump($result); // bool(true)

まとめ

項目内容
関数名session_unset(): bool
主な用途$_SESSION の全変数を削除する
セッションID変更しない
ストレージ変更しない(空のデータが書き込まれる)
Cookie変更しない
呼び出しタイミングsession_start() の後
戻り値成功 true / 失敗 false
完全ログアウトにはsession_unset() + session_destroy() + Cookie削除 が必要
特定キーの削除にはunset($_SESSION['key']) を使う
絶対に使わない操作unset($_SESSION)

session_unset() は「セッション変数を空にする」という一点に特化したシンプルな関数です。ログアウト処理における「データ削除の第一歩」として、session_destroy() と Cookie 削除とセットで使うことで、初めて完全なセッション破棄が実現できます。また、unset($_SESSION) との違いをしっかり理解して、意図しないデータ漏洩を防ぎましょう。


参考リンク

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