[學習]php物件序列化和反序列化
php 物件序列化和反序列化
所有php裡面的值都可以使用函式serialize()
來返回一個包含位元組流的字串來表示。unserialize()
函式能夠重新把字串變回php原來的值。
序列化後的格式
使用serialize()
函式序列化之後的結果是一個字串。對於不同資料型別的值,序列化之後的格式也不同。
例子:
// php 版本5.6.9 class JJ { public $jj = 'jj'; } class K extends JJ { public $a = 'aa'; public $c; static $b = 'bb'; private $d = 'w'; protected $f = 's'; function kkk() { echo "kkk"; } } $k = new K(); $arr = array(0 => 123); $str = "hello"; $num = 456; $bool = true; $float = 123.23; $null = null; $ref = &$str; $k->c = &$str; var_dump(serialize($k)); var_dump(serialize($arr)); var_dump(serialize($str)); var_dump(serialize($num)); var_dump(serialize($bool)); var_dump(serialize($float)); var_dump(serialize($null)); var_dump(serialize($ref));
上面例子的輸出結果:
// 序列化格式中字串要用雙引號包裹,()表示有則有,無則無,比如字串有長度,則必須有這一項。 // 物件序列化格式:"O:類名長度:"類名":屬性個數:{屬性名型別:屬性名長度:"屬性名";屬性值型別:(屬性值長度:)屬性值;}" string(104) "O:2:"Kk":5:{s:1:"a";i:123;s:1:"c";s:5:"hello";s:5:"\000Kk\000d";s:1:"w";s:4:"\000*\000f";s:1:"s";s:2:"jj";s:2:"jj";}" // 陣列序列化格式:"a:陣列長度:{鍵型別:(鍵長度:)鍵;值型別:(值長度:):值;}" string(16) "a:1:{i:0;i:123;}" // 字串序列化格式:"s:字元長度:"字串";" string(12) "s:5:"hello";" // 整型序列化格式:"i:數值;" string(6) "i:456;" // 布林型別序列化格式:"b:數值;" true--1, false--0 string(4) "b:1;" // 浮點型序列化格式:"d:浮點值;" string(9) "d:123.23;" // null序列化格式:"N;" string(2) "N;" // 對於引用賦值,使用引用的那個值的格式 string(12) "s:5:"hello";" // 在序列化物件時不會序列化物件的方法和靜態成員以及const宣告的常量,除前三者之外的成員都會序列化(包括private和protected)。 // 在序列化private和protected欄位時會在欄位名前加上對應的字首:private欄位名前加\000類名\000,protected欄位名前加\000*\000 // 序列化物件時,其父類上的public屬性和private屬性也會序列化,父類上的protected屬性不會序列化。
反序列化
使用unserialize()
函式對序列化後的值進行反序列化。
對於物件的反序列化,在反序列化之前,該物件的類必須已經定義過。
如果在反序列化物件的時候,沒有找到該物件的類的定義,那麼預設會把沒有方法的類__PHP_Incomplete_Class_Name
作為該物件的類,導致返回一個沒有用的物件。
如果要想在另外一個檔案中反序列化一個物件,這個物件的類必須在反序列化之前定義,可以通過包含一個定義該類的檔案或使用函式spl_autoload_register()
來實現。
序列化/反序列化鉤子
序列化和反序列化物件的時候可以通過對應的鉤子函式實現自定義序列化和自定義反序列化。
序列化和反序列化鉤子有三種,包括兩對兒魔術方法和一個介面:
__sleep()
和__wakeup()
(調serialize()
觸發__sleep()
,調unserialize()
觸發__wakeup()
)__serialize()
和__unserialize()
Serializable
// 介面
class Serializable {
/* 方法 */
abstract public serialize(): string
abstract public unserialize(string $serialized): mixed
}
三者中__serialize()
和 __unserialize()
的優先順序最高,Serializable
介面次之,__sleep()
和 __wakeup()
優先順序最低。優先順序高的會覆蓋優先順序低的,即優先順序高的執行,將不再執行優先順序低的。
__sleep()
和 __wakeup()
public __sleep(): array
__sleep()
方法需要返回需要序列化的物件的屬性的陣列,未包含在陣列中的屬性不會被序列化。
如果在子類的__sleep()
函式中返回的有與父類的私有屬性(private)同名的欄位,那麼序列化的時候,會產生一個 E_NOTICE 級別的錯誤,序列化結果是這個欄位會被當成子類的共有欄位處理,值會被序列化為null。如果要序列化父類的私有屬性可以使用 Serializable
介面來實現。
public __wakeup(): void
例子:
// php 版本5.6.9
class CC {
public $a = 'aa';
private $b = 'bb';
protected $c = 'cc';
}
class Connection extends CC
{
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;
}
public function __sleep()
{
echo "__sleep function call! \n";
return array('server', 'username', 'password', 'db', 'a', 'b', 'c');
}
public function __wakeup()
{
echo "__wakeup function call! \n";
}
}
$c = new Connection('192.168.112.34','root','123456','test');
$sili_c = serialize($c);
var_dump($sili_c);
var_dump(unserialize($sili_c));
上面例子的輸出結果為:
__sleep function call!
PHP Notice: serialize(): "b" returned as member variable from __sleep() but does not exist in...(錯誤資訊)
string(230) "O:10:"Connection":7:{s:18:"\000Connection\000server";s:14:"192.168.112.34";s:20:"\000Connection\000username";s:4:"root";s:20:"\000Connection\000password";s:6:"123456";s:14:"\000Connection\000db";s:4:"test";s:1:"a";s:2:"aa";s:1:"b";N;s:4:"\000*\000c";s:2:"cc";}"
__wakeup function call!
class Connection#2 (9) {
protected $link =>
NULL
private $server =>
string(14) "192.168.112.34"
private $username =>
string(4) "root"
private $password =>
string(6) "123456"
private $db =>
string(4) "test"
public $a =>
string(2) "aa"
private $b =>
string(2) "bb"
protected $c =>
string(2) "cc"
public $b =>
NULL
}
__serialize()
和 __unserialize()
此特性自 PHP 7.4.0 起可用。
public __serialize(): array
__serialize()
方法必須以一個代表物件序列化形式的 鍵/值 成對的關聯陣列形式來返回,如果沒有返回陣列,將會丟擲一個 TypeError 錯誤。
public __unserialize(array $data): void
例子:
// php版本 7.4.0
class Connection
{
protected $link;
private $dsn, $username, $password;
public function __construct($dsn, $username, $password)
{
$this->dsn = $dsn;
$this->username = $username;
$this->password = $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'];
}
}
$c = new Connection('192.168.112.34','root','123456');
$sili_c = serialize($c);
var_dump($sili_c);
var_dump(unserialize($sili_c));
上面程式碼輸出:
string(100) "O:10:"Connection":3:{s:3:"dsn";s:14:"192.168.112.34";s:4:"user";s:4:"root";s:4:"pass";s:6:"123456";}"
object(Connection)#2 (4) {
["link":protected]=>
NULL
["dsn":"Connection":private]=>
string(14) "192.168.112.34"
["username":"Connection":private]=>
string(4) "root"
["password":"Connection":private]=>
string(6) "123456"
}
Serializable
介面
序列化介面的例子:
// php版本5.6.9
class CC {
public $a = 'aa';
private $b = 'bb';
protected $c = 'cc';
}
class OBJ extends CC implements Serializable {
private $data;
public $a;
public function __construct() {
$this->data = "My private data";
$this->a = 'aa';
}
public function serialize() {
// 介面函式裡的邏輯可以隨便寫,這裡用serialize序列化,也可以自創規則序列化
return serialize(array(
'data' => $this->data,
'a' => $this->a,
'b' => $this->b
));
}
public function unserialize($data) {
$unres = unserialize($data);
$this->data = $unres['data'];
$this->a = $unres['a'];
}
}
$obj = new OBJ;
$ser = serialize($obj);
var_dump($ser);
$newobj = unserialize($ser);
var_dump($newobj);
上面例子的輸出結果是:
// 注意使用序列化介面序列化後的標識字母為 C
// 序列化了父類的私有屬性而沒有報錯
string(82) "C:3:"OBJ":67:{a:3:{s:4:"data";s:15:"My private data";s:1:"a";s:2:"aa";s:1:"b";N;}}"
class OBJ#2 (4) {
private $data =>
string(15) "My private data"
public $a =>
string(2) "aa"
private $b =>
string(2) "bb"
protected $c =>
string(2) "cc"
}