PHP 物件序列化
1. 物件序列化
1.1 serialize() 和 unserialize()
所有php裡面的值都可以使用函式serialize()
來返回一個包含位元組流的字串來表示。unserialize()
函式能夠重新把字串變回php原來的值。 序列化一個物件將會儲存物件的所有變數,但是不會儲存物件的方法,只會儲存類的名字。
為了能夠unserialize()
一個物件,這個物件的類必須已經定義過。如果序列化類A的一個物件,將會返回一個跟類A相關,而且包含了物件所有變數值的字串。 如果要想在另外一個檔案中反序列化一個物件,這個物件的類必須在反序列化之前定義,可以通過包含一個定義該類的檔案或使用函式spl_autoload_register()
<?php // classa.inc: class A { public $one = 1; public function show_one() { echo $this->one; } } // page1.php: include("classa.inc"); $a = new A; $s = serialize($a); // 把變數$s儲存起來以便檔案page2.php能夠讀到 file_put_contents('store', $s); // page2.php: // 要正確反序列化,必須包含下面一個檔案 include("classa.inc"); $s = file_get_contents('store'); $a = unserialize($s); // 現在可以使用物件$a裡面的函式 show_one() $a->show_one(); ?>
可以在物件上使用 __sleep()
和 __wakeup()
方法對序列化/反序列化事件掛載鉤子。 使用 __sleep()
也能夠讓你僅序列化物件的某些屬性
2.魔術方法
2.1 __sleep()
和 __wakeup()
public __sleep(): array
public __wakeup(): void
serialize()
函式會檢查類中是否存在一個魔術方法 __sleep()
。如果存在,該方法會先被呼叫,然後才執行序列化操作。此功能可以用於清理物件,並返回一個包含物件中所有應被序列化的變數名稱的陣列。如果該方法未返回任何內容,則 null 被序列化,併產生一個 E_NOTICE 級別的錯誤。
注意:
__sleep() 不能返回父類的私有成員的名字。這樣做會產生一個 E_NOTICE 級別的錯誤。可以用 Serializable 介面來替代
__sleep()
方法常用於提交未提交的資料,或類似的清理操作。同時,如果有一些很大的物件,但不需要全部儲存,這個功能就很好用。
與之相反,unserialize()
會檢查是否存在一個 __wakeup()
方法。如果存在,則會先呼叫 __wakeup
方法,預先準備物件需要的資源。
__wakeup()
經常用在反序列化操作中,例如重新建立資料庫連線,或執行其它初始化操作。
示例 #1 Sleep 和 wakeup
<?php
class Connection
{
protected $link;
private $server, $username, $password, $db;
public function __construct($server, $username, $password, $db)
{
$this->server = $server;
$this->username = $username;
$this->password = $password;
$this->db = $db;
$this->connect();
}
private function connect()
{
$this->link = mysql_connect($this->server, $this->username, $this->password);
mysql_select_db($this->db, $this->link);
}
public function __sleep()
{
return array('server', 'username', 'password', 'db');
}
public function __wakeup()
{
$this->connect();
}
}
?>
2.2 __serialize()
和 __unserialize()
注意:
此特性自 PHP 7.4.0 起可用。
public __serialize(): array
public __unserialize(array $data): void
serialize()
函式會檢查類中是否存在一個魔術方法 __serialize()
。如果存在,該方法將在任何序列化之前優先執行。它必須以一個代表物件序列化形式的 鍵/值
成對的關聯陣列形式來返回,如果沒有返回陣列,將會丟擲一個 TypeError 錯誤
注意:
如果類中同時定義了 __serialize() 和 __sleep() 兩個魔術方法,則只有 __serialize() 方法會被呼叫。 __sleep() 方法會被忽略掉。<br>如果物件實現了 Serializable 介面,介面的 serialize() 方法會被忽略,做為代替類中的 __serialize() 方法會被呼叫。
如果類中同時定義了 __unserialize() 和 __wakeup() 兩個魔術方法,則只有 __unserialize() 方法會生效,__wakeup() 方法會被忽略。
示例 #2 序列化和反序列化
<?php
class Connection
{
protected $link;
private $dsn, $username, $password;
public function __construct($dsn, $username, $password)
{
$this->dsn = $dsn;
$this->username = $username;
$this->password = $password;
$this->connect();
}
private function connect()
{
$this->link = new PDO($this->dsn, $this->username, $this->password);
}
public function __serialize(): array
{
return [
'dsn' => $this->dsn,
'user' => $this->username,
'pass' => $this->password,
];
}
public function __unserialize(array $data): void
{
$this->dsn = $data['dsn'];
$this->username = $data['user'];
$this->password = $data['pass'];
$this->connect();
}
}?>
3. 序列化介面
3.1 簡介
自定義序列化的介面。
實現此介面的類將不再支援 __sleep()
和 __wakeup()
。不論何時,只要有例項需要被序列化,serialize 方法都將被呼叫。它將不會呼叫 __destruct()
或有其他影響,除非程式化地呼叫此方法。當資料被反序列化時,類將被感知並且呼叫合適的 unserialize() 方法而不是呼叫 __construct()
。如果需要執行標準的構造器,你應該在這個方法中進行處理。
3.2 介面摘要
class Serializable {
/* 方法 */
abstract public serialize(): string
abstract public unserialize(string $serialized): mixed
}
示例 #1 Basic usage
<?php
class obj implements Serializable {
private $data;
public function __construct() {
$this->data = "My private data";
}
public function serialize() {
return serialize($this->data);
}
public function unserialize($data) {
$this->data = unserialize($data);
}
public function getData() {
return $this->data;
}
}
$obj = new obj;
$ser = serialize($obj);
$newobj = unserialize($ser);
var_dump($newobj->getData());
?>
以上例程的輸出類似於:
string(15) "My private data"
示例 #2 Here's an example how to un-, serialize more than one property:
class Example implements \Serializable
{
protected $property1;
protected $property2;
protected $property3;
public function __construct($property1, $property2, $property3)
{
$this->property1 = $property1;
$this->property2 = $property2;
$this->property3 = $property3;
}
public function serialize()
{
return serialize([
$this->property1,
$this->property2,
$this->property3,
]);
}
public function unserialize($data)
{
list(
$this->property1,
$this->property2,
$this->property3
) = unserialize($data);
}
}
示例 #3 Serializing child and parent classes:
<?php
class MyClass implements Serializable {
private $data;
public function __construct($data) {
$this->data = $data;
}
public function getData() {
return $this->data;
}
public function serialize() {
echo "Serializing MyClass...\n";
return serialize($this->data);
}
public function unserialize($data) {
echo "Unserializing MyClass...\n";
$this->data = unserialize($data);
}
}
class MyChildClass extends MyClass {
private $id;
private $name;
public function __construct($id, $name, $data) {
parent::__construct($data);
$this->id = $id;
$this->name = $name;
}
public function serialize() {
echo "Serializing MyChildClass...\n";
return serialize(
array(
'id' => $this->id,
'name' => $this->name,
'parentData' => parent::serialize()
)
);
}
public function unserialize($data) {
echo "Unserializing MyChildClass...\n";
$data = unserialize($data);
$this->id = $data['id'];
$this->name = $data['name'];
parent::unserialize($data['parentData']);
}
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
}
$obj = new MyChildClass(15, 'My class name', 'My data');
$serial = serialize($obj);
$newObject = unserialize($serial);
echo $newObject->getId() . PHP_EOL;
echo $newObject->getName() . PHP_EOL;
echo $newObject->getData() . PHP_EOL;
?>
This will output:
Serializing MyChildClass...
Serializing MyClass...
Unserializing MyChildClass...
Unserializing MyClass...
15
My class name
My data