PHP反序列化基础
概念
序列化:使用serialize()可以将对象转换为字符串的形式
O:4:”Test”:3:{s:4:”flag”;b:1;s:4:”name”;s:8:”xiaoming”;s:3:”age”;i:20;}
反序列化:使用unserialize()函数将上述的序列化的字符串转回对象
代码案例
<?php
class Test {
public $flag = "flag";
public $name = "lang";
public $age = 10;
}
$test1 = new Test();
$test1->flag = true;
$test1->name = "xiaoming";
$test1->age = 20;
echo serialize($test1);
?>
执行后获得序列化的数据
O:4:”Test”:3:{s:4:”flag”;b:1;s:4:”name”;s:8:”xiaoming”;s:3:”age”;i:20;}
- O:表示对象类型,Object
4:类名的长度(“Test” 是 4 个字符)"Test":类名3:对象属性数量flag、name、age- 每个属性由s(字符串String)、b(布尔BOOL)或i(整数int)开头,后跟属性名和值
- N:代表空值
- a:数组 a:大小:{键序列段;值序列段;重复}
访问控制
public:公有类型、全局,类的内部和外部都可以访问
protected:私有类 只有当前类的内部可以访问
private:受保护的类 只有当前类或者父类可以访问
常见的魔术方法
__construct(): //当对象new的时候会自动调用
__destruct()://当对象被销毁时会被自动调用
__sleep(): //serialize()执行时被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时会被自动调用
__toString(): //把类当作字符串使用时触发
call(): //调用某个方法,若方法存在,则调用;若不存在,则会去调用call函数。
__callStatic(): //在静态上下文中调用不可访问的方法时触发
get(): //读取对象属性时,若存在,则返回属性值;若不存在,则会调用get函数
set(): //设置对象的属性时,若属性存在,则赋值;若不存在,则调用set函数。
__isset(): //在不可访问的属性上调用isset()或empty()触发
__unset(): //在不可访问的属性上使用unset()时触发
__set_state(),调用var_export()导出类时,此静态方法会被调用
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
常见反序列化样式
常见开始
__wakeup()一定会被调用,因为wakeup()是在使用unserialize()的时候被调用,作为初始化操作
__destruct()一定会被调用,因为destruct()是用于销毁状态的时候会自动调用,作为销毁操作
__toString()当对象被反序列化后又当作字符串使用的时候
常见中间部分
__toString()当对象被反序列化后又当作字符串使用的时候
__get()读取不可访问或者不存在属性的时候会被调用
__set()当不可访问或者不存在的属性赋值的时候会被调用
__isset()对于不可访问或者不存在的属性嗲用isset()或者empty()的时候会被调用
常见结尾
__call()调用了不可访问或者不存在的方法的时候被调用
常见魔术方法案例(参考小迪学习)
construct()、destruct()案例
<?php
class Test{
public $name;
public $age;
public $string;
//__construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
// 给类的属性赋值
$this->name = $name;
$this->age = $age;
$this->string = $string;
echo "__construct 初始化\n";
}
// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
/*
* 当对象销毁时会调用此方法
* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
*/
function __destruct(){
echo "__destruct 类执行完毕\n";
}
}
$a = new Test('miren', '20', 'hello');
unset($a);
?>
当unset销毁$a的时候输出
construct 初始化 destruct 类执行完毕
代表在这个Test类里面construct()、destruct()都会执行一个进行初始化一个销毁后执行
当注释掉unset($a);后一样会输出
construct 初始化 destruct 类执行完毕
是因为无论如何都会注销
sleep()、wakeup()、__toString:案例:
<?php
class MyClass {
public $sex;
public $name;
public $age;
public function __construct($name, $age, $sex) {
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
echo "__construct被调用<br>";
}
public function __sleep() {
echo "__sleep()被执行,序列化时调用<br>";
return ['name', 'age', 'sex'];
}
public function __wakeup() {
echo "__wakeup()被执行,反序列化时调用<br>";
}
public function __toString() {
return "姓名:{$this->name},年龄:{$this->age},性别:{$this->sex}";
}
}
// 实例化对象
$a = new MyClass('miren', 20, '男');
// 序列化对象
$b = serialize($a);
echo "序列化后的字符串:" . $b . "<br><br>";
// 反序列化对象
$c = unserialize($b);
// 使用__toString方法直接echo对象
echo "反序列化后的对象信息:" . $c . "<br>";
?>
当serialize的时候自动执行了sleep() 方法、当serialize的时候自动执行了wakeup()方法
执行输出:
construct被调用
__sleep()被执行,序列化时调用
序列化后的字符串:O:7:"MyClass":3:{s:4:"name";s:5:"miren";s:3:"age";i:20;s:3:"sex";s:3:"男";}
__wakeup()被执行,反序列化时调用
反序列化后的对象信息:姓名:miren,年龄:20,性别:男
invoke()函数案例:
<?php
class Invokable {
public function __invoke($x, $y) {
echo "__invoke 被触发<br>";
return $x + $y;
}
}
$obj = new Invokable();
$result = $obj(3, 4); //此处触发 __invoke
echo "结果=$result\n";
对象当函数去调用的时候会触发invoke函数执行
执行输出:
__invoke 被触发
结果=7
toString()函数案例:
<?php
class StrObj {
public function __toString(): string {
echo "__toString 被触发<br>";
return "I am a string";
}
}
$o = new StrObj();
echo $o;// 触发 __toString
当StrObj类被当作字符串进行使用的时候会触发
执行输出:
__toString 被触发
I am a string
call()函数案例:
<?php
class Demo {
public function __call($name, $args) {
echo "__call 被触发:method=$name args=" . json_encode($args, JSON_UNESCAPED_UNICODE) . "\n";
return "from __call";
}
}
$d = new Demo();
echo $d->noSuchMethod(1, "a") . "\n"; // 触发 __call
此处使用$d去实例化Demo类,然后让$d去调用了一个不存在的方法noSuchMethod触发了call函数
执行输出:
__call 被触发:method=noSuchMethod args=[1,"a"] from __call
callStatic()函数案例:
<?php
class Demo {
public static function __callStatic($name, $args) {
echo "__callStatic 被触发:method=$name args=" . json_encode($args, JSON_UNESCAPED_UNICODE) . "\n";
return "from __callStatic";
}
}
echo Demo::noSuchStatic("x") . "\n"; // 触发 __callStatic
同样的当调用不可访问或者不存在的静态方法的时候会直接触发__callStatic执行
执行输出:
__callStatic 被触发:method=noSuchStatic args=["x"] from __callStatic
get()函数案例:
<?php
class User {
private $data = array(
"name" => "张三",
);
public function __get($key) {
echo "触发 __get:你正在读取属性 {$key}<br>";
return $this->data[$key];
}
}
$u = new User();
echo $u->name; //触发 __get
当读一个不存在或不可访问的属性时候会自动触发get()因为此处的name是private类型的
执行输出:
触发 __get:你正在读取属性 name
张三
set()函数案例:
<?php
class Demo {
private $data = [];
public $normal = "init";
public function __set($name, $value) {
echo "__set 被触发:$name = " . var_export($value, true) . "<br>";
$this->data[$name] = $value;
}
}
$d = new Demo();
$d->token = "abc";
$d->normal = "cba";
echo "正常属性 normal 的值:" . $d->normal . "<br>";
当给私有的token赋值之后自动调用了set()函数但是当给公开的normal赋值的时候就正常输出
执行输出:
__set 被触发:token = 'abc'
正常属性 normal 的值:cba
isset()函数案例:
<?php
class Demo {
private $data = ["token" => "abc"];
public function __isset($name): bool {
echo "__isset 被触发:name=$name";
return $this->data[$name];
}
}
$d = new Demo();
var_dump(isset($d->token)); // 触发 __isset
echo "<br>";
var_dump(empty($d->token)); // 也会触发
当对于不可访问的私有属性进行isset和empty的时候会触发isset函数
执行输出:
__isset 被触发:name=tokenbool(true)
__isset 被触发:name=tokenbool(true)
unset()函数案例:
<?php
class Demo {
private $data = ["token" => "abc"];
public function __unset($name) {
echo "__unset 被触发:name=$name\n";
unset($this->data[$name]);
}
}
$d = new Demo();
unset($d->token); // 触发 __unset
与上同理
执行输出:
__unset 被触发:name=token
set_state函数案例:
<?php
class Demo {
public $a;
public function __construct($a) {
$this->a = $a;
}
public static function __set_state($props) {
echo "__set_state 被触发<br>";
$obj = new self(isset($props['a']) ? $props['a'] : 0);
return $obj;
}
}
$obj = new Demo(7);
$code = var_export($obj, true);
echo "var_export 输出:<pre>{$code}</pre>";
//还原
$restored = eval('return ' . $code . ';'); // 触发 __set_state
echo "还原后的对象:<pre>";
var_dump($restored);
echo "</pre>";
当执行var_export该静态方法set_state()会被调用,此处的var_export()是把对象导出成一段可执行的 PHP 代码,然后用eval去执行了code会还原对象
执行输出:
var_export 输出:
Demo::__set_state(array(
'a' => 7,
))
__set_state 被触发
还原后的对象:
object(Demo)#2 (1) {
["a"]=>
int(7)
}
clone()函数案例:
<?php
class Demo {
public int $x = 1;
public function __clone() {
echo "__clone 被触发\n";
$this->x++;
}
}
$a = new Demo();
$b = clone $a; // 触发 __clone
echo "a->x={$a->x}, b->x={$b->x}\n";
此处的clone函数主要用于PHP代码的对象复制,当使用clone去复制的时候会自动调用clone()函数
执行输出:
__clone 被触发 a->x=1, b->x=2
__autoload()函数案例:
<?php
error_reporting(0);
function __autoload($class) {
echo "__autoload 被触发:class=$class\n";
}
new NotExistsClass(); // 触发 __autoload
当加载未定义的类的时候会自动调用
执行输出:
autoload 被触发:class=NotExistsClass
debugInfo()函数案例:
<?php
class Demo {
public $secret = "top_secret";
public function __debugInfo(): ?array {
echo "__debugInfo 被触发<br>";
return ["secret" => "abc", "note" => "cba"];
}
}
$d = new Demo();
var_dump($d); // 触发 __debugInfo
当使用var_dump函数的时候会自动调用debugInfo()魔术方法
执行输出:
__debugInfo 被触发
object(Demo)#1 (2) { ["secret"]=> string(3) "abc" ["note"]=> string(3) "cba" }