[PHP]session_status()完全解説|セッション状態の確認方法と安全な制御フローの実装

PHP

はじめに

PHPでセッションを扱うとき、「今セッションは開始されているのか?」「既に開始済みなのにもう一度 session_start() を呼んでいないか?」といった状態の確認が欠かせません。これを正確に知るための関数が session_status() です。

session_status() はセッションの現在の状態を3つの定数で返します。この戻り値を使うことで、多重起動の防止・条件分岐での安全な初期化・フレームワークのブートストラップ処理など、堅牢なセッション制御が実現できます。シンプルな関数ながら、セッション処理の品質を左右する重要な存在です。


関数の概要

項目内容
関数名session_status()
所属PHP セッション関数
導入バージョンPHP 5.4.0以降
PHP 8.x対応済み

構文

session_status(): int

パラメータはありません。現在のセッション状態を整数定数で返します。


戻り値

定数状態
PHP_SESSION_DISABLED0セッション機能が無効(session.use_cookies = Off 等)
PHP_SESSION_NONE1セッション機能は有効だが、セッションはまだ開始されていない
PHP_SESSION_ACTIVE2セッションが開始されており、アクティブな状態

基本的な使い方

<?php
$status = session_status();

echo match ($status) {
    PHP_SESSION_DISABLED => 'セッション機能が無効',
    PHP_SESSION_NONE     => 'セッション未開始',
    PHP_SESSION_ACTIVE   => 'セッションアクティブ',
};

多重起動防止のイディオム

<?php
// session_start() の二重呼び出しを防ぐ最もシンプルな書き方
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

状態ごとの条件分岐

<?php
switch (session_status()) {
    case PHP_SESSION_DISABLED:
        throw new \RuntimeException('セッション機能が無効です');
    case PHP_SESSION_NONE:
        session_start();
        break;
    case PHP_SESSION_ACTIVE:
        // 既に起動済み、何もしない
        break;
}

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

例1:セッション状態を可視化する診断クラス

<?php
/**
 * session_status() の戻り値を人間が読みやすい形式で表示する診断クラス
 * デバッグ・管理画面・ヘルスチェックエンドポイントに使用する
 */
class SessionStatusInspector
{
    private const STATUS_MAP = [
        PHP_SESSION_DISABLED => [
            'label'       => 'DISABLED',
            'description' => 'セッション機能が無効',
            'icon'        => '🚫',
            'action'      => 'php.ini の session.use_cookies 等を確認してください',
        ],
        PHP_SESSION_NONE => [
            'label'       => 'NONE',
            'description' => 'セッション未開始',
            'icon'        => '⏸️',
            'action'      => 'session_start() を呼び出してセッションを開始できます',
        ],
        PHP_SESSION_ACTIVE => [
            'label'       => 'ACTIVE',
            'description' => 'セッション稼働中',
            'icon'        => '✅',
            'action'      => 'セッションデータへのアクセスが可能です',
        ],
    ];

    public function getStatusInfo(): array
    {
        $code = session_status();
        $info = self::STATUS_MAP[$code] ?? [
            'label'       => 'UNKNOWN',
            'description' => '不明な状態',
            'icon'        => '❓',
            'action'      => '',
        ];

        return array_merge($info, [
            'code'        => $code,
            'session_id'  => session_status() === PHP_SESSION_ACTIVE
                ? substr(session_id(), 0, 8) . '...'
                : null,
            'session_name'=> session_name(),
            'var_count'   => session_status() === PHP_SESSION_ACTIVE
                ? count($_SESSION)
                : null,
        ]);
    }

    public function printReport(): void
    {
        $info = $this->getStatusInfo();
        echo "=== セッション状態レポート ===\n";
        printf("  状態      : %s %s (%s)\n", $info['icon'], $info['label'], $info['description']);
        printf("  コード    : %d\n", $info['code']);
        printf("  セッション名: %s\n", $info['session_name']);
        if ($info['session_id'] !== null) {
            printf("  セッションID: %s\n", $info['session_id']);
            printf("  変数数    : %d\n", $info['var_count']);
        }
        printf("  推奨アクション: %s\n", $info['action']);
    }

    public function isActive(): bool
    {
        return session_status() === PHP_SESSION_ACTIVE;
    }

    public function isDisabled(): bool
    {
        return session_status() === PHP_SESSION_DISABLED;
    }

    public function isNone(): bool
    {
        return session_status() === PHP_SESSION_NONE;
    }
}

$inspector = new SessionStatusInspector();
$inspector->printReport();

/*
出力例(セッション未開始時):
=== セッション状態レポート ===
  状態      : ⏸️ NONE (セッション未開始)
  コード    : 1
  セッション名: PHPSESSID
  推奨アクション: session_start() を呼び出してセッションを開始できます
*/

例2:セッション状態を考慮した安全な初期化クラス

<?php
/**
 * session_status() を活用して、あらゆる状態から安全にセッションを
 * 開始・再開・スキップできる初期化クラス
 */
class SafeSessionInitializer
{
    private array $log = [];

    public function __construct(
        private readonly array $startOptions = []
    ) {}

    /**
     * 状態に応じて適切な処理を行いセッションを確実にアクティブにする
     */
    public function ensureActive(): bool
    {
        $status = session_status();
        $this->log("現在の状態: {$this->statusLabel($status)}");

        return match ($status) {
            PHP_SESSION_DISABLED => $this->handleDisabled(),
            PHP_SESSION_NONE     => $this->handleNone(),
            PHP_SESSION_ACTIVE   => $this->handleActive(),
            default              => false,
        };
    }

    private function handleDisabled(): bool
    {
        $this->log('❌ セッション機能が無効です。処理をスキップします。');
        return false;
    }

    private function handleNone(): bool
    {
        $options = array_merge([
            'name'            => 'AppSession',
            'cookie_httponly' => true,
            'cookie_samesite' => 'Lax',
            'gc_maxlifetime'  => 1800,
            'use_strict_mode' => true,
        ], $this->startOptions);

        $result = session_start($options);
        $this->log($result ? '✅ session_start() 成功' : '❌ session_start() 失敗');
        return $result;
    }

    private function handleActive(): bool
    {
        $this->log('✅ セッションは既にアクティブです(スキップ)');
        return true;
    }

    /**
     * セッションを安全に終了する(状態を確認してから destroy)
     */
    public function safeDestroy(): bool
    {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            $this->log('セッションが未開始のため destroy をスキップ');
            return false;
        }

        $_SESSION = [];

        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(session_name(), '', [
                'expires'  => time() - 86400,
                'path'     => $params['path'],
                'domain'   => $params['domain'],
                'secure'   => $params['secure'],
                'httponly' => $params['httponly'],
                'samesite' => $params['samesite'],
            ]);
        }

        session_destroy();
        $this->log('✅ セッションを破棄しました');
        return true;
    }

    private function statusLabel(int $status): string
    {
        return match ($status) {
            PHP_SESSION_DISABLED => 'DISABLED',
            PHP_SESSION_NONE     => 'NONE',
            PHP_SESSION_ACTIVE   => 'ACTIVE',
            default              => 'UNKNOWN',
        };
    }

    private function log(string $message): void
    {
        $this->log[] = '[' . date('H:i:s') . '] ' . $message;
        echo $message . "\n";
    }

    public function getLog(): array
    {
        return $this->log;
    }
}

$initializer = new SafeSessionInitializer(['cookie_samesite' => 'Strict']);
$initializer->ensureActive();
// $initializer->safeDestroy();

例3:セッション状態をアサートするテスト補助クラス

<?php
/**
 * PHPUnit などのテスト環境でセッション状態を検証するアサーションクラス
 * session_status() を中心にセッションライフサイクルをテストする
 */
class SessionStateAssert
{
    private static array $history = [];

    /**
     * セッションが ACTIVE であることをアサートする
     */
    public static function assertActive(string $message = ''): void
    {
        $status = session_status();
        if ($status !== PHP_SESSION_ACTIVE) {
            $label = self::labelOf($status);
            throw new \AssertionError(
                ($message ? "{$message}: " : '') .
                "セッションが ACTIVE であることを期待しましたが {$label} でした"
            );
        }
        self::record('assertActive: PASS');
    }

    /**
     * セッションが NONE(未開始)であることをアサートする
     */
    public static function assertNotStarted(string $message = ''): void
    {
        $status = session_status();
        if ($status !== PHP_SESSION_NONE) {
            $label = self::labelOf($status);
            throw new \AssertionError(
                ($message ? "{$message}: " : '') .
                "セッションが NONE であることを期待しましたが {$label} でした"
            );
        }
        self::record('assertNotStarted: PASS');
    }

    /**
     * セッションが DISABLED でないことをアサートする
     */
    public static function assertNotDisabled(string $message = ''): void
    {
        if (session_status() === PHP_SESSION_DISABLED) {
            throw new \AssertionError(
                ($message ? "{$message}: " : '') .
                'セッション機能が無効です(PHP_SESSION_DISABLED)'
            );
        }
        self::record('assertNotDisabled: PASS');
    }

    /**
     * セッションキーが存在することをアサートする
     */
    public static function assertHasKey(string $key, string $message = ''): void
    {
        self::assertActive('assertHasKey の前提条件');
        if (!isset($_SESSION[$key])) {
            throw new \AssertionError(
                ($message ? "{$message}: " : '') .
                "セッションキー '{$key}' が存在しません"
            );
        }
        self::record("assertHasKey('{$key}'): PASS");
    }

    /**
     * セッションキーの値をアサートする
     */
    public static function assertKeyEquals(string $key, mixed $expected, string $message = ''): void
    {
        self::assertActive('assertKeyEquals の前提条件');
        $actual = $_SESSION[$key] ?? null;
        if ($actual !== $expected) {
            throw new \AssertionError(
                ($message ? "{$message}: " : '') .
                "セッション['{$key}'] の期待値は " . json_encode($expected) .
                " ですが、実際の値は " . json_encode($actual) . " でした"
            );
        }
        self::record("assertKeyEquals('{$key}'): PASS");
    }

    private static function labelOf(int $status): string
    {
        return match ($status) {
            PHP_SESSION_DISABLED => 'DISABLED',
            PHP_SESSION_NONE     => 'NONE',
            PHP_SESSION_ACTIVE   => 'ACTIVE',
            default              => 'UNKNOWN',
        };
    }

    private static function record(string $entry): void
    {
        self::$history[] = '[' . date('H:i:s') . '] ' . $entry;
    }

    public static function getHistory(): array
    {
        return self::$history;
    }

    public static function reset(): void
    {
        self::$history = [];
    }
}

// テスト利用例
try {
    SessionStateAssert::assertNotDisabled();
    echo "✅ assertNotDisabled: PASS\n";

    session_start();
    SessionStateAssert::assertActive();
    echo "✅ assertActive: PASS\n";

    $_SESSION['role'] = 'admin';
    SessionStateAssert::assertHasKey('role');
    SessionStateAssert::assertKeyEquals('role', 'admin');
    echo "✅ assertKeyEquals: PASS\n";

} catch (\AssertionError $e) {
    echo "❌ アサーション失敗: " . $e->getMessage() . "\n";
}

例4:ミドルウェアパイプラインでのセッション状態ゲートクラス

<?php
/**
 * フレームワークのミドルウェアとして機能するセッション状態チェッククラス
 * リクエストの処理前に session_status() でセッション状態を検証し、
 * 条件を満たさない場合はリクエストを遮断する
 */
class SessionStatusGate
{
    private array $rules = [];

    /**
     * セッションが ACTIVE であることを必須条件として追加する
     */
    public function requireActive(string $redirectTo = '/login'): static
    {
        $this->rules[] = [
            'check'      => fn() => session_status() === PHP_SESSION_ACTIVE,
            'message'    => 'セッションがアクティブではありません',
            'redirect'   => $redirectTo,
        ];
        return $this;
    }

    /**
     * セッションが NONE(未開始)であることを必須条件として追加する
     * 例:ログインページはログイン済みユーザーを弾く
     */
    public function requireNotActive(string $redirectTo = '/dashboard'): static
    {
        $this->rules[] = [
            'check'    => fn() => session_status() !== PHP_SESSION_ACTIVE,
            'message'  => 'セッションが既にアクティブです',
            'redirect' => $redirectTo,
        ];
        return $this;
    }

    /**
     * セッションが有効(DISABLED でない)であることを必須条件として追加する
     */
    public function requireEnabled(): static
    {
        $this->rules[] = [
            'check'    => fn() => session_status() !== PHP_SESSION_DISABLED,
            'message'  => 'セッション機能が無効です',
            'redirect' => null,
        ];
        return $this;
    }

    /**
     * 追加のセッションキー存在チェックルール
     */
    public function requireSessionKey(string $key, string $redirectTo = '/login'): static
    {
        $this->rules[] = [
            'check'    => fn() => session_status() === PHP_SESSION_ACTIVE
                               && isset($_SESSION[$key]),
            'message'  => "セッションキー '{$key}' が必要です",
            'redirect' => $redirectTo,
        ];
        return $this;
    }

    /**
     * すべてのルールを検証してリクエストの通過可否を返す
     */
    public function passes(): bool
    {
        foreach ($this->rules as $rule) {
            if (!($rule['check'])()) {
                echo "ゲート拒否: {$rule['message']}\n";
                if ($rule['redirect']) {
                    echo "リダイレクト先: {$rule['redirect']}\n";
                    // header('Location: ' . $rule['redirect']); exit;
                }
                return false;
            }
        }
        return true;
    }
}

// 認証が必要なページでの使用例
session_start();
$_SESSION['user_id'] = 42;

$gate = (new SessionStatusGate())
    ->requireEnabled()
    ->requireActive('/login')
    ->requireSessionKey('user_id', '/login');

if ($gate->passes()) {
    echo "✅ アクセス許可: ダッシュボードを表示\n";
} else {
    echo "❌ アクセス拒否\n";
}

/*
出力例:
✅ アクセス許可: ダッシュボードを表示
*/

例5:セッション状態の変化をモニタリングするクラス

<?php
/**
 * セッション状態の変化を時系列で記録するモニタリングクラス
 * デバッグやセッションライフサイクルの可視化に使用する
 */
class SessionStatusMonitor
{
    private array $timeline = [];
    private int   $lastStatus;

    public function __construct()
    {
        $this->lastStatus = session_status();
        $this->record('初期化');
    }

    /**
     * 現在の状態を記録する(変化があれば差分も記録)
     */
    public function snapshot(string $label = ''): int
    {
        $current = session_status();
        $changed = $current !== $this->lastStatus;

        $this->timeline[] = [
            'timestamp'  => microtime(true),
            'label'      => $label,
            'status'     => $current,
            'status_str' => $this->labelOf($current),
            'changed'    => $changed,
            'from'       => $changed ? $this->labelOf($this->lastStatus) : null,
        ];

        if ($changed) {
            echo "状態変化: {$this->labelOf($this->lastStatus)} → {$this->labelOf($current)}"
                . ($label ? " ({$label})" : '') . "\n";
        }

        $this->lastStatus = $current;
        return $current;
    }

    private function record(string $label): void
    {
        $this->snapshot($label);
    }

    private function labelOf(int $status): string
    {
        return match ($status) {
            PHP_SESSION_DISABLED => 'DISABLED',
            PHP_SESSION_NONE     => 'NONE',
            PHP_SESSION_ACTIVE   => 'ACTIVE',
            default              => 'UNKNOWN',
        };
    }

    public function printTimeline(): void
    {
        echo "\n=== セッション状態タイムライン ===\n";
        $start = $this->timeline[0]['timestamp'] ?? microtime(true);
        foreach ($this->timeline as $i => $entry) {
            $elapsed = round(($entry['timestamp'] - $start) * 1000, 2);
            $change  = $entry['changed']
                ? " ← {$entry['from']} から変化"
                : '';
            printf(
                "#%02d +%7.2fms %-10s %s%s\n",
                $i + 1,
                $elapsed,
                $entry['status_str'],
                $entry['label'],
                $change
            );
        }
    }

    public function getTransitions(): array
    {
        return array_filter($this->timeline, fn($e) => $e['changed']);
    }
}

$monitor = new SessionStatusMonitor();
$monitor->snapshot('session_start前');

session_start();
$monitor->snapshot('session_start後');

$_SESSION['key'] = 'value';
$monitor->snapshot('データセット後');

session_write_close();
$monitor->snapshot('write_close後');

session_start();
$monitor->snapshot('再start後');

$monitor->printTimeline();

/*
出力例:
状態変化: NONE → ACTIVE (session_start後)
状態変化: ACTIVE → NONE (write_close後)
状態変化: NONE → ACTIVE (再start後)

=== セッション状態タイムライン ===
#01 +   0.00ms NONE       初期化
#02 +   0.05ms NONE       session_start前
#03 +   2.31ms ACTIVE     session_start後 ← NONE から変化
#04 +   2.35ms ACTIVE     データセット後
#05 +   2.48ms NONE       write_close後 ← ACTIVE から変化
#06 +   3.12ms ACTIVE     再start後 ← NONE から変化
*/

例6:セッション状態を使ったフレームワーク統合ブートストラッパー

<?php
/**
 * session_status() を起点にセッションライフサイクル全体を管理する
 * フレームワーク統合向けブートストラッパークラス
 *
 * リクエストの種類(Web / API / CLI)と現在のセッション状態を組み合わせて
 * 最適な起動戦略を選択する
 */
class SessionBootstrap
{
    private string $requestType;
    private array  $bootLog = [];

    public function __construct()
    {
        $this->requestType = $this->detectRequestType();
    }

    /**
     * アプリケーション起動時に呼ぶエントリーポイント
     */
    public function boot(): bool
    {
        $status = session_status();
        $this->log("起動: requestType={$this->requestType}, status={$this->labelOf($status)}");

        // DISABLED は即終了
        if ($status === PHP_SESSION_DISABLED) {
            $this->log('セッション無効のためブートをスキップ');
            return false;
        }

        // 既に ACTIVE なら何もしない(他のミドルウェアが先に起動した等)
        if ($status === PHP_SESSION_ACTIVE) {
            $this->log('既にアクティブのためスキップ');
            return true;
        }

        // NONE から requestType に応じた戦略で起動
        return match ($this->requestType) {
            'web'  => $this->bootForWeb(),
            'api'  => $this->bootForApi(),
            'cli'  => $this->bootForCli(),
            default => false,
        };
    }

    private function bootForWeb(): bool
    {
        $result = session_start([
            'name'            => 'WebAppSession',
            'cookie_httponly' => true,
            'cookie_secure'   => !empty($_SERVER['HTTPS']),
            'cookie_samesite' => 'Lax',
            'gc_maxlifetime'  => 1800,
            'use_strict_mode' => true,
            'lazy_write'      => true,
        ]);
        $this->log('Web セッション起動: ' . ($result ? '成功' : '失敗'));

        // アクティブになったことを再確認
        if (session_status() === PHP_SESSION_ACTIVE && !isset($_SESSION['_boot_at'])) {
            session_regenerate_id(true);
            $_SESSION['_boot_at'] = time();
        }

        return $result;
    }

    private function bootForApi(): bool
    {
        // APIはステートレスが基本。セッションは使わずトークン認証を推奨
        $this->log('API リクエスト: セッション不使用(トークン認証を推奨)');
        return false;
    }

    private function bootForCli(): bool
    {
        $this->log('CLI 実行: セッション不使用');
        return false;
    }

    /**
     * シャットダウン前に状態を確認して適切にクローズする
     */
    public function shutdown(): void
    {
        $status = session_status();
        $this->log("シャットダウン: status={$this->labelOf($status)}");

        if ($status === PHP_SESSION_ACTIVE) {
            session_write_close();
            $this->log('session_write_close() 実行');
        }
    }

    private function detectRequestType(): string
    {
        if (PHP_SAPI === 'cli') return 'cli';
        $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
        if (str_contains($accept, 'application/json')) return 'api';
        return 'web';
    }

    private function labelOf(int $status): string
    {
        return match ($status) {
            PHP_SESSION_DISABLED => 'DISABLED',
            PHP_SESSION_NONE     => 'NONE',
            PHP_SESSION_ACTIVE   => 'ACTIVE',
            default              => 'UNKNOWN',
        };
    }

    private function log(string $msg): void
    {
        $this->bootLog[] = '[' . date('H:i:s') . '] ' . $msg;
        echo $msg . "\n";
    }

    public function getBootLog(): array
    {
        return $this->bootLog;
    }

    public function currentStatus(): int
    {
        return session_status();
    }

    public function currentStatusLabel(): string
    {
        return $this->labelOf(session_status());
    }
}

$bootstrap = new SessionBootstrap();
$bootstrap->boot();

echo "起動後の状態: " . $bootstrap->currentStatusLabel() . "\n";

register_shutdown_function([$bootstrap, 'shutdown']);

/*
出力例(Web リクエスト):
起動: requestType=web, status=NONE
Web セッション起動: 成功
起動後の状態: ACTIVE
*/

session_status() の3定数を整理する

<?php
// PHP_SESSION_DISABLED = 0
// → セッション機能そのものが php.ini で無効化されている
//   session_start() を呼んでも動作しない

// PHP_SESSION_NONE = 1
// → セッション機能は有効だが、まだ session_start() を呼んでいない状態
//   この状態で session_start() を呼べばセッションが開始される

// PHP_SESSION_ACTIVE = 2
// → session_start() 済みでセッションがアクティブ
//   $_SESSION の読み書きが可能
//   session_write_close() や session_destroy() を呼ぶと NONE に戻る

// よく使うパターン
$status = session_status();

$isActive   = $status === PHP_SESSION_ACTIVE;   // 2
$canStart   = $status === PHP_SESSION_NONE;      // 1
$isDisabled = $status === PHP_SESSION_DISABLED;  // 0

関連関数との比較

関数役割
session_status()現在のセッション状態を取得(DISABLED / NONE / ACTIVE)
session_start()セッションを開始する(NONE → ACTIVE)
session_destroy()セッションを破棄する(ACTIVE → NONE)
session_write_close()セッションを書き込んでクローズ(ACTIVE → NONE)
session_abort()セッションの変更を破棄してクローズ(ACTIVE → NONE)
session_id()セッションIDを取得(ACTIVE でないと空文字を返す)

よくある落とし穴

<?php
// ❌ NG:session_status() を確認せずに session_start() を呼ぶ
session_start();
// ... 何らかの処理(この中で session_start() が再度呼ばれると警告)
session_start(); // Notice: A session had already been started

// ✅ OK:状態を確認してから起動する
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}
<?php
// ❌ よくある誤解:session_start() 後に session_status() が変わらないと思っている
var_dump(session_status()); // int(1) = PHP_SESSION_NONE

session_start();

var_dump(session_status()); // int(2) = PHP_SESSION_ACTIVE ← 変わる

session_write_close();

var_dump(session_status()); // int(1) = PHP_SESSION_NONE ← また戻る
<?php
// ❌ 注意:PHP_SESSION_DISABLED はセッション機能の無効であり
//          「セッションが存在しない」とは違う
if (session_status() === PHP_SESSION_DISABLED) {
    // この場合 session_start() を呼んでも動作しない
    // php.ini や ini_set で session.use_cookies 等を確認する必要がある
}

まとめ

項目内容
関数名session_status(): int
主な用途現在のセッション状態を取得して条件分岐に使う
戻り値PHP_SESSION_DISABLED(0) / PHP_SESSION_NONE(1) / PHP_SESSION_ACTIVE(2)
導入バージョンPHP 5.4.0以降
典型的な使い方session_status() === PHP_SESSION_NONE で多重起動防止
よく組み合わせる関数session_start() / session_destroy() / session_write_close()

session_status() は3つの定数を返すだけのシンプルな関数ですが、多重起動防止・状態に応じた分岐・フレームワークのブートストラップ・テストのアサーションなど、セッション処理のあらゆる場面で欠かせない存在です。セッション関連のコードを書くときは、常に session_status() で状態を確認する習慣をつけましょう。


参考リンク

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