首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 序列化與反序列化完整指南:物件持久化、傳輸與魔術方法

     編輯:PHP 綜合知識

核心概念

序列化:將物件/變數轉換為可儲存、可傳輸的字串格式。
反序列化:從字串重建原始物件/變數結構。
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
?>

魔術方法:精確控制序列化行為

__sleep():指定要序列化的屬性

class User {
    public $username;
    public $email;
    private $passwordHash; // 敏感資料,不應序列化
    private $sessionToken; // 臨時資料,不應序列化
    public $lastLogin;
    
    public function __sleep() {
        // 必須返回陣列:指定要序列化的屬性名稱
        return ['username', 'email', 'lastLogin'];
        // passwordHash、sessionToken 將被排除
    }
}

__wakeup():反序列化後初始化

class User {
    // ... 屬性 ...
    
    public function __wakeup() {
        // 反序列化後自動呼叫
        // 重新建立資料庫連線、重新計算衍生屬性、驗證完整性
        $this->sessionToken = $this->generateNewToken();
        $this->db = Database::getConnection();
    }
}

__serialize() / __unserialize() (PHP 7.4+ 推薦)

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 前綴)

進階應用場景

1. Session 物件儲存 (PHP 內建使用序列化)

// 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") ?: '';
    }
}

2. 快取物件 (Redis/Memcached)

$cacheKey = "user_profile_{$userId}";
$serialized = serialize($user);
$redis->setex($cacheKey, 1800, $serialized);

// 讀取
$data = $redis->get($cacheKey);
if ($data) {
    $user = unserialize($data);
}

3. 隊列任務傳遞 (物件作為 Job Payload)

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();

4. 資料庫欄位儲存複雜結構

// 遷移: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

安全性風險與防護

⚠️ 反序列化漏洞 (Object Injection)

若攻擊者控制 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 定義✅✅大型系統、跨語言、版本演進

最佳實踐總結

  1. 預設選 JSON:除非需要保留物件類別、私有屬性、資源型別
  2. 內部信任環境:Session、Redis 快取、同應用隊列可用 serialize
  3. PHP 7.4+ 用 __serialize/__unserialize:語義清晰、型別提示、效能更好
  4. 嚴格白名單:unserialize($data, ['allowed_classes' => [MyClass::class]])
  5. 敏感資料排除:__sleep 返回白名單屬性,密碼、Token、連線資源不序列化
  6. 版本演進:類別結構更時,__unserialize 處理舊格式相容

總結

PHP 序列化是強大的物件持久化工具,核心 API 為 serialize()/unserialize()。魔術方法 __sleep/__wakeup (舊) 與 __serialize/__unserialize (新) 提供精細控制。**核心鐵律:永遠不要反序列化不可信資料**。現代架構傾向 JSON/MessagePack/Protobuf 替代,僅在 Session、內部快取、同語言隊列等受信任場景使用原生序列化。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved