魔术方法

魔术方法总结

魔术方法 触发时机
__construct() 类的构建函数
__destruct() 类的析构函数
__sleep() 执行 serialize() 时,先会调用这个函数
__wakeup() 执行 unserialize() 时,先会调用这个函数
__toString() 类被当成字符串调用时会触发
__invoke() 以调用函数的方式调用一个对象时会触发
__call() 在对象中调用一个不存在或不可访问方法时会触发
__callStatic() 用静态方式中调用一个不存在或不可访问方法会触发
__get() 调用的成员属性不存在时会触发
__set() 给不存在的成员属性赋值时会触发
__isset() 当对不存在或不可访问属性调用 isset()empty() 时会触发
__unset() 当对不存在或不可访问属性调用 unset() 时会触发
__clone() 当使用clone函数对象复制完成时会触发
__set_state() 调用var_export()导出类时会触发此静态方法
__autoload() 尝试加载未定义的类会触发
__debugInfo() 打印所需调试信息

__construct()

构造函数,在创建、实例化一个对象时,首先会去自动执行的一个魔术方法

  • **触发时机:**实例化对象
  • **功能:**提前清理不必要内容
  • 参数:非必要
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
// 创造一个类User
class User{
public $username;
public function __construct($username){
$this->username = $username;
echo "触发了构造函数一次";
}
}
// 实例化User类,此时会触发 __construct() 魔术方法
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);
?>
  • 在实例化User类时,会触发__construct()魔术方法
  • __construct()魔术方法会将传入的benben参数赋值给类的username属性
  • 并且会echo输出触发了构造函数一次

image-20250418233104863

__destruct()

析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法

  • **触发时机:**对象引用完成,或对象被销毁(实例化或反序列化之后)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
class User {
public function __destruct()
{
echo "触发了析构函数1次"."<br />" ;
}
}
// 实例化对象结束后,这个对象最终会被销毁,就会触发一次 __destruct() 魔术方法
$test = new User("benben");
$ser = serialize($test);
// 反序列化结束后也会触发一次 __destruct() 魔术方法
// 因为反序列化得到的是对象,用完之后会被销毁
unserialize($ser);
?>
  • 实例化对象结束后,这个对象最终会被销毁,就会触发一次 __destruct() 魔术方法,第一次执行echo语句输出触发了析构函数1次
  • 因为反序列化得到的是对象,用完之后会被销毁,所以反序列化结束后也会触发一次 __destruct() 魔术方法,第二次执行echo语句输出触发了析构函数1次

image-20250418233854270

析构函数例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
```php
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $cmd = "echo 'dazhuang666!!';" ;
public function __destruct()
{
eval ($this->cmd);
}
}
$ser = $_GET["benben"];
unserialize($ser);

?>

进行序列化

1
2
3
4
5
6
7
8
9
10
11
 <?php
class User {
var $cmd = "echo 'dazhuang666!!';" ;
public function __destruct()
{
eval ($this->cmd);
}
}
$a = new User();
echo serialize($a);
?>
1
O:4:"User":1:{s:3:"cmd";s:21:"echo 'dazhuang666!!';";}

最后会发现只会触发一次__destruct()魔术方法,是在unserialize($ser);这时触发的

image-20250418234510957

__sleep()

序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()

如果存在,该方法会被先调用,然后才执行序列化操作

此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组

如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误

  • **触发时机:**序列化serialize()之前
  • **功能:**对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性
  • 参数:成员属性
  • **返回值:**需要被序列化存储的成员属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) {
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
public function __sleep() {
return array('username', 'nickname');
}
}
$user = new User('a', 'b', 'c');
echo serialize($user);
?>
  • 在整个过程中,先实例化User,会首先触发__construct()魔术方法,会给$username, $nickname, $password分别赋值'a', 'b', 'c'
  • 然后在序列化serialize()对象时就会触发__sleep(),返回只含有usernamenickname的数组

如果没有sleep()魔术方法,序列化后的结果为

1
O:4:"User":3:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";s:14:"Userpassword";s:1:"c";}

这题中加入了sleep()魔术方法,序列化结果为

1
O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}

只有usernamenickname

image-20250419003412293

__wakeup()

反序列化unserialize()会检查是否存在一个__wakeup()方法

如果存在,则会先调用__wakeup()方法,预先准备对象所需的资源

预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作

  • **触发时机:**反序列化serialize()之前
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
$this->password = $this->username;
}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';
var_dump(unserialize($user_ser));
?>
  • 这里对已经构造好的一个序列化数据进行反序列化,在序列化数据中已经定义好了usernameanicknameb
  • 在反序列化之前,会触发__wakeup()魔术方法,使得password的值等于username的值

image-20250419120745329

要绕过这个魔术方法只需要让代表属性数量的值加一即可

__toString

表达方式错误导致魔术方法触发

调用对象可以使用print_rvar_dump

使用echoprint是调用字符串的方式

  • **触发时机:**把对象当作字符串调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __toString()
{
return '格式不对,输出不了!';
}
}
$test = new User() ;
print_r($test);
echo "<br />";
echo $test;
?>
  • 这里将User类实例化为一个对象test,然后使用了print_recho调用了这个对象
  • 可以使用print_r调用对象
  • 但不能使用echo,这会导致将对象以字符串的形式调用,从而触发__toString魔术方法

image-20250419121708648

__invoke()

格式表达错误导致魔术方法触发

  • **触发时机:**把对象当作函数调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是个函数!';
}
}
$test = new User() ;
echo $test ->benben;
echo "<br />";
echo $test() ->benben;
?>
  • echo $test ->benben;是正常的调用对象中的benben属性,输出benebn的值
  • echo $test() ->benben;是在以调用函数的方法调用test这个对象,会触发__invoke()魔术方法,会输出它不是个函数!

image-20250419122307346

__call()

  • **触发时机:**调用一个不存在的方法
  • **参数:**2个参数传参$arg1(不存在的方法名),$arg2(给不存在的方法传入的参数)
  • **返回值:**调用的不存在的方法的名称和参数
1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __call($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test -> callxxx('a');
?>
  • $test -> callxxx('a');这里调用了不存在的方法callxxx(),并传入参数a,会触发魔术方法__call(),传入的两个参数分别是不存在的方法名callxxx()和传入的参数a

image-20250419122845613

__callStatic()

  • **触发时机:**静态调用或调用成员常量时使用的方法不存在
  • **参数:**2个参数传参$arg1(不存在的方法名),$arg2(给不存在的方法传入的参数)
  • **返回值:**调用的不存在的方法名称和参数
1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a');
?>
  • $test::callxxx('a');这里使用了静态调用的方式调用test对象中的callxxx()方法,但这个方法不存在,所以会触发__callStatic()魔术方法,传入的两个参数分别是不存在的方法名callxxx和传入的参数a

image-20250419123453331

__get()

  • **触发时机:**调用的成员属性不存在
  • **参数:**传参$arg1(不存在的成员属性)
  • **返回值:**不存在的成员属性名称
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2;
?>
  • $test ->var2;这里调用了test对象中的var2这个成员属性,但是在对象中这个成员属性是不存在的,这就会触发__get()魔术方法,传入的参数$arg1是不存在的成员属性var2

image-20250419123856139

__set()

  • **触发时机:**给不存在的成员属性赋值
  • **参数:**传参$arg1(不存在的成员属性名),arg2(给不存在的成员属性赋的值)
  • **返回值:**不存在的成员属性的名称和赋的值
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __set($arg1 ,$arg2)
{
echo $arg1.','.$arg2;
}
}
$test = new User() ;
$test ->var2=1;
?>
  • $test ->var2=1;这里对test对象中的var2属性赋值为1,但是var2这个属性是不存在的,就会触发__set()魔术方法,传入的参数分别是不存在的属性名var2和赋的值1

image-20250419124319673

__isset()

  • **触发时机:**对不可访问或不存在的属性使用isset()empty()时会触发
  • **参数:**传参$arg1(不可访问或不存在的属性)
  • **返回值:**不可访问或不存在的成员属性的名称
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class User {
private $var;
public function __isset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
isset($test->var);
echo "<br>";
empty($test->var1);
?>
  • isset($test->var);这里使用isset函数调用了不可访问的私有属性var,就会触发__isset()魔术方法
  • empty($test->var1);这里使用了empty函数调用了不存在的属性var1,就会触发__isset()魔术方法

image-20250419125253469

__unset()

  • **触发时机:**对不可访问或不存在的属性使用unset()时会触发
  • **参数:**传参$arg1(不可访问或不存在的属性)
  • **返回值:**不存在的成员属性名
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
unset($test->var);
echo "<br>";
unset($test->var2);
?>
  • unset($test->var);这里使用unset函数调用不可访问的私有属性var,会触发__unset()魔术方法,传入的参数是不可访问的私有属性var
  • unset($test->var2);这里使用unset函数调用不存在的属性var2,会触发__unset()魔术方法,传入的参数是不存在的属性var2

image-20250419125819907

__clone()

  • **触发时机:**当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test)
?>
  • $test = new User() ;User类实例化成对象test,这个类中有__clone魔术方法
  • $newclass = clone($test)这里使用clone函数将对象test拷贝给新对象newclass,新对象就会触发拷贝过来的__clone魔术方法

image-20250419130238232


魔术方法
https://yschen20.github.io/2025/08/03/PHP序列化魔术方法/
作者
Suzen
发布于
2025年8月3日
更新于
2025年9月3日
许可协议