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"
}