php 序列化以及 magic 函数

概述

有时候需要把一个对象在网络上传输,为了方便传输,可以把整个对象转化为二进制串,等到达另一端时,再还原为原来的对象,这个过程称之为也叫序列化。

有两种情况我们必须把对象也叫序列化,第一种情况就是把一个对象在网络中传输的时候要将对象也叫序列化,第二种情况就是把对象写入文件或是数据库的时候用到也叫序列化。

串行化有两个过程,一个是序列化,就是把对象转化为二进制的字符串,我们使用serialize()函数来序列化一个对象,另一个是反序列化,就是把对象转化的二进制字符串再转化为对象, 我们使用unserialize()函数来反序列化一个对象。

详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
error_reporting(7);
class Person
{
var $name;
var $age;
var $friends;
function __construct($name = "", $age = "",$friends = array()) {
$this->name = $name;
$this->age = $age;
$this->friends = $friends;
}
function say() {
echo "I am " . $this->name . ",my age is " . $this->age . "my friends has ";
foreach ($this->friends as $friend){
echo $friend." ";
}
echo ".<br>";
}
}
$p1 = new Person("Bob", 20,array("Alice","Jhon") );
$p1_string = serialize($p1); //把一个对象序列化,返一个字符串
echo $p1_string . "<br>"; //串行化的字符串我们通常不去解析
$p2 = unserialize($p1_string); //把一个序列化的字符串反序列化形成对象$p2
$p2->say();
?>

具体输出:

1
2
O:6:"Person":3:{s:4:"name";s:3:"Bob";s:3:"age";i:20;s:7:"friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}}
I am Bob,my age is 20my friends has Alice Jhon .

至于序列化语法解析,看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(7);
class Person
{
public $name;
private $age;
protected $friends;
var $job;
function __construct($name = "Bob", $age = 20,$friends =array("Alice","Jhon") ,$job=Null) {
$this->name = $name;
$this->age = $age;
$this->friends = $friends;
$this->job = $job;
}
}
$p1 = new Person();
$p1_string = serialize($p1); //把一个对象序列化,返一个字符串
echo $p1_string . "<br>"; //串行化的字符串我们通常不去解析
$p2 = unserialize($p1_string); //把一个序列化的字符串反序列化形成对象$p2
?>

具体输出:

1
O:6:"Person":4:{s:4:"name";s:3:"Bob";s:11:"Personage";i:20;s:10:"*friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}s:3:"job";N;}

需要序列化一个类的话,首先PHP会先将类名序列化。格式为 O:类名长度:”类名”:值:{} ,大致Fuzzy了下,这个类名长度前可以有%2b%30 这些符号也可以反序列化。其他的格式可以自己对照了解。如:

1
O:+6:"Person":4:{s:4:"name";s:3:"Bob";s:11:"Personage";i:20;s:10:"*friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}s:3:"job";N;}
1
O:06:"Person":4:{s:4:"name";s:3:"Bob";s:11:"Personage";i:20;s:10:"*friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}s:3:"job";N;}

以下是zval对应的类型和键对照表

1
2
3
4
5
6
7
8
9
10
11
数组中二次赋值(&): R;
对象二次赋值 : r;
NULL : N;
true : b:1;
false : b:0;
Long : i;
Double : d;
String : s/S;
Class : C;
Array : a;
Object : O;

可以看出变量不同的属性也有着不同的格式

1
2
3
public : key;
protected : *key;
private : 对象名key;

魔术方法

在PHP5中有两个魔术方法__sleep()方法和__wakeup()方法,在对象串行化的时候,会调用一个sleep()方法来完成一 些睡前的事情;而在重新醒来,即由二进制串重新组成一个对象的时候,则会自动调用PHP的另一个函数wakeup(),做一些对象醒来就要做的动作。sleep()函数不接受任何参数, 但返回一个数组,其中包含需要串行化的属性。末被包含的属性将在串行化时被忽略,如果没有sleep()方法,PHP将保存所有属性。当然,还有其他的魔术方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
error_reporting(7);
class Person
{
var $name;
var $age;
var $friends;
function __construct($name = "", $age = "",$friends = array()) {
$this->name = $name;
$this->age = $age;
$this->friends = $friends;
}
function say() {
echo "I am " . $this->name . ",my age is " . $this->age . "my friends has ";
foreach ($this->friends as $friend){
echo $friend." ";
}
echo ".<br>";
}
function __sleep() { // 此时,属性$age将被删除!!!
$arr = array("name", "friends");
return($arr);
}
function __wakeup() {//重新生成对象时,并重新赋值$age为40
$this->name = "Orleven";
}
}
$p1 = new Person("Bob", 20,array("Alice","Jhon") );
$p1_string = serialize($p1); //把一个对象序列化,返一个字符串
echo $p1_string . "<br>"; //串行化的字符串我们通常不去解析
$p2 = unserialize($p1_string); //把一个序列化的字符串反序列化形成对象$p2
$p2->say();
?>

具体输出:

1
2
O:6:"Person":2:{s:4:"name";s:3:"Bob";s:7:"friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}}
I am Orleven,my age is my friends has Alice Jhon .

不久之前刚出来的__wakeup()引发的漏洞。大意是指,当我们反序列化一个对象时,如果它的属性发生了变化,就会导致wakeup函数中不会执行,那么如果__wakeup()中存在一些重要的语句,就会导致不会被执行。

对于如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
error_reporting(7);
class Person
{
var $name;
var $age;
var $friends;
function __construct($name = "", $age = "",$friends = array()) {
$this->name = $name;
$this->age = $age;
$this->friends = $friends;
}
function say() {
echo "I am " . $this->name . ",my age is " . $this->age . ",my friends has ";
foreach ($this->friends as $friend){
echo $friend." ";
}
echo ".<br>";
}
function __wakeup() {//重新生成对象时,并重新赋值$age为40
echo "Wakeup...<br/>";
$this->name = "Orleven";
echo "Wakeuped<br/>";
}
function __destruct(){
//Do something
$this->say();
echo "Destructed<br/>";
}
}

如果执行如下代码:

1
2
3
$p1_string = 'O:6:"Person":3:{s:4:"name";s:3:"Bob";s:3:"age";i:20;s:7:"friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}}'; //把一个对象序列化,返一个字符串
echo $p1_string."<br/>";
$p2 = unserialize($p1_string); //把一个序列化的字符串反序列化形成对象$p2

具体输出:

1
2
3
4
5
O:6:"Person":3:{s:4:"name";s:3:"Bob";s:3:"age";i:20;s:7:"friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}}
Wakeup...
Wakeuped
I am Orleven,my age is 20,my friends has Alice Jhon .
Destructed

如果执行如下代码:

1
2
3
$p3_string = 'O:6:"Person":4:{s:4:"name";s:3:"Bob";s:3:"age";i:20;s:7:"friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}}'; //把一个对象序列化,返一个字符串
echo $p3_string."<br/>";
$p4 = unserialize($p3_string); //把一个序列化的字符串反序列化形成对象$p4

具体输出:

1
2
3
O:6:"Person":4:{s:4:"name";s:3:"Bob";s:3:"age";i:20;s:7:"friends";a:2:{i:0;s:5:"Alice";i:1;s:4:"Jhon";}}
I am Bob,my age is 20,my friends has Alice Jhon .
Destructed

发现wakeup并没有执行。