こんにちは!今回は、PHPの標準関数であるsession_decode()について詳しく解説していきます。エンコードされたセッションデータを$_SESSION変数に復元できる、セッション操作の高度な関数です!
session_decode関数とは?
session_decode()関数は、エンコードされた文字列からセッションデータをデコードして$_SESSION変数に格納する関数です。
session_encode()の逆の操作を行い、セッションデータのバックアップ・復元、移行、カスタムストレージの実装などに使用されます!
基本的な構文
session_decode(string $data): bool
- $data: エンコードされたセッションデータ文字列
- 戻り値: 成功時は
true、失敗時はfalse
session_encode()との関係
// エンコード: $_SESSION → 文字列
session_start();
$_SESSION['user'] = 'alice';
$_SESSION['role'] = 'admin';
$encoded = session_encode();
echo $encoded; // 例: user|s:5:"alice";role|s:5:"admin";
// デコード: 文字列 → $_SESSION
session_start();
$data = 'user|s:5:"alice";role|s:5:"admin";';
session_decode($data);
echo $_SESSION['user']; // alice
echo $_SESSION['role']; // admin
セッションハンドラーの種類
// PHPのセッションシリアライザー
// 1. php (デフォルト)
// 形式: key|serialized_value
$encoded = 'name|s:4:"John";age|i:30;';
// 2. php_binary
// 形式: バイナリ形式(キーの長さをバイナリで表現)
// 読みにくいが高速
// 3. php_serialize
// 形式: 配列全体をserialize()
$encoded = 'a:2:{s:4:"name";s:4:"John";s:3:"age";i:30;}';
// ハンドラーを確認
echo ini_get('session.serialize_handler'); // 通常は 'php'
// ハンドラーを変更
ini_set('session.serialize_handler', 'php_serialize');
重要な注意点
// セキュリティ警告
// session_decode()は信頼できないデータに使用しない!
// 悪意のあるデータでオブジェクトインジェクション攻撃が可能
// 危険な例
$untrustedData = $_POST['session_data']; // ユーザー入力
session_decode($untrustedData); // 危険!
// session_start()後に使用
session_start();
session_decode($data); // 正しい
// デコード前にsession_start()がないとエラー
// session_decode($data); // エラー
// $_SESSION配列を上書き
session_start();
$_SESSION['old'] = 'value';
session_decode('new|s:5:"value";');
// $_SESSION['old']は消える(完全に置き換わる)
基本的な使用例
シンプルなエンコード・デコード
// セッション開始
session_start();
// データを設定
$_SESSION['username'] = 'alice';
$_SESSION['email'] = 'alice@example.com';
$_SESSION['login_time'] = time();
// エンコード
$encoded = session_encode();
echo "エンコード: {$encoded}\n";
// セッションをクリア
$_SESSION = [];
// デコード
$result = session_decode($encoded);
echo "デコード結果: " . ($result ? '成功' : '失敗') . "\n";
// データを確認
echo "ユーザー名: {$_SESSION['username']}\n";
echo "メール: {$_SESSION['email']}\n";
異なるハンドラー間の変換
session_start();
$_SESSION['data'] = 'test';
// phpハンドラーでエンコード
ini_set('session.serialize_handler', 'php');
$encodedPhp = session_encode();
echo "PHP形式: {$encodedPhp}\n";
// php_serializeハンドラーでエンコード
ini_set('session.serialize_handler', 'php_serialize');
$encodedPhpSerialize = session_encode();
echo "PHP Serialize形式: {$encodedPhpSerialize}\n";
// デコード(正しいハンドラーを使用)
$_SESSION = [];
ini_set('session.serialize_handler', 'php');
session_decode($encodedPhp);
echo "復元されたデータ: {$_SESSION['data']}\n";
セッションデータの上書き
session_start();
// 初期データ
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'alice';
echo "初期データ: " . count($_SESSION) . "件\n";
// 新しいデータでデコード(完全に置き換わる)
$newData = 'role|s:5:"admin";status|s:6:"active";';
session_decode($newData);
echo "デコード後: " . count($_SESSION) . "件\n";
echo "role: {$_SESSION['role']}\n";
echo "username存在: " . (isset($_SESSION['username']) ? 'Yes' : 'No') . "\n";
// usernameは消える
部分的なマージ(カスタム実装)
session_start();
// 現在のセッション
$_SESSION['existing'] = 'value1';
$_SESSION['keep'] = 'value2';
// 現在のデータを保存
$currentSession = $_SESSION;
// 新しいデータをデコード
$newData = 'new|s:6:"value3";update|s:6:"value4";';
session_decode($newData);
// マージ
$_SESSION = array_merge($currentSession, $_SESSION);
echo "マージ後:\n";
foreach ($_SESSION as $key => $value) {
echo " {$key}: {$value}\n";
}
実践的な使用例
例1: セッションバックアップ・復元システム
class SessionBackupManager {
private $backupDir;
/**
* バックアップマネージャーを初期化
*/
public function __construct($backupDir = '/tmp/session_backups') {
$this->backupDir = $backupDir;
if (!is_dir($backupDir)) {
mkdir($backupDir, 0755, true);
}
}
/**
* 現在のセッションをバックアップ
*/
public function backup($backupName = null) {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
// セッションデータをエンコード
$encoded = session_encode();
if ($encoded === false) {
throw new Exception("Failed to encode session");
}
// バックアップ名を生成
if ($backupName === null) {
$backupName = 'backup_' . date('YmdHis') . '_' . uniqid();
}
// バックアップファイルに保存
$backupFile = $this->backupDir . '/' . $backupName . '.dat';
$backupData = [
'timestamp' => time(),
'session_id' => session_id(),
'encoded_data' => $encoded,
'handler' => ini_get('session.serialize_handler'),
'data_count' => count($_SESSION)
];
$result = file_put_contents($backupFile, serialize($backupData));
if ($result === false) {
throw new Exception("Failed to write backup file");
}
return [
'backup_name' => $backupName,
'file' => $backupFile,
'size' => $result,
'timestamp' => $backupData['timestamp'],
'data_count' => $backupData['data_count']
];
}
/**
* バックアップからセッションを復元
*/
public function restore($backupName, $merge = false) {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$backupFile = $this->backupDir . '/' . $backupName . '.dat';
if (!file_exists($backupFile)) {
throw new Exception("Backup file not found: {$backupName}");
}
// バックアップデータを読み込み
$backupData = unserialize(file_get_contents($backupFile));
if ($backupData === false) {
throw new Exception("Failed to unserialize backup data");
}
// 現在のセッションを保存(マージ用)
$currentSession = $merge ? $_SESSION : [];
// ハンドラーを一時的に変更
$originalHandler = ini_get('session.serialize_handler');
ini_set('session.serialize_handler', $backupData['handler']);
// セッションデータをデコード
$result = session_decode($backupData['encoded_data']);
// ハンドラーを元に戻す
ini_set('session.serialize_handler', $originalHandler);
if (!$result) {
throw new Exception("Failed to decode session data");
}
// マージが必要な場合
if ($merge) {
$_SESSION = array_merge($currentSession, $_SESSION);
}
return [
'success' => true,
'backup_name' => $backupName,
'timestamp' => $backupData['timestamp'],
'restored_count' => count($_SESSION),
'merged' => $merge
];
}
/**
* バックアップ一覧を取得
*/
public function listBackups() {
$backups = [];
$files = glob($this->backupDir . '/*.dat');
foreach ($files as $file) {
$data = unserialize(file_get_contents($file));
$backups[] = [
'name' => basename($file, '.dat'),
'file' => $file,
'timestamp' => $data['timestamp'],
'date' => date('Y-m-d H:i:s', $data['timestamp']),
'size' => filesize($file),
'data_count' => $data['data_count']
];
}
// タイムスタンプでソート(新しい順)
usort($backups, function($a, $b) {
return $b['timestamp'] - $a['timestamp'];
});
return $backups;
}
/**
* バックアップを削除
*/
public function deleteBackup($backupName) {
$backupFile = $this->backupDir . '/' . $backupName . '.dat';
if (!file_exists($backupFile)) {
return false;
}
return unlink($backupFile);
}
/**
* 古いバックアップをクリーンアップ
*/
public function cleanupOldBackups($maxAge = 86400) {
$deleted = 0;
$backups = $this->listBackups();
$cutoff = time() - $maxAge;
foreach ($backups as $backup) {
if ($backup['timestamp'] < $cutoff) {
if ($this->deleteBackup($backup['name'])) {
$deleted++;
}
}
}
return [
'deleted' => $deleted,
'cutoff_date' => date('Y-m-d H:i:s', $cutoff)
];
}
/**
* バックアップを比較
*/
public function compareBackups($backupName1, $backupName2) {
$file1 = $this->backupDir . '/' . $backupName1 . '.dat';
$file2 = $this->backupDir . '/' . $backupName2 . '.dat';
if (!file_exists($file1) || !file_exists($file2)) {
throw new Exception("One or both backup files not found");
}
$data1 = unserialize(file_get_contents($file1));
$data2 = unserialize(file_get_contents($file2));
// 一時的にデコード
$originalHandler = ini_get('session.serialize_handler');
ini_set('session.serialize_handler', $data1['handler']);
session_decode($data1['encoded_data']);
$session1 = $_SESSION;
ini_set('session.serialize_handler', $data2['handler']);
session_decode($data2['encoded_data']);
$session2 = $_SESSION;
ini_set('session.serialize_handler', $originalHandler);
// 差分を計算
$diff = [
'only_in_backup1' => array_diff_key($session1, $session2),
'only_in_backup2' => array_diff_key($session2, $session1),
'common' => array_intersect_key($session1, $session2),
'changed' => []
];
foreach ($diff['common'] as $key => $value) {
if ($session1[$key] !== $session2[$key]) {
$diff['changed'][$key] = [
'backup1' => $session1[$key],
'backup2' => $session2[$key]
];
}
}
return $diff;
}
}
// 使用例
echo "=== セッションバックアップ・復元 ===\n";
// セッション初期化
session_start();
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'alice';
$_SESSION['role'] = 'admin';
$_SESSION['login_time'] = time();
echo "初期セッション: " . count($_SESSION) . "件のデータ\n";
$manager = new SessionBackupManager('/tmp/session_backups_test');
// バックアップ作成
echo "\nバックアップ作成:\n";
$backup1 = $manager->backup('backup1');
echo " バックアップ名: {$backup1['backup_name']}\n";
echo " ファイルサイズ: {$backup1['size']} bytes\n";
echo " データ数: {$backup1['data_count']}\n";
// セッションを変更
$_SESSION['status'] = 'active';
$_SESSION['last_activity'] = time();
unset($_SESSION['role']);
echo "\nセッション変更後: " . count($_SESSION) . "件のデータ\n";
// 2つ目のバックアップ
$backup2 = $manager->backup('backup2');
echo "2つ目のバックアップ作成: {$backup2['backup_name']}\n";
// セッションをクリア
$_SESSION = [];
echo "\nセッションクリア後: " . count($_SESSION) . "件\n";
// 復元
echo "\nバックアップから復元:\n";
$restored = $manager->restore('backup1');
echo " 復元成功: " . ($restored['success'] ? 'Yes' : 'No') . "\n";
echo " 復元データ数: {$restored['restored_count']}\n";
echo " 復元後のユーザー名: {$_SESSION['username']}\n";
// バックアップ一覧
echo "\n=== バックアップ一覧 ===\n";
$backups = $manager->listBackups();
foreach ($backups as $backup) {
echo "{$backup['name']}:\n";
echo " 日時: {$backup['date']}\n";
echo " データ数: {$backup['data_count']}\n";
}
// バックアップ比較
echo "\n=== バックアップ比較 ===\n";
$diff = $manager->compareBackups('backup1', 'backup2');
echo "backup1のみ: " . count($diff['only_in_backup1']) . "件\n";
echo "backup2のみ: " . count($diff['only_in_backup2']) . "件\n";
echo "変更: " . count($diff['changed']) . "件\n";
例2: セッションデータ移行ツール
class SessionMigrationTool {
/**
* セッションデータを別のハンドラー形式に変換
*/
public function convertHandler($sessionData, $fromHandler, $toHandler) {
// 元のハンドラーで設定
$originalHandler = ini_get('session.serialize_handler');
session_start();
// 元の形式でデコード
ini_set('session.serialize_handler', $fromHandler);
$result = session_decode($sessionData);
if (!$result) {
throw new Exception("Failed to decode with handler: {$fromHandler}");
}
// 新しい形式でエンコード
ini_set('session.serialize_handler', $toHandler);
$converted = session_encode();
// 元に戻す
ini_set('session.serialize_handler', $originalHandler);
return [
'from_handler' => $fromHandler,
'to_handler' => $toHandler,
'original_data' => $sessionData,
'converted_data' => $converted,
'original_length' => strlen($sessionData),
'converted_length' => strlen($converted)
];
}
/**
* 旧バージョンのセッション形式を新バージョンに移行
*/
public function migrateFromLegacy($legacyData) {
session_start();
// 旧形式をパース(カスタムパーサー)
$parsed = $this->parseLegacyFormat($legacyData);
// 現在の形式で再エンコード
$_SESSION = $parsed;
$newData = session_encode();
return [
'legacy_data' => $legacyData,
'parsed_count' => count($parsed),
'new_data' => $newData,
'migrated_keys' => array_keys($parsed)
];
}
/**
* 旧形式をパース(例:カスタム区切り文字形式)
*/
private function parseLegacyFormat($data) {
$result = [];
// 例: key1:value1;key2:value2; 形式
$pairs = explode(';', trim($data, ';'));
foreach ($pairs as $pair) {
if (empty($pair)) continue;
list($key, $value) = explode(':', $pair, 2);
$result[$key] = $value;
}
return $result;
}
/**
* 複数のセッションファイルを一括移行
*/
public function batchMigrate($sessionDir, $outputDir, $fromHandler, $toHandler) {
if (!is_dir($outputDir)) {
mkdir($outputDir, 0755, true);
}
$files = glob($sessionDir . '/sess_*');
$migrated = 0;
$failed = 0;
$errors = [];
foreach ($files as $file) {
try {
$sessionData = file_get_contents($file);
$converted = $this->convertHandler($sessionData, $fromHandler, $toHandler);
$outputFile = $outputDir . '/' . basename($file);
file_put_contents($outputFile, $converted['converted_data']);
$migrated++;
} catch (Exception $e) {
$failed++;
$errors[] = [
'file' => basename($file),
'error' => $e->getMessage()
];
}
}
return [
'total' => count($files),
'migrated' => $migrated,
'failed' => $failed,
'errors' => $errors
];
}
/**
* セッションデータを検証
*/
public function validateSessionData($sessionData, $handler = 'php') {
$originalHandler = ini_get('session.serialize_handler');
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
$result = session_decode($sessionData);
ini_set('session.serialize_handler', $originalHandler);
return [
'valid' => $result,
'handler' => $handler,
'data_count' => $result ? count($_SESSION) : 0,
'keys' => $result ? array_keys($_SESSION) : []
];
}
/**
* セッションデータの構造を分析
*/
public function analyzeStructure($sessionData, $handler = 'php') {
$validation = $this->validateSessionData($sessionData, $handler);
if (!$validation['valid']) {
return ['error' => 'Invalid session data'];
}
session_start();
ini_set('session.serialize_handler', $handler);
session_decode($sessionData);
$analysis = [
'total_keys' => count($_SESSION),
'data_types' => [],
'nested_arrays' => 0,
'objects' => 0,
'total_size' => strlen($sessionData)
];
foreach ($_SESSION as $key => $value) {
$type = gettype($value);
if (!isset($analysis['data_types'][$type])) {
$analysis['data_types'][$type] = 0;
}
$analysis['data_types'][$type]++;
if (is_array($value) && count($value) > 0) {
$analysis['nested_arrays']++;
}
if (is_object($value)) {
$analysis['objects']++;
}
}
return $analysis;
}
}
// 使用例
echo "=== セッションデータ移行 ===\n";
$migrator = new SessionMigrationTool();
// セッションデータを作成
session_start();
$_SESSION = [
'user_id' => 456,
'username' => 'bob',
'preferences' => ['theme' => 'dark', 'lang' => 'ja'],
'login_count' => 5
];
// php形式でエンコード
ini_set('session.serialize_handler', 'php');
$phpData = session_encode();
echo "PHP形式: {$phpData}\n";
// php_serialize形式に変換
echo "\n形式変換:\n";
$converted = $migrator->convertHandler($phpData, 'php', 'php_serialize');
echo " 元の形式: {$converted['from_handler']}\n";
echo " 新しい形式: {$converted['to_handler']}\n";
echo " 元のサイズ: {$converted['original_length']} bytes\n";
echo " 新しいサイズ: {$converted['converted_length']} bytes\n";
echo " 変換後のデータ: {$converted['converted_data']}\n";
// データ検証
echo "\nデータ検証:\n";
$validation = $migrator->validateSessionData($phpData, 'php');
echo " 有効: " . ($validation['valid'] ? 'Yes' : 'No') . "\n";
echo " キー数: {$validation['data_count']}\n";
echo " キー: " . implode(', ', $validation['keys']) . "\n";
// 構造分析
echo "\n構造分析:\n";
$analysis = $migrator->analyzeStructure($phpData, 'php');
echo " 総キー数: {$analysis['total_keys']}\n";
echo " 総サイズ: {$analysis['total_size']} bytes\n";
echo " データ型:\n";
foreach ($analysis['data_types'] as $type => $count) {
echo " {$type}: {$count}\n";
}
// 旧形式からの移行
echo "\n旧形式からの移行:\n";
$legacyData = 'user_id:789;username:charlie;status:active;';
$migrated = $migrator->migrateFromLegacy($legacyData);
echo " 旧データ: {$migrated['legacy_data']}\n";
echo " パース数: {$migrated['parsed_count']}\n";
echo " 移行キー: " . implode(', ', $migrated['migrated_keys']) . "\n";
echo " 新データ: {$migrated['new_data']}\n";
例3: カスタムセッションストレージ
class CustomSessionStorage {
private $storage = [];
/**
* セッションをカスタムストレージに保存
*/
public function save($key) {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$encoded = session_encode();
if ($encoded === false) {
throw new Exception("Failed to encode session");
}
$this->storage[$key] = [
'data' => $encoded,
'timestamp' => time(),
'session_id' => session_id(),
'handler' => ini_get('session.serialize_handler')
];
return true;
}
/**
* カスタムストレージからセッションを読み込み
*/
public function load($key, $merge = false) {
if (!isset($this->storage[$key])) {
throw new Exception("Storage key not found: {$key}");
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$stored = $this->storage[$key];
// 現在のセッションを保存(マージ用)
$currentSession = $merge ? $_SESSION : [];
// ハンドラーを設定
$originalHandler = ini_get('session.serialize_handler');
ini_set('session.serialize_handler', $stored['handler']);
// デコード
$result = session_decode($stored['data']);
// ハンドラーを戻す
ini_set('session.serialize_handler', $originalHandler);
if (!$result) {
throw new Exception("Failed to decode session data");
}
// マージ
if ($merge) {
$_SESSION = array_merge($currentSession, $_SESSION);
}
return [
'key' => $key,
'timestamp' => $stored['timestamp'],
'age' => time() - $stored['timestamp'],
'merged' => $merge
];
}
/**
* ストレージ内のすべてのキーを取得
*/
public function listKeys() {
$keys = [];
foreach ($this->storage as $key => $data) {
$keys[] = [
'key' => $key,
'timestamp' => $data['timestamp'],
'age' => time() - $data['timestamp'],
'size' => strlen($data['data'])
];
}
return $keys;
}
/**
* ストレージをクリア
*/
public function clear($key = null) {
if ($key === null) {
$this->storage = [];
return true;
}
if (isset($this->storage[$key])) {
unset($this->storage[$key]);
return true;
}
return false;
}
/**
* セッションの差分を保存
*/
public function saveDiff($key, $baseKey) {
if (!isset($this->storage[$baseKey])) {
throw new Exception("Base key not found: {$baseKey}");
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
// ベースセッションをデコード
$originalHandler = ini_get('session.serialize_handler');
$baseData = $this->storage[$baseKey];
ini_set('session.serialize_handler', $baseData['handler']);
session_decode($baseData['data']);
$baseSession = $_SESSION;
// 現在のセッション
session_start();
$currentSession = $_SESSION;
// 差分を計算
$diff = [
'added' => array_diff_key($currentSession, $baseSession),
'removed' => array_diff_key($baseSession, $currentSession),
'modified' => []
];
foreach (array_intersect_key($currentSession, $baseSession) as $key => $value) {
if ($currentSession[$key] !== $baseSession[$key]) {
$diff['modified'][$key] = $currentSession[$key];
}
}
ini_set('session.serialize_handler', $originalHandler);
$this->storage[$key] = [
'type' => 'diff',
'base_key' => $baseKey,
'diff' => $diff,
'timestamp' => time()
];
return $diff;
}
/**
* 差分を適用
*/
public function applyDiff($diffKey) {
if (!isset($this->storage[$diffKey])) {
throw new Exception("Diff key not found: {$diffKey}");
}
$diffData = $this->storage[$diffKey];
if ($diffData['type'] !== 'diff') {
throw new Exception("Not a diff storage: {$diffKey}");
}
// ベースをロード
$this->load($diffData['base_key']);
// 差分を適用
$diff = $diffData['diff'];
// 追加
foreach ($diff['added'] as $key => $value) {
$_SESSION[$key] = $value;
}
// 削除
foreach ($diff['removed'] as $key => $value) {
unset($_SESSION[$key]);
}
// 変更
foreach ($diff['modified'] as $key => $value) {
$_SESSION[$key] = $value;
}
return true;
}
}
// 使用例
echo "=== カスタムセッションストレージ ===\n";
$storage = new CustomSessionStorage();
// セッションを保存
session_start();
$_SESSION = [
'user_id' => 111,
'username' => 'dave',
'role' => 'user'
];
$storage->save('state1');
echo "状態1を保存\n";
// セッションを変更
$_SESSION['status'] = 'active';
$_SESSION['last_login'] = time();
unset($_SESSION['role']);
$storage->save('state2');
echo "状態2を保存\n";
// セッションをクリア
$_SESSION = [];
echo "セッションクリア\n";
// 状態1を復元
echo "\n状態1を復元:\n";
$loaded = $storage->load('state1');
echo " キー: {$loaded['key']}\n";
echo " 経過時間: {$loaded['age']}秒\n";
echo " 復元データ: " . count($_SESSION) . "件\n";
echo " ユーザー名: {$_SESSION['username']}\n";
// ストレージ一覧
echo "\nストレージキー一覧:\n";
foreach ($storage->listKeys() as $info) {
echo " {$info['key']}: {$info['size']} bytes, {$info['age']}秒前\n";
}
// 差分保存
session_start();
$_SESSION = ['base' => 'value', 'keep' => 'data'];
$storage->save('base');
$_SESSION['new'] = 'added';
$_SESSION['base'] = 'modified';
unset($_SESSION['keep']);
$diff = $storage->saveDiff('diff1', 'base');
echo "\n差分保存:\n";
echo " 追加: " . count($diff['added']) . "件\n";
echo " 削除: " . count($diff['removed']) . "件\n";
echo " 変更: " . count($diff['modified']) . "件\n";
例4: セッションデバッグツール
class SessionDebugTool {
/**
* セッションデータを可視化
*/
public function visualize($sessionData = null, $handler = 'php') {
if ($sessionData === null) {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$sessionData = session_encode();
}
$originalHandler = ini_get('session.serialize_handler');
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
$result = session_decode($sessionData);
ini_set('session.serialize_handler', $originalHandler);
if (!$result) {
return ['error' => 'Failed to decode session data'];
}
$visualization = [
'raw_data' => $sessionData,
'handler' => $handler,
'total_keys' => count($_SESSION),
'keys' => []
];
foreach ($_SESSION as $key => $value) {
$visualization['keys'][$key] = [
'type' => gettype($value),
'value' => $this->formatValue($value),
'size' => strlen(serialize($value))
];
}
return $visualization;
}
/**
* 値をフォーマット
*/
private function formatValue($value, $maxLength = 50) {
if (is_array($value)) {
return '[' . count($value) . ' items]';
} elseif (is_object($value)) {
return get_class($value) . ' object';
} elseif (is_string($value)) {
if (strlen($value) > $maxLength) {
return substr($value, 0, $maxLength) . '...';
}
return $value;
} else {
return var_export($value, true);
}
}
/**
* セッションデータを比較
*/
public function compare($data1, $data2, $handler = 'php') {
$originalHandler = ini_get('session.serialize_handler');
// データ1をデコード
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
session_decode($data1);
$session1 = $_SESSION;
// データ2をデコード
$_SESSION = [];
session_decode($data2);
$session2 = $_SESSION;
ini_set('session.serialize_handler', $originalHandler);
$comparison = [
'only_in_data1' => array_diff_key($session1, $session2),
'only_in_data2' => array_diff_key($session2, $session1),
'common' => array_intersect_key($session1, $session2),
'differences' => []
];
foreach ($comparison['common'] as $key => $value) {
if ($session1[$key] !== $session2[$key]) {
$comparison['differences'][$key] = [
'data1' => $session1[$key],
'data2' => $session2[$key]
];
}
}
return $comparison;
}
/**
* セッションデータのヒストリーを追跡
*/
public function trackHistory() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION['_debug_history'])) {
$_SESSION['_debug_history'] = [];
}
$snapshot = [
'timestamp' => microtime(true),
'data' => session_encode(),
'keys' => array_keys($_SESSION),
'key_count' => count($_SESSION)
];
$_SESSION['_debug_history'][] = $snapshot;
// 最新10件のみ保持
if (count($_SESSION['_debug_history']) > 10) {
$_SESSION['_debug_history'] = array_slice($_SESSION['_debug_history'], -10);
}
return $snapshot;
}
/**
* ヒストリーを取得
*/
public function getHistory() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
return $_SESSION['_debug_history'] ?? [];
}
/**
* セッションデータの整合性をチェック
*/
public function checkIntegrity($sessionData, $handler = 'php') {
$issues = [];
// デコードテスト
$originalHandler = ini_get('session.serialize_handler');
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
$result = session_decode($sessionData);
if (!$result) {
$issues[] = [
'type' => 'decode_failure',
'severity' => 'critical',
'message' => 'Failed to decode session data'
];
}
// サイズチェック
$size = strlen($sessionData);
if ($size > 10240) { // 10KB
$issues[] = [
'type' => 'large_size',
'severity' => 'warning',
'message' => "Session data is large: {$size} bytes"
];
}
// キー数チェック
$keyCount = count($_SESSION);
if ($keyCount > 100) {
$issues[] = [
'type' => 'many_keys',
'severity' => 'warning',
'message' => "Many session keys: {$keyCount}"
];
}
// オブジェクトチェック
foreach ($_SESSION as $key => $value) {
if (is_object($value)) {
$issues[] = [
'type' => 'contains_object',
'severity' => 'info',
'message' => "Key '{$key}' contains object: " . get_class($value)
];
}
}
ini_set('session.serialize_handler', $originalHandler);
return [
'valid' => empty(array_filter($issues, function($i) {
return $i['severity'] === 'critical';
})),
'issues' => $issues,
'summary' => [
'size' => $size,
'key_count' => $keyCount,
'total_issues' => count($issues)
]
];
}
/**
* デバッグレポートを生成
*/
public function generateReport() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$sessionData = session_encode();
$report = [
'session_id' => session_id(),
'session_name' => session_name(),
'handler' => ini_get('session.serialize_handler'),
'save_path' => session_save_path(),
'visualization' => $this->visualize($sessionData),
'integrity' => $this->checkIntegrity($sessionData),
'history_count' => count($this->getHistory()),
'generated_at' => date('Y-m-d H:i:s')
];
return $report;
}
}
// 使用例
echo "=== セッションデバッグツール ===\n";
$debugger = new SessionDebugTool();
// セッションデータを作成
session_start();
$_SESSION = [
'user_id' => 999,
'username' => 'debuguser',
'data' => ['key1' => 'value1', 'key2' => 'value2'],
'timestamp' => time()
];
// 可視化
echo "セッションデータ可視化:\n";
$viz = $debugger->visualize();
echo " ハンドラー: {$viz['handler']}\n";
echo " 総キー数: {$viz['total_keys']}\n";
echo " 生データ: {$viz['raw_data']}\n";
echo "\nキー詳細:\n";
foreach ($viz['keys'] as $key => $info) {
echo " {$key}: {$info['type']} - {$info['value']} ({$info['size']} bytes)\n";
}
// ヒストリー追跡
echo "\nヒストリー追跡:\n";
$debugger->trackHistory();
$_SESSION['change1'] = 'test';
$debugger->trackHistory();
$_SESSION['change2'] = 'test2';
$debugger->trackHistory();
$history = $debugger->getHistory();
echo " ヒストリー数: " . count($history) . "\n";
// 整合性チェック
echo "\n整合性チェック:\n";
$integrity = $debugger->checkIntegrity(session_encode());
echo " 有効: " . ($integrity['valid'] ? 'Yes' : 'No') . "\n";
echo " 問題数: {$integrity['summary']['total_issues']}\n";
if (!empty($integrity['issues'])) {
foreach ($integrity['issues'] as $issue) {
echo " [{$issue['severity']}] {$issue['message']}\n";
}
}
// デバッグレポート
echo "\n=== デバッグレポート ===\n";
$report = $debugger->generateReport();
echo "生成日時: {$report['generated_at']}\n";
echo "セッションID: {$report['session_id']}\n";
echo "ハンドラー: {$report['handler']}\n";
echo "総キー数: {$report['visualization']['total_keys']}\n";
例5: セッションマージシステム
class SessionMergeSystem {
/**
* 複数のセッションデータをマージ
*/
public function merge($sessionDataArray, $handler = 'php', $strategy = 'last_wins') {
$originalHandler = ini_get('session.serialize_handler');
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
$allSessions = [];
// すべてのセッションをデコード
foreach ($sessionDataArray as $index => $data) {
session_decode($data);
$allSessions[$index] = $_SESSION;
$_SESSION = [];
}
// マージ戦略に応じて処理
$merged = $this->applyMergeStrategy($allSessions, $strategy);
$_SESSION = $merged;
ini_set('session.serialize_handler', $originalHandler);
return [
'strategy' => $strategy,
'input_count' => count($sessionDataArray),
'output_keys' => count($merged),
'merged_data' => $merged
];
}
/**
* マージ戦略を適用
*/
private function applyMergeStrategy($sessions, $strategy) {
switch ($strategy) {
case 'first_wins':
// 最初の値を優先
$result = [];
foreach ($sessions as $session) {
foreach ($session as $key => $value) {
if (!isset($result[$key])) {
$result[$key] = $value;
}
}
}
return $result;
case 'last_wins':
// 最後の値を優先(デフォルト)
$result = [];
foreach ($sessions as $session) {
$result = array_merge($result, $session);
}
return $result;
case 'array_merge':
// 配列は結合、それ以外は上書き
$result = [];
foreach ($sessions as $session) {
foreach ($session as $key => $value) {
if (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
$result[$key] = array_merge($result[$key], $value);
} else {
$result[$key] = $value;
}
}
}
return $result;
case 'keep_all':
// 衝突するキーは配列に
$result = [];
foreach ($sessions as $session) {
foreach ($session as $key => $value) {
if (!isset($result[$key])) {
$result[$key] = $value;
} else {
if (!is_array($result[$key]) || !isset($result[$key][0])) {
$result[$key] = [$result[$key]];
}
$result[$key][] = $value;
}
}
}
return $result;
default:
return [];
}
}
/**
* 優先順位付きマージ
*/
public function mergeWithPriority($sessionDataArray, $priorities, $handler = 'php') {
$originalHandler = ini_get('session.serialize_handler');
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
// 優先順位でソート
$sorted = [];
foreach ($sessionDataArray as $index => $data) {
$priority = $priorities[$index] ?? 0;
$sorted[$priority][] = ['index' => $index, 'data' => $data];
}
ksort($sorted);
// 優先順位の低い方から適用
$result = [];
foreach ($sorted as $priority => $items) {
foreach ($items as $item) {
session_decode($item['data']);
$result = array_merge($result, $_SESSION);
$_SESSION = [];
}
}
$_SESSION = $result;
ini_set('session.serialize_handler', $originalHandler);
return [
'merged_count' => count($sessionDataArray),
'output_keys' => count($result),
'merged_data' => $result
];
}
/**
* 条件付きマージ
*/
public function conditionalMerge($sessionDataArray, $condition, $handler = 'php') {
$originalHandler = ini_get('session.serialize_handler');
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
$result = [];
foreach ($sessionDataArray as $index => $data) {
session_decode($data);
foreach ($_SESSION as $key => $value) {
if (call_user_func($condition, $key, $value, $index)) {
$result[$key] = $value;
}
}
$_SESSION = [];
}
$_SESSION = $result;
ini_set('session.serialize_handler', $originalHandler);
return $result;
}
/**
* セッション差分をマージ
*/
public function mergeDiff($baseData, $diffData, $handler = 'php') {
$originalHandler = ini_get('session.serialize_handler');
session_start();
$_SESSION = [];
ini_set('session.serialize_handler', $handler);
// ベースをデコード
session_decode($baseData);
$base = $_SESSION;
// 差分をデコード
$_SESSION = [];
session_decode($diffData);
$diff = $_SESSION;
// マージ
$merged = array_merge($base, $diff);
$_SESSION = $merged;
ini_set('session.serialize_handler', $originalHandler);
return [
'base_keys' => count($base),
'diff_keys' => count($diff),
'merged_keys' => count($merged),
'added_keys' => array_diff_key($merged, $base)
];
}
}
// 使用例
echo "=== セッションマージシステム ===\n";
$merger = new SessionMergeSystem();
// セッションデータを作成
session_start();
// セッション1
$_SESSION = ['user_id' => 1, 'name' => 'Alice', 'role' => 'user'];
$session1 = session_encode();
// セッション2
$_SESSION = ['user_id' => 1, 'email' => 'alice@example.com', 'role' => 'admin'];
$session2 = session_encode();
// セッション3
$_SESSION = ['user_id' => 1, 'status' => 'active', 'last_login' => time()];
$session3 = session_encode();
// マージ(last_wins戦略)
echo "last_wins戦略でマージ:\n";
$result1 = $merger->merge([$session1, $session2, $session3], 'php', 'last_wins');
echo " 入力数: {$result1['input_count']}\n";
echo " 出力キー数: {$result1['output_keys']}\n";
echo " role: {$result1['merged_data']['role']}\n"; // 'admin'
// マージ(first_wins戦略)
echo "\nfirst_wins戦略でマージ:\n";
$result2 = $merger->merge([$session1, $session2, $session3], 'php', 'first_wins');
echo " role: {$result2['merged_data']['role']}\n"; // 'user'
// マージ(keep_all戦略)
echo "\nkeep_all戦略でマージ:\n";
$result3 = $merger->merge([$session1, $session2, $session3], 'php', 'keep_all');
echo " role: " . (is_array($result3['merged_data']['role']) ?
implode(', ', $result3['merged_data']['role']) : $result3['merged_data']['role']) . "\n";
// 優先順位付きマージ
echo "\n優先順位付きマージ:\n";
$priorities = [0 => 1, 1 => 3, 2 => 2]; // session2が最優先
$result4 = $merger->mergeWithPriority([$session1, $session2, $session3], $priorities);
echo " role: {$result4['merged_data']['role']}\n"; // session2の'admin'
// 条件付きマージ
echo "\n条件付きマージ('user'で始まるキーのみ):\n";
$result5 = $merger->conditionalMerge(
[$session1, $session2, $session3],
function($key, $value, $index) {
return strpos($key, 'user') === 0;
}
);
echo " マージされたキー: " . implode(', ', array_keys($result5)) . "\n";
例6: セキュアセッションデコーダー
class SecureSessionDecoder {
private $trustedSources = [];
private $maxSize = 10240; // 10KB
/**
* 信頼できるソースを登録
*/
public function addTrustedSource($identifier) {
$this->trustedSources[] = $identifier;
}
/**
* セキュアにデコード
*/
public function secureDecode($sessionData, $sourceIdentifier = null, $handler = 'php') {
// ソースの検証
if ($sourceIdentifier !== null && !in_array($sourceIdentifier, $this->trustedSources)) {
throw new Exception("Untrusted source: {$sourceIdentifier}");
}
// サイズチェック
if (strlen($sessionData) > $this->maxSize) {
throw new Exception("Session data too large: " . strlen($sessionData) . " bytes");
}
// フォーマット検証
if (!$this->validateFormat($sessionData, $handler)) {
throw new Exception("Invalid session data format");
}
// サンドボックス内でデコード
$decoded = $this->decodeInSandbox($sessionData, $handler);
// 内容検証
$this->validateContent($decoded);
// セッションに適用
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$_SESSION = $decoded;
return [
'success' => true,
'source' => $sourceIdentifier,
'keys' => array_keys($decoded),
'size' => strlen($sessionData)
];
}
/**
* フォーマットを検証
*/
private function validateFormat($sessionData, $handler) {
switch ($handler) {
case 'php':
// key|serialized_value の形式をチェック
return preg_match('/^[a-zA-Z0-9_]+\|/', $sessionData) === 1;
case 'php_serialize':
// シリアライズ形式をチェック
return strpos($sessionData, 'a:') === 0;
default:
return false;
}
}
/**
* サンドボックス内でデコード
*/
private function decodeInSandbox($sessionData, $handler) {
$originalHandler = ini_get('session.serialize_handler');
// 一時セッションでデコード
$tempSession = [];
if (session_status() === PHP_SESSION_ACTIVE) {
$tempSession = $_SESSION;
$_SESSION = [];
} else {
session_start();
}
ini_set('session.serialize_handler', $handler);
try {
$result = session_decode($sessionData);
if (!$result) {
throw new Exception("Failed to decode session data");
}
$decoded = $_SESSION;
$_SESSION = $tempSession;
ini_set('session.serialize_handler', $originalHandler);
return $decoded;
} catch (Exception $e) {
$_SESSION = $tempSession;
ini_set('session.serialize_handler', $originalHandler);
throw $e;
}
}
/**
* 内容を検証
*/
private function validateContent($decoded) {
// オブジェクトのチェック
foreach ($decoded as $key => $value) {
if (is_object($value)) {
throw new Exception("Session contains object: " . get_class($value));
}
if (is_array($value)) {
$this->validateContent($value);
}
}
// キー数の制限
if (count($decoded) > 100) {
throw new Exception("Too many session keys: " . count($decoded));
}
return true;
}
/**
* ホワイトリストベースのデコード
*/
public function decodeWithWhitelist($sessionData, $allowedKeys, $handler = 'php') {
$decoded = $this->decodeInSandbox($sessionData, $handler);
// ホワイトリストにないキーを削除
$filtered = [];
foreach ($decoded as $key => $value) {
if (in_array($key, $allowedKeys)) {
$filtered[$key] = $value;
}
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$_SESSION = $filtered;
return [
'total_keys' => count($decoded),
'filtered_keys' => count($filtered),
'removed_keys' => count($decoded) - count($filtered)
];
}
/**
* サニタイズしてデコード
*/
public function sanitizedDecode($sessionData, $handler = 'php') {
$decoded = $this->decodeInSandbox($sessionData, $handler);
// 危険な値をサニタイズ
$sanitized = $this->sanitizeValues($decoded);
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$_SESSION = $sanitized;
return [
'sanitized' => true,
'keys' => count($sanitized)
];
}
/**
* 値をサニタイズ
*/
private function sanitizeValues($data) {
$sanitized = [];
foreach ($data as $key => $value) {
if (is_string($value)) {
// HTMLエスケープ
$sanitized[$key] = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
} elseif (is_array($value)) {
$sanitized[$key] = $this->sanitizeValues($value);
} elseif (is_int($value) || is_float($value) || is_bool($value)) {
$sanitized[$key] = $value;
} else {
// その他の型は除外
continue;
}
}
return $sanitized;
}
}
// 使用例
echo "=== セキュアセッションデコーダー ===\n";
$decoder = new SecureSessionDecoder();
// 信頼できるソースを登録
$decoder->addTrustedSource('internal_backup');
$decoder->addTrustedSource('migration_tool');
// セッションデータを作成
session_start();
$_SESSION = [
'user_id' => 123,
'username' => 'secureuser',
'email' => 'user@example.com',
'role' => 'admin',
'untrusted' => '<script>alert("xss")</script>'
];
$sessionData = session_encode();
// セキュアデコード
echo "セキュアデコード:\n";
try {
$result = $decoder->secureDecode($sessionData, 'internal_backup');
echo " 成功\n";
echo " ソース: {$result['source']}\n";
echo " キー数: " . count($result['keys']) . "\n";
} catch (Exception $e) {
echo " エラー: " . $e->getMessage() . "\n";
}
// ホワイトリストベースデコード
echo "\nホワイトリストベースデコード:\n";
$allowedKeys = ['user_id', 'username', 'email'];
$result2 = $decoder->decodeWithWhitelist($sessionData, $allowedKeys);
echo " 総キー数: {$result2['total_keys']}\n";
echo " フィルター後: {$result2['filtered_keys']}\n";
echo " 削除されたキー: {$result2['removed_keys']}\n";
// サニタイズデコード
echo "\nサニタイズデコード:\n";
$result3 = $decoder->sanitizedDecode($sessionData);
echo " サニタイズ完了\n";
echo " キー数: {$result3['keys']}\n";
session_start();
echo " untrustedフィールド: {$_SESSION['untrusted']}\n";
// HTMLエスケープされている
セキュリティ上の注意
// 危険: 信頼できないデータのデコード
// オブジェクトインジェクション攻撃のリスク
// NG: ユーザー入力を直接デコード
$userInput = $_POST['session'];
session_decode($userInput); // 危険!
// OK: 信頼できるソースからのみデコード
// - 自分でエンコードしたデータ
// - 署名付きデータ
// - 暗号化されたデータ
// ベストプラクティス
// 1. 内部バックアップのみに使用
// 2. ホワイトリストで検証
// 3. サイズ制限を設ける
// 4. オブジェクトを含まないことを確認
まとめ
session_decode()関数の特徴をまとめると:
できること:
- エンコードされたセッションデータを
$_SESSIONに復元 - セッションデータのバックアップ・復元
- カスタムセッションストレージの実装
- セッションデータの移行
session_encode()との関係:
session_encode():$_SESSION→ 文字列session_decode(): 文字列 →$_SESSION- 対になる関数
重要な注意点:
- セキュリティリスク: 信頼できないデータには使用しない
session_start()後に使用$_SESSION配列を完全に上書き- シリアライズハンドラーの指定が重要
推奨される使用場面:
- セッションバックアップシステム
- セッションデータの移行
- カスタムセッションストレージ
- デバッグツール
- テスト環境でのセッション再現
ベストプラクティス:
// 1. 信頼できるソースのみ
$myBackup = session_encode();
session_decode($myBackup); // OK
// 2. サイズチェック
if (strlen($data) > 10240) {
throw new Exception("Too large");
}
// 3. フォーマット検証
if (!preg_match('/^[a-zA-Z0-9_]+\|/', $data)) {
throw new Exception("Invalid format");
}
// 4. サンドボックス内で検証
$temp = $_SESSION;
session_decode($data);
// 検証...
$_SESSION = $temp; // 問題があれば元に戻す
関連関数:
session_encode(): セッションをエンコードsession_start(): セッション開始serialize(): データをシリアライズunserialize(): データをアンシリアライズ
session_decode()は、セッションデータを文字列から復元できる強力な関数ですが、セキュリティリスクが高いため、信頼できるデータソースでのみ使用してください!
