[PHP]session_create_id関数を完全解説!カスタムセッションIDを生成する方法

PHP

こんにちは!今回は、PHPの標準関数であるsession_create_id()について詳しく解説していきます。カスタムセッションIDを生成できる、セッション管理の柔軟性を高める関数です!

session_create_id関数とは?

session_create_id()関数は、新しいセッションIDを生成する関数です。

PHP 7.1.0で追加された関数で、セッションIDにプレフィックスを付けることができます。これにより、複数のアプリケーション間でセッションを分離したり、セッションIDの構造を制御したりできます!

基本的な構文

session_create_id(string $prefix = ""): string|false
  • $prefix: セッションIDのプレフィックス(省略可)
  • 戻り値: 新しいセッションID、失敗時はfalse

重要な注意点

// プレフィックスに使用できる文字
// 英数字、カンマ(,)、ハイフン(-)のみ

// 正しい例
$id1 = session_create_id('app1-');        // OK
$id2 = session_create_id('user123,');     // OK
$id3 = session_create_id('tenant-001-');  // OK

// 不正な例(警告が発生)
// $id4 = session_create_id('app_1');     // アンダースコア不可
// $id5 = session_create_id('app.1');     // ドット不可
// $id6 = session_create_id('app/1');     // スラッシュ不可

// セッションIDの長さ
// session.sid_length(デフォルト32文字)+ プレフィックスの長さ

// セッション開始前に使用
$newId = session_create_id('myapp-');
session_id($newId);  // 生成したIDを設定
session_start();     // セッション開始

セッションIDの形式

// プレフィックスなし
$id = session_create_id();
echo $id;  // 例: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

// プレフィックスあり
$id = session_create_id('app-');
echo $id;  // 例: app-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

// 長さの確認
$id = session_create_id('prefix-');
echo strlen($id);  // プレフィックス長 + session.sid_length

基本的な使用例

シンプルなセッションID生成

// 新しいセッションIDを生成
$newId = session_create_id();
echo "生成されたID: {$newId}\n";
echo "ID長: " . strlen($newId) . "\n";

// セッションIDとして使用
session_id($newId);
session_start();

echo "現在のセッションID: " . session_id() . "\n";

プレフィックス付きセッションID

// アプリケーション識別子をプレフィックスとして追加
$appId = session_create_id('myapp-');
echo "アプリケーションID: {$appId}\n";

session_id($appId);
session_start();

$_SESSION['data'] = 'test';
session_write_close();

複数アプリケーションでの分離

// アプリケーション1
function startApp1Session() {
    $id = session_create_id('app1-');
    session_id($id);
    session_start();
    return session_id();
}

// アプリケーション2
function startApp2Session() {
    $id = session_create_id('app2-');
    session_id($id);
    session_start();
    return session_id();
}

// 使用例
$app1Id = startApp1Session();
echo "App1 セッションID: {$app1Id}\n";
session_write_close();

$app2Id = startApp2Session();
echo "App2 セッションID: {$app2Id}\n";

セッションIDの再生成

// 既存のセッション
session_start();
$oldId = session_id();
echo "古いID: {$oldId}\n";

// 新しいIDを生成して置き換え
$newId = session_create_id('renewed-');
session_id($newId);

// セッションデータを保持したまま新しいIDで再開
$sessionData = $_SESSION;
session_destroy();

session_id($newId);
session_start();
$_SESSION = $sessionData;

echo "新しいID: " . session_id() . "\n";

実践的な使用例

例1: マルチテナントセッション管理

class MultiTenantSessionManager {
    private $tenantId;
    private $sessionPrefix;
    
    /**
     * マルチテナントセッションマネージャーを初期化
     */
    public function __construct($tenantId) {
        $this->tenantId = $tenantId;
        $this->sessionPrefix = $this->generatePrefix($tenantId);
    }
    
    /**
     * テナントIDからプレフィックスを生成
     */
    private function generatePrefix($tenantId) {
        // テナントIDをハッシュ化して短縮
        $hash = substr(md5($tenantId), 0, 8);
        return "t{$hash}-";
    }
    
    /**
     * テナント固有のセッションを開始
     */
    public function startSession() {
        // カスタムセッションIDを生成
        $sessionId = session_create_id($this->sessionPrefix);
        
        if ($sessionId === false) {
            throw new Exception("Failed to create session ID");
        }
        
        // セッションIDを設定
        session_id($sessionId);
        
        // セッション開始
        session_start();
        
        // テナント情報を記録
        if (!isset($_SESSION['tenant_id'])) {
            $_SESSION['tenant_id'] = $this->tenantId;
            $_SESSION['session_created_at'] = time();
        }
        
        return [
            'session_id' => session_id(),
            'tenant_id' => $this->tenantId,
            'prefix' => $this->sessionPrefix
        ];
    }
    
    /**
     * テナントIDを検証
     */
    public function validateTenant() {
        if (!isset($_SESSION['tenant_id'])) {
            return false;
        }
        
        return $_SESSION['tenant_id'] === $this->tenantId;
    }
    
    /**
     * セッションIDからテナントを識別
     */
    public static function getTenantFromSessionId($sessionId) {
        // プレフィックスを抽出
        if (preg_match('/^t([a-f0-9]{8})-/', $sessionId, $matches)) {
            return [
                'prefix' => $matches[0],
                'hash' => $matches[1]
            ];
        }
        
        return null;
    }
    
    /**
     * テナント情報を取得
     */
    public function getTenantInfo() {
        if (session_status() !== PHP_SESSION_ACTIVE) {
            return null;
        }
        
        return [
            'tenant_id' => $_SESSION['tenant_id'] ?? null,
            'session_id' => session_id(),
            'prefix' => $this->sessionPrefix,
            'created_at' => $_SESSION['session_created_at'] ?? null,
            'is_valid' => $this->validateTenant()
        ];
    }
    
    /**
     * すべてのテナントセッションをリスト
     */
    public static function listTenantSessions($sessionPath) {
        $sessions = [];
        
        if (!is_dir($sessionPath)) {
            return $sessions;
        }
        
        $files = scandir($sessionPath);
        
        foreach ($files as $file) {
            if ($file === '.' || $file === '..') {
                continue;
            }
            
            // セッションファイル名からIDを抽出
            if (preg_match('/^sess_(.+)$/', $file, $matches)) {
                $sessionId = $matches[1];
                $tenantInfo = self::getTenantFromSessionId($sessionId);
                
                if ($tenantInfo) {
                    $sessions[] = [
                        'session_id' => $sessionId,
                        'tenant_hash' => $tenantInfo['hash'],
                        'file' => $file,
                        'size' => filesize($sessionPath . '/' . $file),
                        'modified' => filemtime($sessionPath . '/' . $file)
                    ];
                }
            }
        }
        
        return $sessions;
    }
}

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

// テナント1のセッション
echo "\nテナント1:\n";
$tenant1 = new MultiTenantSessionManager('tenant-001');
$info1 = $tenant1->startSession();
echo "  セッションID: {$info1['session_id']}\n";
echo "  テナントID: {$info1['tenant_id']}\n";
echo "  プレフィックス: {$info1['prefix']}\n";

$_SESSION['tenant1_data'] = 'テナント1のデータ';
session_write_close();

// テナント2のセッション
echo "\nテナント2:\n";
$tenant2 = new MultiTenantSessionManager('tenant-002');
$info2 = $tenant2->startSession();
echo "  セッションID: {$info2['session_id']}\n";
echo "  テナントID: {$info2['tenant_id']}\n";
echo "  プレフィックス: {$info2['prefix']}\n";

$_SESSION['tenant2_data'] = 'テナント2のデータ';

// テナント情報を確認
$tenantInfo = $tenant2->getTenantInfo();
echo "\nテナント2の詳細情報:\n";
echo "  有効: " . ($tenantInfo['is_valid'] ? 'Yes' : 'No') . "\n";
echo "  作成日時: " . date('Y-m-d H:i:s', $tenantInfo['created_at']) . "\n";

session_write_close();

// セッションIDからテナントを識別
echo "\nセッションIDからテナント識別:\n";
$extracted = MultiTenantSessionManager::getTenantFromSessionId($info1['session_id']);
echo "  テナント1のハッシュ: {$extracted['hash']}\n";

例2: セッションIDプレフィックス管理システム

class SessionPrefixManager {
    private static $prefixRegistry = [];
    
    /**
     * プレフィックスを登録
     */
    public static function registerPrefix($name, $prefix) {
        // プレフィックスの検証
        if (!self::isValidPrefix($prefix)) {
            throw new Exception("Invalid prefix: {$prefix}");
        }
        
        self::$prefixRegistry[$name] = [
            'prefix' => $prefix,
            'registered_at' => time(),
            'usage_count' => 0
        ];
        
        return true;
    }
    
    /**
     * プレフィックスが有効か検証
     */
    private static function isValidPrefix($prefix) {
        // 英数字、カンマ、ハイフンのみ許可
        return preg_match('/^[a-zA-Z0-9,-]+$/', $prefix) === 1;
    }
    
    /**
     * 登録済みプレフィックスでセッションIDを生成
     */
    public static function createSessionId($prefixName) {
        if (!isset(self::$prefixRegistry[$prefixName])) {
            throw new Exception("Prefix not registered: {$prefixName}");
        }
        
        $prefix = self::$prefixRegistry[$prefixName]['prefix'];
        $sessionId = session_create_id($prefix);
        
        if ($sessionId === false) {
            throw new Exception("Failed to create session ID");
        }
        
        // 使用回数をカウント
        self::$prefixRegistry[$prefixName]['usage_count']++;
        
        return [
            'session_id' => $sessionId,
            'prefix_name' => $prefixName,
            'prefix' => $prefix,
            'usage_count' => self::$prefixRegistry[$prefixName]['usage_count']
        ];
    }
    
    /**
     * 登録済みプレフィックス一覧を取得
     */
    public static function listPrefixes() {
        return self::$prefixRegistry;
    }
    
    /**
     * プレフィックスの使用統計
     */
    public static function getStatistics() {
        $stats = [
            'total_prefixes' => count(self::$prefixRegistry),
            'total_usage' => 0,
            'most_used' => null,
            'least_used' => null
        ];
        
        if (empty(self::$prefixRegistry)) {
            return $stats;
        }
        
        $usageCounts = array_column(self::$prefixRegistry, 'usage_count');
        $stats['total_usage'] = array_sum($usageCounts);
        
        arsort($usageCounts);
        $mostUsed = array_key_first($usageCounts);
        $stats['most_used'] = [
            'name' => $mostUsed,
            'count' => $usageCounts[$mostUsed]
        ];
        
        asort($usageCounts);
        $leastUsed = array_key_first($usageCounts);
        $stats['least_used'] = [
            'name' => $leastUsed,
            'count' => $usageCounts[$leastUsed]
        ];
        
        return $stats;
    }
    
    /**
     * 環境別のプレフィックスを生成
     */
    public static function createEnvironmentPrefix($environment) {
        $prefixes = [
            'production' => 'prod-',
            'staging' => 'stg-',
            'development' => 'dev-',
            'testing' => 'test-'
        ];
        
        $prefix = $prefixes[$environment] ?? 'env-';
        
        self::registerPrefix($environment, $prefix);
        
        return $prefix;
    }
    
    /**
     * アプリケーション別のプレフィックスを生成
     */
    public static function createApplicationPrefix($appName, $version = null) {
        $cleanName = preg_replace('/[^a-zA-Z0-9]/', '', $appName);
        $prefix = strtolower(substr($cleanName, 0, 6)) . '-';
        
        if ($version !== null) {
            $cleanVersion = preg_replace('/[^a-zA-Z0-9]/', '', $version);
            $prefix .= $cleanVersion . '-';
        }
        
        self::registerPrefix($appName . ($version ? "-v{$version}" : ''), $prefix);
        
        return $prefix;
    }
}

// 使用例
echo "=== セッションIDプレフィックス管理 ===\n";

// プレフィックスを登録
echo "プレフィックス登録:\n";
SessionPrefixManager::registerPrefix('admin', 'admin-');
SessionPrefixManager::registerPrefix('user', 'user-');
SessionPrefixManager::registerPrefix('api', 'api-');
echo "  3つのプレフィックスを登録\n";

// 環境別プレフィックス
echo "\n環境別プレフィックス:\n";
$prodPrefix = SessionPrefixManager::createEnvironmentPrefix('production');
$devPrefix = SessionPrefixManager::createEnvironmentPrefix('development');
echo "  本番: {$prodPrefix}\n";
echo "  開発: {$devPrefix}\n";

// アプリケーション別プレフィックス
echo "\nアプリケーション別プレフィックス:\n";
$app1Prefix = SessionPrefixManager::createApplicationPrefix('MyShopApp', '2.0');
$app2Prefix = SessionPrefixManager::createApplicationPrefix('BlogEngine');
echo "  MyShopApp v2.0: {$app1Prefix}\n";
echo "  BlogEngine: {$app2Prefix}\n";

// セッションIDを生成
echo "\nセッションID生成:\n";
$adminSession = SessionPrefixManager::createSessionId('admin');
echo "  管理者: {$adminSession['session_id']}\n";
echo "  プレフィックス: {$adminSession['prefix']}\n";

$userSession = SessionPrefixManager::createSessionId('user');
echo "  ユーザー: {$userSession['session_id']}\n";

// 複数回使用
for ($i = 0; $i < 3; $i++) {
    SessionPrefixManager::createSessionId('api');
}

// 統計情報
echo "\n=== 使用統計 ===\n";
$stats = SessionPrefixManager::getStatistics();
echo "総プレフィックス数: {$stats['total_prefixes']}\n";
echo "総使用回数: {$stats['total_usage']}\n";
echo "最多使用: {$stats['most_used']['name']} ({$stats['most_used']['count']}回)\n";
echo "最少使用: {$stats['least_used']['name']} ({$stats['least_used']['count']}回)\n";

// プレフィックス一覧
echo "\n=== 登録済みプレフィックス ===\n";
foreach (SessionPrefixManager::listPrefixes() as $name => $info) {
    echo "{$name}:\n";
    echo "  プレフィックス: {$info['prefix']}\n";
    echo "  使用回数: {$info['usage_count']}\n";
    echo "  登録日時: " . date('Y-m-d H:i:s', $info['registered_at']) . "\n";
}

例3: セキュアセッションID生成システム

class SecureSessionIdGenerator {
    private $entropy = 'high';
    private $hashAlgorithm = 'sha256';
    
    /**
     * セキュアセッションID生成システムを初期化
     */
    public function __construct($entropy = 'high', $hashAlgorithm = 'sha256') {
        $this->entropy = $entropy;
        $this->hashAlgorithm = $hashAlgorithm;
    }
    
    /**
     * 高エントロピーのセッションIDを生成
     */
    public function generateSecureId($prefix = '') {
        // ベースとなるセッションIDを生成
        $baseId = session_create_id($prefix);
        
        if ($baseId === false) {
            throw new Exception("Failed to create base session ID");
        }
        
        // エントロピーに応じて追加のランダム性を付与
        if ($this->entropy === 'high') {
            return $this->enhanceEntropy($baseId);
        }
        
        return $baseId;
    }
    
    /**
     * エントロピーを強化
     */
    private function enhanceEntropy($baseId) {
        // 複数のランダム源を組み合わせ
        $randomData = [
            random_bytes(16),
            microtime(true),
            getmypid(),
            $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
        ];
        
        $combined = $baseId . implode('', $randomData);
        $hashed = hash($this->hashAlgorithm, $combined);
        
        // プレフィックスを保持
        if (strpos($baseId, '-') !== false) {
            list($prefix, $id) = explode('-', $baseId, 2);
            return $prefix . '-' . $hashed;
        }
        
        return $hashed;
    }
    
    /**
     * タイムスタンプ付きセッションID
     */
    public function generateTimestampedId($prefix = '') {
        $timestamp = base_convert(time(), 10, 36);
        $fullPrefix = $prefix . $timestamp . '-';
        
        return session_create_id($fullPrefix);
    }
    
    /**
     * ユーザー固有のセッションID
     */
    public function generateUserSessionId($userId, $prefix = '') {
        // ユーザーIDをハッシュ化
        $userHash = substr(hash('sha256', $userId), 0, 8);
        $fullPrefix = $prefix . 'u' . $userHash . '-';
        
        return session_create_id($fullPrefix);
    }
    
    /**
     * セッションIDの強度を評価
     */
    public function evaluateStrength($sessionId) {
        $strength = [
            'length' => strlen($sessionId),
            'has_prefix' => strpos($sessionId, '-') !== false,
            'entropy_bits' => 0,
            'rating' => 'weak'
        ];
        
        // エントロピービットを推定
        $uniqueChars = count(array_unique(str_split($sessionId)));
        $strength['entropy_bits'] = $strength['length'] * log($uniqueChars, 2);
        
        // 強度を評価
        if ($strength['entropy_bits'] > 256) {
            $strength['rating'] = 'very_strong';
        } elseif ($strength['entropy_bits'] > 192) {
            $strength['rating'] = 'strong';
        } elseif ($strength['entropy_bits'] > 128) {
            $strength['rating'] = 'moderate';
        }
        
        return $strength;
    }
    
    /**
     * セッションIDの衝突テスト
     */
    public function testCollision($count = 1000, $prefix = '') {
        $ids = [];
        $collisions = 0;
        
        for ($i = 0; $i < $count; $i++) {
            $id = $this->generateSecureId($prefix);
            
            if (in_array($id, $ids)) {
                $collisions++;
            }
            
            $ids[] = $id;
        }
        
        return [
            'total_generated' => $count,
            'unique_ids' => count(array_unique($ids)),
            'collisions' => $collisions,
            'collision_rate' => ($collisions / $count) * 100
        ];
    }
    
    /**
     * 複数のアルゴリズムで比較
     */
    public function compareAlgorithms($prefix = '') {
        $algorithms = ['md5', 'sha1', 'sha256', 'sha512'];
        $results = [];
        
        foreach ($algorithms as $algo) {
            $originalAlgo = $this->hashAlgorithm;
            $this->hashAlgorithm = $algo;
            
            $startTime = microtime(true);
            $id = $this->generateSecureId($prefix);
            $endTime = microtime(true);
            
            $results[$algo] = [
                'id' => $id,
                'length' => strlen($id),
                'generation_time' => ($endTime - $startTime) * 1000,
                'strength' => $this->evaluateStrength($id)
            ];
            
            $this->hashAlgorithm = $originalAlgo;
        }
        
        return $results;
    }
}

// 使用例
echo "=== セキュアセッションID生成 ===\n";

$generator = new SecureSessionIdGenerator('high', 'sha256');

// 基本的なセキュアID生成
echo "セキュアID生成:\n";
$secureId = $generator->generateSecureId('secure-');
echo "  ID: {$secureId}\n";
echo "  長さ: " . strlen($secureId) . "\n";

// 強度評価
$strength = $generator->evaluateStrength($secureId);
echo "\n強度評価:\n";
echo "  長さ: {$strength['length']}\n";
echo "  エントロピービット: " . round($strength['entropy_bits'], 2) . "\n";
echo "  評価: {$strength['rating']}\n";

// タイムスタンプ付きID
echo "\nタイムスタンプ付きID:\n";
$timestampId = $generator->generateTimestampedId('ts-');
echo "  ID: {$timestampId}\n";

// ユーザー固有ID
echo "\nユーザー固有ID:\n";
$userId1 = $generator->generateUserSessionId('user123', 'app-');
$userId2 = $generator->generateUserSessionId('user456', 'app-');
echo "  ユーザー123: {$userId1}\n";
echo "  ユーザー456: {$userId2}\n";

// 衝突テスト
echo "\n=== 衝突テスト ===\n";
$collisionTest = $generator->testCollision(1000, 'test-');
echo "生成数: {$collisionTest['total_generated']}\n";
echo "ユニークID数: {$collisionTest['unique_ids']}\n";
echo "衝突数: {$collisionTest['collisions']}\n";
echo "衝突率: " . round($collisionTest['collision_rate'], 4) . "%\n";

// アルゴリズム比較
echo "\n=== アルゴリズム比較 ===\n";
$comparison = $generator->compareAlgorithms('compare-');
foreach ($comparison as $algo => $result) {
    echo "\n{$algo}:\n";
    echo "  ID長: {$result['length']}\n";
    echo "  生成時間: " . round($result['generation_time'], 4) . "ms\n";
    echo "  評価: {$result['strength']['rating']}\n";
}

例4: アプリケーション分離システム

class ApplicationIsolationManager {
    private $applications = [];
    
    /**
     * アプリケーションを登録
     */
    public function registerApplication($appName, $config = []) {
        $prefix = $this->generateApplicationPrefix($appName);
        
        $this->applications[$appName] = [
            'prefix' => $prefix,
            'config' => $config,
            'sessions' => [],
            'created_at' => time()
        ];
        
        return $prefix;
    }
    
    /**
     * アプリケーション固有のプレフィックスを生成
     */
    private function generateApplicationPrefix($appName) {
        // アプリケーション名から安全なプレフィックスを生成
        $clean = preg_replace('/[^a-zA-Z0-9]/', '', $appName);
        $short = strtolower(substr($clean, 0, 8));
        return "{$short}-";
    }
    
    /**
     * アプリケーションのセッションを開始
     */
    public function startApplicationSession($appName, $userId = null) {
        if (!isset($this->applications[$appName])) {
            throw new Exception("Application not registered: {$appName}");
        }
        
        $app = $this->applications[$appName];
        $prefix = $app['prefix'];
        
        // ユーザーIDがある場合はプレフィックスに含める
        if ($userId !== null) {
            $userHash = substr(md5($userId), 0, 6);
            $prefix .= "{$userHash}-";
        }
        
        // セッションID生成
        $sessionId = session_create_id($prefix);
        
        if ($sessionId === false) {
            throw new Exception("Failed to create session ID");
        }
        
        // セッションを開始
        session_id($sessionId);
        session_start();
        
        // アプリケーション情報を記録
        $_SESSION['app_name'] = $appName;
        $_SESSION['app_prefix'] = $prefix;
        $_SESSION['user_id'] = $userId;
        $_SESSION['session_started_at'] = time();
        
        // アプリケーションのセッションリストに追加
        $this->applications[$appName]['sessions'][] = [
            'session_id' => $sessionId,
            'user_id' => $userId,
            'started_at' => time()
        ];
        
        return [
            'app_name' => $appName,
            'session_id' => $sessionId,
            'prefix' => $prefix,
            'user_id' => $userId
        ];
    }
    
    /**
     * セッションIDからアプリケーションを識別
     */
    public function identifyApplication($sessionId) {
        foreach ($this->applications as $appName => $app) {
            if (strpos($sessionId, $app['prefix']) === 0) {
                return [
                    'app_name' => $appName,
                    'prefix' => $app['prefix'],
                    'config' => $app['config']
                ];
            }
        }
        
        return null;
    }
    
    /**
     * アプリケーションのセッション一覧
     */
    public function getApplicationSessions($appName) {
        if (!isset($this->applications[$appName])) {
            return [];
        }
        
        return $this->applications[$appName]['sessions'];
    }
    
    /**
     * すべてのアプリケーション情報
     */
    public function getAllApplications() {
        $info = [];
        
        foreach ($this->applications as $appName => $app) {
            $info[$appName] = [
                'prefix' => $app['prefix'],
                'session_count' => count($app['sessions']),
                'created_at' => $app['created_at']
            ];
        }
        
        return $info;
    }
    
    /**
     * アプリケーション間のセッション移行
     */
    public function migrateSession($fromApp, $toApp, $sessionData = []) {
        // 現在のセッションを終了
        if (session_status() === PHP_SESSION_ACTIVE) {
            $oldSessionId = session_id();
            $oldData = $_SESSION;
            session_write_close();
        }
        
        // 新しいアプリケーションでセッション開始
        $result = $this->startApplicationSession($toApp, $sessionData['user_id'] ?? null);
        
        // データを移行
        if (isset($oldData)) {
            foreach ($oldData as $key => $value) {
                if (!in_array($key, ['app_name', 'app_prefix'])) {
                    $_SESSION[$key] = $value;
                }
            }
        }
        
        $_SESSION['migrated_from'] = $fromApp;
        $_SESSION['migrated_at'] = time();
        
        session_write_close();
        
        return $result;
    }
    
    /**
     * アプリケーション統計
     */
    public function getStatistics() {
        $stats = [
            'total_applications' => count($this->applications),
            'total_sessions' => 0,
            'applications' => []
        ];
        
        foreach ($this->applications as $appName => $app) {
            $sessionCount = count($app['sessions']);
            $stats['total_sessions'] += $sessionCount;
            
            $stats['applications'][$appName] = [
                'prefix' => $app['prefix'],
                'sessions' => $sessionCount,
                'uptime' => time() - $app['created_at']
            ];
        }
        
        return $stats;
    }
}

// 使用例
echo "=== アプリケーション分離システム ===\n";

$manager = new ApplicationIsolationManager();

// アプリケーションを登録
echo "アプリケーション登録:\n";
$manager->registerApplication('ShopApp', ['theme' => 'blue']);
$manager->registerApplication('BlogApp', ['theme' => 'green']);
$manager->registerApplication('AdminPanel', ['theme' => 'red']);
echo "  3つのアプリケーションを登録\n";

// 各アプリケーションでセッション開始
echo "\nセッション開始:\n";

$shop1 = $manager->startApplicationSession('ShopApp', 'user123');
echo "  ShopApp (user123): {$shop1['session_id']}\n";
session_write_close();

$blog1 = $manager->startApplicationSession('BlogApp', 'user456');
echo "  BlogApp (user456): {$blog1['session_id']}\n";
session_write_close();

$admin1 = $manager->startApplicationSession('AdminPanel', 'admin001');
echo "  AdminPanel (admin001): {$admin1['session_id']}\n";
session_write_close();

// セッションIDからアプリケーションを識別
echo "\nアプリケーション識別:\n";
$identified = $manager->identifyApplication($shop1['session_id']);
echo "  セッションID {$shop1['session_id']}\n";
echo "  → アプリケーション: {$identified['app_name']}\n";

// 統計情報
echo "\n=== 統計情報 ===\n";
$stats = $manager->getStatistics();
echo "総アプリケーション数: {$stats['total_applications']}\n";
echo "総セッション数: {$stats['total_sessions']}\n";
echo "\nアプリケーション別:\n";
foreach ($stats['applications'] as $appName => $appStats) {
    echo "  {$appName}:\n";
    echo "    プレフィックス: {$appStats['prefix']}\n";
    echo "    セッション数: {$appStats['sessions']}\n";
    echo "    稼働時間: {$appStats['uptime']}秒\n";
}

例5: セッションID監査システム

class SessionIdAuditSystem {
    private $auditLog = [];
    private $logFile;
    
    /**
     * 監査システムを初期化
     */
    public function __construct($logFile = '/tmp/session_audit.log') {
        $this->logFile = $logFile;
        $this->loadAuditLog();
    }
    
    /**
     * セッションID生成を記録
     */
    public function auditSessionCreation($prefix = '', $metadata = []) {
        $sessionId = session_create_id($prefix);
        
        if ($sessionId === false) {
            $this->logEvent('creation_failed', [
                'prefix' => $prefix,
                'error' => 'Failed to create session ID'
            ]);
            throw new Exception("Failed to create session ID");
        }
        
        $auditEntry = [
            'event' => 'session_created',
            'session_id' => $sessionId,
            'prefix' => $prefix,
            'timestamp' => time(),
            'metadata' => $metadata,
            'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
        ];
        
        $this->logEvent('session_created', $auditEntry);
        
        return $sessionId;
    }
    
    /**
     * イベントをログに記録
     */
    private function logEvent($eventType, $data) {
        $entry = [
            'event_type' => $eventType,
            'timestamp' => time(),
            'data' => $data
        ];
        
        $this->auditLog[] = $entry;
        
        // ファイルに追記
        $logLine = json_encode($entry) . "\n";
        file_put_contents($this->logFile, $logLine, FILE_APPEND);
    }
    
    /**
     * 監査ログを読み込み
     */
    private function loadAuditLog() {
        if (!file_exists($this->logFile)) {
            return;
        }
        
        $lines = file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        
        foreach ($lines as $line) {
            $entry = json_decode($line, true);
            if ($entry) {
                $this->auditLog[] = $entry;
            }
        }
    }
    
    /**
     * セッションID使用パターンを分析
     */
    public function analyzePatterns() {
        $patterns = [
            'total_created' => 0,
            'by_prefix' => [],
            'by_hour' => array_fill(0, 24, 0),
            'by_ip' => []
        ];
        
        foreach ($this->auditLog as $entry) {
            if ($entry['event_type'] !== 'session_created') {
                continue;
            }
            
            $patterns['total_created']++;
            
            // プレフィックス別
            $prefix = $entry['data']['prefix'] ?? 'none';
            if (!isset($patterns['by_prefix'][$prefix])) {
                $patterns['by_prefix'][$prefix] = 0;
            }
            $patterns['by_prefix'][$prefix]++;
            
            // 時間帯別
            $hour = (int)date('G', $entry['timestamp']);
            $patterns['by_hour'][$hour]++;
            
            // IP別
            $ip = $entry['data']['ip_address'] ?? 'unknown';
            if (!isset($patterns['by_ip'][$ip])) {
                $patterns['by_ip'][$ip] = 0;
            }
            $patterns['by_ip'][$ip]++;
        }
        
        return $patterns;
    }
    
    /**
     * 疑わしい活動を検出
     */
    public function detectSuspiciousActivity() {
        $suspicious = [];
        $patterns = $this->analyzePatterns();
        
        // 同一IPからの過剰なセッション生成
        foreach ($patterns['by_ip'] as $ip => $count) {
            if ($count > 100) {
                $suspicious[] = [
                    'type' => 'excessive_sessions',
                    'ip' => $ip,
                    'count' => $count,
                    'severity' => 'high'
                ];
            }
        }
        
        // 短時間での大量生成
        $recentSessions = array_filter($this->auditLog, function($entry) {
            return $entry['timestamp'] > (time() - 300) && 
                   $entry['event_type'] === 'session_created';
        });
        
        if (count($recentSessions) > 50) {
            $suspicious[] = [
                'type' => 'burst_creation',
                'count' => count($recentSessions),
                'timeframe' => '5 minutes',
                'severity' => 'medium'
            ];
        }
        
        return $suspicious;
    }
    
    /**
     * 監査レポートを生成
     */
    public function generateReport() {
        $patterns = $this->analyzePatterns();
        $suspicious = $this->detectSuspiciousActivity();
        
        $report = [
            'generated_at' => time(),
            'summary' => [
                'total_sessions' => $patterns['total_created'],
                'unique_ips' => count($patterns['by_ip']),
                'unique_prefixes' => count($patterns['by_prefix']),
                'suspicious_activities' => count($suspicious)
            ],
            'patterns' => $patterns,
            'suspicious_activities' => $suspicious,
            'recommendations' => $this->generateRecommendations($patterns, $suspicious)
        ];
        
        return $report;
    }
    
    /**
     * 推奨事項を生成
     */
    private function generateRecommendations($patterns, $suspicious) {
        $recommendations = [];
        
        if (count($suspicious) > 0) {
            $recommendations[] = "疑わしい活動が検出されました。セキュリティを強化してください。";
        }
        
        $peakHour = array_search(max($patterns['by_hour']), $patterns['by_hour']);
        $recommendations[] = "ピーク時間帯は{$peakHour}時です。この時間帯のパフォーマンスを最適化してください。";
        
        if ($patterns['total_created'] > 1000) {
            $recommendations[] = "大量のセッションが生成されています。セッションクリーンアップを検討してください。";
        }
        
        return $recommendations;
    }
    
    /**
     * 監査ログをクリア
     */
    public function clearAuditLog() {
        $this->auditLog = [];
        if (file_exists($this->logFile)) {
            unlink($this->logFile);
        }
    }
}

// 使用例
echo "=== セッションID監査システム ===\n";

$audit = new SessionIdAuditSystem('/tmp/session_audit_test.log');

// セッションIDを生成(監査付き)
echo "セッションID生成(監査付き):\n";
for ($i = 0; $i < 20; $i++) {
    $prefix = $i % 2 === 0 ? 'app1-' : 'app2-';
    $sessionId = $audit->auditSessionCreation($prefix, [
        'user_type' => $i % 2 === 0 ? 'regular' : 'premium',
        'source' => 'web'
    ]);
    
    if ($i < 3) {
        echo "  {$sessionId}\n";
    }
}
echo "  ... (合計20個生成)\n";

// パターン分析
echo "\n=== パターン分析 ===\n";
$patterns = $audit->analyzePatterns();
echo "総生成数: {$patterns['total_created']}\n";
echo "\nプレフィックス別:\n";
foreach ($patterns['by_prefix'] as $prefix => $count) {
    echo "  {$prefix}: {$count}個\n";
}

// 疑わしい活動の検出
echo "\n=== 疑わしい活動 ===\n";
$suspicious = $audit->detectSuspiciousActivity();
if (empty($suspicious)) {
    echo "疑わしい活動は検出されませんでした\n";
} else {
    foreach ($suspicious as $activity) {
        echo "タイプ: {$activity['type']}\n";
        echo "  深刻度: {$activity['severity']}\n";
    }
}

// 監査レポート
echo "\n=== 監査レポート ===\n";
$report = $audit->generateReport();
echo "レポート生成日時: " . date('Y-m-d H:i:s', $report['generated_at']) . "\n";
echo "\nサマリー:\n";
foreach ($report['summary'] as $key => $value) {
    echo "  {$key}: {$value}\n";
}

echo "\n推奨事項:\n";
foreach ($report['recommendations'] as $recommendation) {
    echo "  - {$recommendation}\n";
}

// クリーンアップ
$audit->clearAuditLog();

例6: カスタムセッションIDバリデーション

class SessionIdValidator {
    private $rules = [];
    private $errors = [];
    
    /**
     * バリデーションルールを追加
     */
    public function addRule($name, callable $validator, $errorMessage) {
        $this->rules[$name] = [
            'validator' => $validator,
            'message' => $errorMessage
        ];
        
        return $this;
    }
    
    /**
     * セッションIDを検証
     */
    public function validate($sessionId) {
        $this->errors = [];
        $results = [];
        
        foreach ($this->rules as $name => $rule) {
            $isValid = call_user_func($rule['validator'], $sessionId);
            
            $results[$name] = [
                'valid' => $isValid,
                'message' => $isValid ? 'OK' : $rule['message']
            ];
            
            if (!$isValid) {
                $this->errors[$name] = $rule['message'];
            }
        }
        
        return [
            'valid' => empty($this->errors),
            'results' => $results,
            'errors' => $this->errors
        ];
    }
    
    /**
     * 標準的なバリデーションルールをセットアップ
     */
    public function setupStandardRules() {
        // 最小長チェック
        $this->addRule('min_length', function($id) {
            return strlen($id) >= 32;
        }, 'セッションIDは32文字以上である必要があります');
        
        // 最大長チェック
        $this->addRule('max_length', function($id) {
            return strlen($id) <= 128;
        }, 'セッションIDは128文字以下である必要があります');
        
        // 文字種チェック
        $this->addRule('valid_characters', function($id) {
            return preg_match('/^[a-zA-Z0-9,-]+$/', $id) === 1;
        }, 'セッションIDに不正な文字が含まれています');
        
        // プレフィックス形式チェック
        $this->addRule('prefix_format', function($id) {
            if (strpos($id, '-') === false) {
                return true;  // プレフィックスなしはOK
            }
            
            // プレフィックスは先頭にあること
            return preg_match('/^[a-zA-Z0-9,]+-/', $id) === 1;
        }, 'プレフィックスの形式が不正です');
        
        return $this;
    }
    
    /**
     * セキュリティレベル別のルール
     */
    public function setupSecurityRules($level = 'medium') {
        $this->setupStandardRules();
        
        switch ($level) {
            case 'high':
                // エントロピーチェック
                $this->addRule('high_entropy', function($id) {
                    $uniqueChars = count(array_unique(str_split($id)));
                    return $uniqueChars >= 16;
                }, '十分なエントロピーがありません');
                
                // 予測可能なパターンのチェック
                $this->addRule('no_patterns', function($id) {
                    // 連続する文字のチェック
                    return !preg_match('/(.)\1{3,}/', $id);
                }, '予測可能なパターンが含まれています');
                
                break;
                
            case 'low':
                // 基本的なチェックのみ
                break;
                
            case 'medium':
            default:
                // 適度なエントロピーチェック
                $this->addRule('medium_entropy', function($id) {
                    $uniqueChars = count(array_unique(str_split($id)));
                    return $uniqueChars >= 12;
                }, 'エントロピーが不十分です');
        }
        
        return $this;
    }
    
    /**
     * カスタムプレフィックスルール
     */
    public function requirePrefix($allowedPrefixes) {
        $this->addRule('required_prefix', function($id) use ($allowedPrefixes) {
            foreach ($allowedPrefixes as $prefix) {
                if (strpos($id, $prefix) === 0) {
                    return true;
                }
            }
            return false;
        }, '許可されたプレフィックスが含まれていません: ' . implode(', ', $allowedPrefixes));
        
        return $this;
    }
    
    /**
     * セッションIDを生成して検証
     */
    public function generateAndValidate($prefix = '', $maxAttempts = 10) {
        for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
            $sessionId = session_create_id($prefix);
            
            if ($sessionId === false) {
                continue;
            }
            
            $validation = $this->validate($sessionId);
            
            if ($validation['valid']) {
                return [
                    'success' => true,
                    'session_id' => $sessionId,
                    'attempts' => $attempt,
                    'validation' => $validation
                ];
            }
        }
        
        return [
            'success' => false,
            'error' => '有効なセッションIDを生成できませんでした',
            'attempts' => $maxAttempts
        ];
    }
    
    /**
     * バッチ検証
     */
    public function validateBatch($sessionIds) {
        $results = [];
        
        foreach ($sessionIds as $id) {
            $results[$id] = $this->validate($id);
        }
        
        return [
            'total' => count($sessionIds),
            'valid' => count(array_filter($results, function($r) { return $r['valid']; })),
            'invalid' => count(array_filter($results, function($r) { return !$r['valid']; })),
            'results' => $results
        ];
    }
    
    /**
     * エラーを取得
     */
    public function getErrors() {
        return $this->errors;
    }
}

// 使用例
echo "=== カスタムセッションIDバリデーション ===\n";

$validator = new SessionIdValidator();

// 標準ルールをセットアップ
echo "標準ルールで検証:\n";
$validator->setupStandardRules();

$testId1 = session_create_id('test-');
$result1 = $validator->validate($testId1);
echo "  セッションID: {$testId1}\n";
echo "  検証結果: " . ($result1['valid'] ? '有効' : '無効') . "\n";
if (!$result1['valid']) {
    echo "  エラー:\n";
    foreach ($result1['errors'] as $rule => $message) {
        echo "    - {$message}\n";
    }
}

// セキュリティレベル別
echo "\n高セキュリティレベルで検証:\n";
$validator2 = new SessionIdValidator();
$validator2->setupSecurityRules('high');

$testId2 = session_create_id('secure-');
$result2 = $validator2->validate($testId2);
echo "  セッションID: {$testId2}\n";
echo "  検証結果: " . ($result2['valid'] ? '有効' : '無効') . "\n";

// プレフィックス要件
echo "\nプレフィックス要件付き検証:\n";
$validator3 = new SessionIdValidator();
$validator3->setupStandardRules()
           ->requirePrefix(['app1-', 'app2-', 'admin-']);

$testIds = [
    session_create_id('app1-'),
    session_create_id('app2-'),
    session_create_id('invalid-')
];

foreach ($testIds as $id) {
    $result = $validator3->validate($id);
    echo "  {$id}: " . ($result['valid'] ? '有効' : '無効') . "\n";
}

// 生成と検証
echo "\n生成と検証:\n";
$validator4 = new SessionIdValidator();
$validator4->setupSecurityRules('medium')
           ->requirePrefix(['myapp-']);

$generated = $validator4->generateAndValidate('myapp-');
if ($generated['success']) {
    echo "  成功\n";
    echo "  セッションID: {$generated['session_id']}\n";
    echo "  試行回数: {$generated['attempts']}\n";
} else {
    echo "  失敗: {$generated['error']}\n";
}

// バッチ検証
echo "\n=== バッチ検証 ===\n";
$batchIds = [];
for ($i = 0; $i < 5; $i++) {
    $batchIds[] = session_create_id('batch-');
}

$validator5 = new SessionIdValidator();
$validator5->setupStandardRules();
$batchResults = $validator5->validateBatch($batchIds);

echo "総数: {$batchResults['total']}\n";
echo "有効: {$batchResults['valid']}\n";
echo "無効: {$batchResults['invalid']}\n";

プレフィックスの制約

// 使用可能な文字
// - 英数字: a-z, A-Z, 0-9
// - カンマ: ,
// - ハイフン: -

// 正しい例
$id1 = session_create_id('abc123-');      // OK
$id2 = session_create_id('USER,001-');    // OK
$id3 = session_create_id('app-v2-');      // OK

// 不正な例(警告が発生)
// $id4 = session_create_id('app_1-');    // アンダースコア不可
// $id5 = session_create_id('app.1-');    // ドット不可
// $id6 = session_create_id('app@1-');    // 記号不可
// $id7 = session_create_id('アプリ-');    // 日本語不可

// プレフィックスの長さ制限なし(実用的には短くすべき)
$longPrefix = str_repeat('a', 100) . '-';
$id8 = session_create_id($longPrefix);  // 可能だが非推奨

まとめ

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

できること:

  • 新しいセッションIDの生成
  • プレフィックス付きセッションIDの作成
  • アプリケーション間のセッション分離
  • カスタムセッションID構造の実現

プレフィックス:

  • 使用可能文字: 英数字、カンマ(,)、ハイフン(-)
  • 長さ制限: なし(実用的には短く)
  • 用途: アプリケーション識別、環境識別、テナント分離

推奨される使用場面:

  • マルチテナントシステム
  • 複数アプリケーションの共存
  • セッションの論理的分離
  • セキュリティ強化
  • セッション監査

重要な注意点:

  • session_start()の前にsession_id()でIDを設定
  • プレフィックスに不正な文字を使用すると警告
  • PHP 7.1.0以降で使用可能
  • セッションIDの一意性は保証される

ベストプラクティス:

// 1. アプリケーション識別
$appId = session_create_id('myapp-');
session_id($appId);
session_start();

// 2. 環境別のプレフィックス
$env = getenv('APP_ENV');  // 'prod', 'dev', etc.
$prefix = "{$env}-";
$sessionId = session_create_id($prefix);

// 3. ユーザー識別付き
$userId = getCurrentUserId();
$userHash = substr(md5($userId), 0, 8);
$sessionId = session_create_id("u{$userHash}-");

// 4. セキュリティ強化
$timestamp = base_convert(time(), 10, 36);
$sessionId = session_create_id("sec{$timestamp}-");

関連関数:

  • session_id(): セッションIDの取得/設定
  • session_start(): セッション開始
  • session_regenerate_id(): セッションID再生成
  • session_name(): セッション名の取得/設定

session_create_id()は、柔軟なセッション管理を実現する重要な関数です。プレフィックスを活用することで、複数のアプリケーションやテナントを効果的に分離し、セキュリティとメンテナンス性を向上できます!

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