关注这个专栏的其他相关笔记:[Web 安全] 反序列化漏洞 - 学习笔记-CSDN博客
PHP 魔术方法 - 简介 - PHP 魔术方法 - 简单教程,简单编程PHP 中,以两个下划线 ( `__` ) 开头方法称之为 「 魔术方法 」 这些 「 魔术方法 」 在 [PHP](/l/yufei/php/php-basic-index.html) 中扮演这重要的角色,作为一名 PHP 开发人员,你必须知道它们,且会用它们 本专栏,我们就来看看和学习这些魔术方法,以及一些简单的使用范例 ## PHP 魔术方法一览 |方法名|说明| |:---|:---| |\__construct()类的构造函数 |\__destruct()| 类 - 简单教程,简单编程 https://twle.cn/c/yufei/phpmmethod/phpmmethod-basic-index.html
0x01:PHP 魔术方法简介
在 PHP 中,以两个下划线(__
)开头的方法就被称为「 魔术方法 」。魔术方法是 PHP 中一个预定好的,在特定情况下会自动触发的行为方法。 这些魔术方法在 PHP 中扮演着重要的角色,作为一名 PHP 开发人员,我们必须要掌握并且能熟练使用它们。下面,开始本章的学习,以下是常见的 PHP 魔术方法,及其作用简介:
方法名 | 作用解析 |
---|---|
__construct() | 类的构造函数,创建对象时触发 |
__destruct() | 类的析构函数,对象被销毁时触发 |
__call() | 当调用对象的一个不存在或不可访问的方法时会自动调用 |
__callStatic() | 当调用对象或类的一个不存在或不可访问的静态方法时会自动调用 |
__get() | 调用不可访问、不存在的对象成员属性时触发 |
__set() | 在给不可访问、不存在的对象成员属性赋值时触发 |
__isset() | 当对不可访问属性调用 isset() 或 empty() 时触发 |
__unset() | 当使用 reset() 重制一个对象不存在的或不可访问的属性时会自动调用 |
__invoke() | 把对象当作函数调用时触发 |
__sleep() | 执行 serialize() 函数前会先调用此方法。 |
__wakeup() | 执行 unserialize() 函数前会先调用此方法。 |
__toString() | 当把对象当成字符串调用时会触发此方法 |
__clone() | 使用 clone 关键字拷贝完一个对象后触发 |
__set_state() | 当使用 var_export() 将数组导出为变量时会自动调用 |
__autoload() | 尝试自动加载一个未定义的类 |
__debugInfo() | 打印输出调试信息,针对 var_dump() 函数 |
0x02:PHP 魔术方法 — __construct()
0x0201:方法简介
PHP 构造函数 __construct()
是对象被创建后自动调用的第一个方法。
任何类都会有一个构造函数,当我们没有显示的声明它时,系统其实已经为它创建了一个隐藏的默认的构造函数,这个默认的构造函数没有任何参数,也不会执行任何代码,等价于一个空函数。
一旦我们在类中显式的声明了一个构造函数,那么默认的构造函数就会消失,也可以说是我们创建的构造函数会覆盖掉系统默认的构造函数。
0x0202:方法作用
构造函数通常用于执行一些初始化任务,例如在创建对象时设置成员变量的初始值。
0x0203:方法声明
在类中声明一个构造函数的语法格式一般如下:
class ClassName {
function __construct([parameter list]){
// 函数主体,这里面通常用于初始化对象的一些属性
}
}
注意:一个 PHP 类中只能有一个构造函数,因为 PHP 不允许进行函数重载!
0x0204:调用示例
下面的代码声明了一个 Dog
类,同时在该类中创建了一个构造函数,用于初始化对象的相应属性:
<?php
class Dog {
public $name; // 姓名
public $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
}
$dog = new Dog("旺财", "10"); // 实例化一只小狗
如上,可以看到,我们只是实例化了 Dog 类,并没有主动调用类中的方法,__construct()
方法就自己调用了。
0x03:PHP 魔术方法 — __destruct()
0x0301:方法简介
__destruct()
方法会在该类的一个对象被删除时自动调用。一般情况下,该函数的触发时机为:
-
主动调用
unset($obj)
。 -
主动调用
$obj = NULL
。 -
程序自动结束。
0x0302:方法作用
__destruct()
函数通常被用于对象执行完毕后进行释放资源的操作,比如关闭文件、关闭数据库链接、清空一个结果集等。
0x0303:方法声明
在类中声明 __destruct()
函数的语法格式如下,该函数没有任何参数也没有任何返回值:
class ClassName {
function __destruct() {
// 其他代码
}
}
0x0304:调用示例
在下面这个例子中,我们给 Dog
类添加上析构函数 __destruct()
,当对象走向消亡(生命周期结束)时,它会进行提示:
<?php
class Dog {
public $name; // 姓名
public $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
function __destruct() {
echo "=============== destruct ===============\n";
echo "快乐的时光总是短暂的,你的 🐕 " . $this -> name . "还是走向了它的终点\n";
echo "请不要伤心,它的故事只是已另一种形式展开。。。。。";
}
}
$dog = new Dog("旺财", "10"); // 实例化一只小狗
0x04:PHP 魔术方法 — __call()
0x0401:方法简介
__call()
方法只能被用于类中,当程序尝试调用类对象的一个 不存在 的或者 不可访问 的方法或属性时会被自动调用。
0x0402:方法声明
该方法有两个参数,第一个参数是调用的那个不存在的 方法名,第二个参数是一个数组(array),是传递给不存在方法的所有参数组成的数组:
class ClassName {
function __call( string $func_name, array $args) {
// 内部代码
}
}
0x0403:调用示例
如下,我们给 Dog
类创建了一个 __call
方法,用于在程序调用其中不存在的方法时进行自动调用:
<?php
class Dog {
public $name; // 姓名
public $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
function __call($func_name, $args) {
echo "================ Call Error ! ================\n";
echo "Sorry, " . $this -> name . "不会" .$func_name . "\n";
print_r($args);
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
$dog -> fly("高高", $hight="100 米"); // 让小狗飞高高,想飞 100 米那么高
0x05:PHP 魔术方法 — __callStatic()
0x0501:方法简介
__callStatic()
会在程序调用一个不存在的静态方法(该方法不存在或者不可访问)时被自动调用。
0x0502:方法声明
该方法接收两个参数,第一个参数是调用的那个不存在的静态方法名,第二个参数是一个数组(array),是传递给不存在的静态方法的所有参数组成的数组:
class ClassName {
static function __callStatic( string $func_name, array $args) {
// 内部代码
}
}
0x0503:调用示例
如下,我们给 Dog
类创建了一个 __callStatic
方法,用于在程序调用其中不存在的静态类时自动触发:
<?php
class Dog {
public $name; // 姓名
public $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
static function __callStatic($name, $arguments) {
echo "================ Call Error ! ================\n";
echo "静态方法:" . $name . "不存在!\n";
print_r($arguments);
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
// 下面就是调用静态方法的写法
$dog::fly("高高", $hight="100 米"); // 让小狗飞高高,想飞 100 米那么高
0x06:PHP 魔术方法 — __get()
0x0601:方法简介
当一个类定义了一个 __get()
魔术方法后,我们就可以获取该类的实例的私有属性或不存在的属性而不犯错,这里所说的获取,是指获取其值。
0x0602:方法声明
该方法的原型如下:
class ClassName {
public mixed function __get( string $propertyName ) {
// 内部代码
}
}
0x0603:调用示例
在下面的示例中,我们创建了一个 Dog
类,并为其添加了 __get()
魔法方法,当程序调用类中不存在的属性时,就会提示报错:
<?php
class Dog {
public $name; // 姓名
public $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
public function __get($propertyName) {
echo "================ Get Error ! ================\n";
echo "Sorry, The Dog Class Didn't have <" . $propertyName . "> attribute\n";
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
echo $dog -> type; // 想要知道 Dog 属于哪类
0x07:PHP 魔术方法 — __set()
0x0701:方法简介
魔术方法 __set()
可以用来给类的实例的不存在的属性或不可访问的属性赋值。
0x0702:方法声明
该方法有两个参数,第一个参数 $property
是不存在的或不可访问的实例属性,第二个参数 $value
是实际要赋的值。
该方法可以有返回值,也可以没有返回值,这取决于开发者的要求:
class ClassName {
public function __set( $propertyName, $value ) {
// 内部代码
}
}
0x0703:调用示例
在如下示例中,当我们为私有属性 age
赋值时就会触发类中的 __set
方法,做一个简单的判断,不让这个年龄过大或者过小:
<?php
class Dog {
public $name; // 姓名
private $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
public function __set($propertyName, $value) {
print_r("===================== Set =====================\n");
if ($propertyName == "age") {
if ($value < 0 or $value > 35) {
echo "Error! Your Dog Age IS Error !!!\n"; // 当设置的年龄超过了狗年龄的范围时触发
} else {
$this -> age = $value;
echo "Now, Your Dog Age is " . $this -> age . "\n";
}
}
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
$dog -> age = 100; // 想让狗的年龄变成 100 岁
$dog -> age = 18; // 想让狗的年龄回到 18 岁
0x08:PHP 魔术方法 — __isset()
0x0801:方法简介
在讨论 __isset()
魔术方法之前,笔者先简单介绍一下 isset()
方法,该方法主要用于判断一个变量或一个实例的一个属性是否被定义。
如果变量或实例的属性不存在,或被赋值为 NULL,就会返回 false,其它情况下一律返回 true,哪怕目标被赋值为 false
,0
,''
。
isset()
通常用于判断某个变量是否被设置,但它同时可以在外部实例中判断实例的某个属性值是否被设置,这通常有两个常见:
-
如果属性是公开(public)属性,那么可以直接使用
isset()
来判断该属性是否设置。 -
如果属性是一个私有(private)的属性,那么
isset()
就无法正常工作了。
针对上述的第二种情况,我们就需要用到 __isset()
方法了。
0x0802:方法作用
通过在类中定义 __isset()
魔术方法,我们就可以使用 isset()
来判断这个类的实例的某个私有属性是否被 “设置”(只要 __isset()
返回 true
,那么 isset()
方法就会返回 true
,反之亦然)。
0x0803:方法声明
该方法只接收一个参数,就是要进行判断的属性名,该方法的返回值为一个 Bool 类型:
class ClassName {
public bool function __isset( $propertyName ) {
// 内部代码
return [true or false];
}
}
0x0804:调用示例
在下面的代码中,类中的 age
属性为私有的,要想判断实例的 age
属性是否被设置,我们就要借助 __isset()
方法:
<?php
class Dog {
public $name; // 姓名
private $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
public function __isset($property) {
print_r("WUHU, {$property} is a private attribute, __isset function is auto runs!!!\n");
return isset($this -> $property);
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
var_dump(isset($dog -> age));
0x09:PHP 魔术方法 — __unset()
0x0901:方法简介
如果一个类中定义了魔术方法 __unset()
,那么我们就可以使用 unset()
函数来销毁类的私有属性,或在销毁一个不存在的属性时得到通知。
然而实际上到底有没有销毁那个属性,取决于 __unset()
的具体实现,假如我们定义了一个空的 __unset()
方法,emmmm,没人这么闲吧。
0x0902:方法声明
该方法的原型如下:
class ClassName {
public function __unset( $propertyName ) {
// 内部代码
}
}
0x0903:调用示例
在下面的示例中,我们在 Dog
类中定义了一个 __unset()
方法,并用它尝试销毁类中的一个私有属性,与一个不存在的属性:
<?php
class Dog {
public $name; // 姓名
private $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
public function __unset( $property ) {
if ($property != "age") {
echo "啊哦, 你销毁的东东不存在 !!!!\n";
} else {
echo "<$property> 已成功被销毁 !!!\n";
unset($this -> $property);
}
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
unset($dog -> type); // 尝试销毁不存在的 Type 属性
unset($dog -> age); // 尝试销毁类的私有属性 age
0x10:PHP 魔术方法 — __sleep()
0x1001:方法简介
当我们在 PHP 中调用 serialize()
函数尝试序列化一个实例时,会首先检查该实例中是否存在 __sleep()
方法,如果该方法存在,则自动调用,否则使用默认的序列化方式。
0x1003:方法声明
我们可以在 __sleep()
方法中定制类的实例的序列化输出结果,并剔除一些不需要被序列化的属性,比如那些保存了超大数据的属性。
该魔术方法没有任何参数,单必须要有返回值,返回值的类型是 Array 类的,它包含了想要序列化的该实例的属性名:
class ClassName {
public array function __sleep() {
// 内部代码
return array();
}
}
0x1004:调用示例
比如下面这个例子,我们创建了一个 Dog
类,当程序序列化该类对象时,我们剔除了 $age
属性,并对 $name
属性进行了编码操作:
<?php
class Dog {
public $name; // 姓名
private $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
function __sleep() {
print_r("============= Dog 类正在序列化 Ing =============");
$this -> name = base64_encode($this -> name);
$this -> type = "Dog"; // 临时创建一个属性
return array("name", "type"); // 返回的时候排除了 $age 属性
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
echo serialize($dog); // 对 dog 进行序列化
0x11:PHP 魔术方法 — __wakeup()
0x1101:方法简介
当我们在 PHP 中使用 unserialize()
反序列化一个对象时,如果类中存在 __wakeup()
方法,那么该方法就会被自动调用。
0x1102:方法声明
该魔术方法既没有参数,也没有返回值:
class ClassName {
public function __wakeup() {
// 内部代码
}
}
0x1103:调用示例
下面示例中,我们往 Dog
类中添加了反序列化方法,用来在反序列化时,对 $name
进行 Base64 解码:
<?php
class Dog {
public $name; // 姓名
private $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
function __sleep() {
print_r("============= Dog 类正在序列化 Ing =============");
$this -> name = base64_encode($this -> name);
$this -> type = "Dog"; // 临时创建一个属性
return array("name", "type"); // 返回的时候排除了 $age 属性
}
function __wakeup() {
print_r("============= Dog 类正在反序列化 Ing =============");
$this -> name = base64_decode($this -> name); // 对 Dog 名称进行 Base64 解码
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
$serialize_dog = serialize($dog); // 对 dog 进行序列化
echo $serialize_dog . "\n";
$new_dog = unserialize($serialize_dog);
echo "\nDog 的名称: " . $new_dog -> name;
0x12:PHP 魔术方法 — __toString()
0x1201:方法简介
当我们使用 echo
语句尝试输出一个对象时,就会自动检查一个对象有没有定义 __toString()
方法,如果定义了,就会输出 __toString()
方法的返回值,如果没有定义,那么就会直接抛出一个异常,表明该对象不能直接转换为字符串。
0x1202:方法声明
该方法没有任何参数,也不会传递任何参数,但该方法必须有一个返回值,且返回值必须为字符串类型:
class ClassName {
public string function __toString() {
// 内部代码
}
}
0x1203:调用示例
在下面例子中,我们为 Dog 类新增添了一个 __toString()
方法,并通过 echo
输出了该类:
<?php
class Dog {
public $name; // 姓名
private $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
public function __toString() {
return sprintf("Dog('%s', '%s')", $this -> name, $this -> age);
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
echo $dog;
0x13:PHP 魔术方法 — __invoke()
0x1301:方法简介
当我们尝试将一个对象当作一个方法来使用时就会自动调用它的 __invoke()
方法,如果目标对象中不包含该方法,就会直接报错。
0x1302:方法声明
该方法可以有返回值,也可以没有,对于返回值的类型,它也没有任何限制:
class ClassName {
public mixed function __invoke() {
// 内部代码
}
}
0x1303:调用示例
下面的代码,我们给 Dog 类加上了 __invoke()
魔术方法,然后我们就可以将它的实例当作普通方法来调用了:
<?php
class Dog {
public $name; // 姓名
private $age; // 年龄
function __construct($name, $age) {
echo "恭喜你,你成功创建了一只 🐕 !!!\n";
$this -> name = $name; // 初始化 Dog 的名称
echo "Dog Name : " . $this -> name . "\n";
$this -> age = $age; // 初始化 Dog 的年龄
echo "Dog Age : " . $this -> age . "\n";
}
function __invoke() {
echo "Hello, My Name is " . $this -> name . " I am " .$this ->age . "Years Old Now !!!";
}
}
$dog = new Dog("旺财", "1"); // 实例化一只小狗
$dog(); // 把 dog 对象当作方法调用