序列化:將物件/變數轉換為可儲存、可傳輸的字串格式。
反序列化:從字串重建原始物件/變數結構。
PHP 使用 serialize() / unserialize(),格式為 PHP 專有字串表示 (非 JSON)。
<?php
class Person {
public $name;
public $age;
private $id; // 私有屬性也會序列化
public function __construct($name, $age, $id) {
$this->name = $name;
$this->age = $age;
$this->id = $id;
}
public function greet() {
return "Hi, I'm {$this->name}, age {$this->age}";
}
}
$person = new Person('Alice', 30, 1001);
// 序列化
$serialized = serialize($person);
echo "序列化結果:\n$serialized\n\n";
// 輸出類似:O:6:"Person":3:{s:4:"name";s:5:"Alice";s:3:"age";i:30;s:11:"Personid";i:1001;}
// 儲存到檔案
file_put_contents('person.dat', $serialized);
// 反序列化 (需包含類別定義)
require 'Person.php';
$data = file_get_contents('person.dat');
$restored = unserialize($data);
echo $restored->greet(); // "Hi, I'm Alice, age 30"
var_dump($restored instanceof Person); // true
?>
class User {
public $username;
public $email;
private $passwordHash; // 敏感資料,不應序列化
private $sessionToken; // 臨時資料,不應序列化
public $lastLogin;
public function __sleep() {
// 必須返回陣列:指定要序列化的屬性名稱
return ['username', 'email', 'lastLogin'];
// passwordHash、sessionToken 將被排除
}
}
class User {
// ... 屬性 ...
public function __wakeup() {
// 反序列化後自動呼叫
// 重新建立資料庫連線、重新計算衍生屬性、驗證完整性
$this->sessionToken = $this->generateNewToken();
$this->db = Database::getConnection();
}
}
class ModernUser {
public $username;
public $email;
private $passwordHash;
private $computedCache;
// 返回要序列化的資料陣列 (鍵值對)
public function __serialize(): array {
return [
'username' => $this->username,
'email' => $this->email,
// 不包含 passwordHash、computedCache
];
}
// 從資料陣列還原
public function __unserialize(array $data): void {
$this->username = $data['username'];
$this->email = $data['email'];
$this->passwordHash = ''; // 重新載入或標記需重設
$this->computedCache = null; // 重置快取
}
}
基本類型:
i:123; // 整數
d:3.14; // 浮點數
s:5:"hello"; // 字串 (長度:內容)
b:1; // 布林
N; // NULL
a:2:{...} // 陣列 (元素數:{鍵值對...})
O:8:"ClassName":3:{...} // 物件 (類名長度:"類名":屬性數:{屬性...})
物件屬性表示:
s:4:"name" // public 屬性
s:11:"ClassNameprop" // private 屬性 (類名+屬性名)
s:7:"\0*\0prop" // protected 屬性 (\0*\0 前綴)
// PHP 預設 session.save_handler = files 使用序列化
$_SESSION['user'] = $userObject; // 寫入時自動 serialize
// 讀取時自動 unserialize (需類別定義已載入)
// 自定義 Session 處理器 (Redis/Memcached)
class SessionHandler implements SessionHandlerInterface {
public function write($id, $data) {
// $data 已是序列化字串
$this->redis->setex("session:$id", 3600, $data);
}
public function read($id) {
return $this->redis->get("session:$id") ?: '';
}
}
$cacheKey = "user_profile_{$userId}";
$serialized = serialize($user);
$redis->setex($cacheKey, 1800, $serialized);
// 讀取
$data = $redis->get($cacheKey);
if ($data) {
$user = unserialize($data);
}
class SendEmailJob {
public $to;
public $subject;
public $template;
public $data;
public function handle() {
// 發送郵件邏輯
}
}
$job = new SendEmailJob();
$job->to = 'user@example.com';
// ...
$payload = serialize($job);
// 推送到 Redis list、RabbitMQ、Beanstalkd 等
$redis->lpush('queue:emails', $payload);
// Worker 端
$payload = $redis->rpop('queue:emails');
$job = unserialize($payload);
$job->handle();
// 遷移:ADD COLUMN settings TEXT
$user->settings = ['theme' => 'dark', 'notifications' => true, 'lang' => 'zh-TW'];
$stmt = $pdo->prepare("UPDATE users SET settings = ? WHERE id = ?");
$stmt->execute([serialize($user->settings), $user->id]);
// 讀取
$row = $pdo->query("SELECT settings FROM users WHERE id = 1")->fetch();
$user->settings = unserialize($row['settings']);
// 現代替代方案:JSON 欄位 + json_encode/decode
若攻擊者控制 unserialize() 的輸入,可構造惡意物件觸發 __wakeup()/__destruct()/__toString() 等魔術方法,導致 RCE、檔案操作、SQL 注入等。
| 層級 | 措施 |
|---|---|
| 根本 | 永遠不要 unserialize() 來自不可信來源的資料 (用戶輸入、Cookie、API 參數、資料庫若可被汙染) |
| 替代 | 使用 JSON (json_encode/decode) 進行資料交換、儲存、傳輸 |
| 必要時 | PHP 7.0+ unserialize($data, ['allowed_classes' => ['MyClass', 'OtherClass']]) 白名單類別 |
| 簽名驗證 | 序列化時附加 HMAC 簽名,反序列化前驗證完整性 |
| 類別強化 | 魔術方法中嚴格驗證狀態、避免危險操作、__wakeup 中拋出例外阻止 |
class SafeSerializer {
private $secretKey;
public function __construct($secretKey) {
$this->secretKey = $secretKey;
}
public function serialize($data): string {
$serialized = serialize($data);
$signature = hash_hmac('sha256', $serialized, $this->secretKey);
return $signature . '|' . $serialized;
}
public function unserialize(string $payload, array $allowedClasses = []) {
[$signature, $serialized] = explode('|', $payload, 2);
$expected = hash_hmac('sha256', $serialized, $this->secretKey);
if (!hash_equals($signature, $expected)) {
throw new Exception('序列化資料完整性驗證失敗');
}
return unserialize($serialized, ['allowed_classes' => $allowedClasses]);
}
}
| 方案 | 速度 | 體積 | 型別保真 | 跨語言 | 安全性 | 適用場景 |
|---|---|---|---|---|---|---|
| PHP serialize | 快 | 中 | ✅ 完整 (含物件、資源) | ❌ | ⚠️ 風險高 | Session、內部快取、同語言隊列 |
| JSON | 快 | 小 | ⚠️ 僅基本類型 | ✅ 標準 | ✅ 安全 | API、前端交互、配置、異構系統 |
| igbinary | 極快 | 小 | ✅ 完整 | ❌ | ⚠️ 風險高 | 高效能快取 (Redis/Memcached) |
| MessagePack | 極快 | 極小 | ✅ 豐富類型 | ✅ | ✅ | 微服務通訊、高效能序列化 |
| Protocol Buffers | 快 | 極小 | ✅ Schema 定義 | ✅ | ✅ | 大型系統、跨語言、版本演進 |
unserialize($data, ['allowed_classes' => [MyClass::class]])__sleep 返回白名單屬性,密碼、Token、連線資源不序列化__unserialize 處理舊格式相容PHP 序列化是強大的物件持久化工具,核心 API 為 serialize()/unserialize()。魔術方法 __sleep/__wakeup (舊) 與 __serialize/__unserialize (新) 提供精細控制。**核心鐵律:永遠不要反序列化不可信資料**。現代架構傾向 JSON/MessagePack/Protobuf 替代,僅在 Session、內部快取、同語言隊列等受信任場景使用原生序列化。