前言

__construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset()__sleep()__wakeup()__serialize(), __unserialize(), __toString()__invoke()__set_state()__clone()__debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。

参考文献

魔术方法官方文档:https://www.php.net/manual/zh/language.oop5.magic.php

演示

__construct:构造方法

__destruct:析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Magic
{
// 构造函数
public function __construct() {
echo '对象被实例化 new 的时候调用此方法' . PHP_EOL;
}

// 析构函数
public function __destruct() {
echo '对象删除或者销毁时被调用' . PHP_EOL;
}
}
new Magic;

/*输出:
对象被实例化 new 的时候调用此方法 对
象删除或者销毁时被调用
*/

__call(),在对象中调用一个不可访问方法时调用

__callStatic(),用静态方式中调用一个不可访问方法时调用

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
<?php
class Magic {
public function __call($funName, $arguments) {
echo "你所调用的函数:" . $funName . "(参数:"; // 输出调用不存在的方法名
print_r($arguments); // 输出调用不存在的方法时的参数列表
echo ")不存在!\n" . PHP_EOL; // 结束换行
}

// 声明此方法用来处理调用对象中不存在的方法
public static function __callStatic($funName, $arguments) {
echo "你所调用的静态方法:" . $funName . "(参数:"; // 输出调用不存在的方法名
print_r($arguments); // 输出调用不存在的方法时的参数列表
echo ")不存在!\n" . PHP_EOL; // 结束换行
}
}
$magic = new Magic;
var_dump($magic->testCall('names'));
var_dump(Magic::testCall('names'));

/*
输出:
你所调用的函数:testCall(参数:Array ( [0] => names ) )不存在!
NULL

你所调用的静态方法:testCall(参数:Array ( [0] => names ) )不存在!
NULL
*/

__get(),获得一个类的成员私有的变量时调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Magic {
// 私有变量
private $value = 100;

// 读取不可访问属性的值时调用
public function __get($propertyName) {
return $this->$propertyName = 200;
}
}
$magic = new Magic;
echo $magic->value;

/*输出:
200
*/

__set(),设置一个类的成员私有的变量时调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Magic {
// 私有变量
private $value = 100;

// 设置一个类的成员变量时调用
public function __set($property, $value){
$this->$property = $value;
}

// 输出私有方法
public function run(){
return $this->value;
}
}
$magic = new Magic;
$magic->value = 666;
echo $magic->run();

/*输出:
666
*/

__isset(),当对不可访问属性调用isset()或empty()时调用

__unset(),当对不可访问属性调用unset()时被调用

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
<?php
class Magic {
public $name = '张三';
private $area = '北京';

// 当对不可访问属性调用isset()或empty()时调用
public function __isset($content) {
echo "当在类外部使用isset()函数测定私有成员{$content}时,自动调用<br>";
echo isset($this->$content) . "<br>";
}

// 当对不可访问属性调用时被调用
public function __unset($content) {
echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
echo isset($this->$content) . "<br>";
}
}
$magic = new Magic;
echo isset($magic->name), "<br>"; // 返回:1
echo isset($magic->area), "<br>";
/*输出:
1
当在类外部使用isset()函数测定私有成员sex时,自动调用
1
*/

unset($magic->name); // 返回:1
unset($magic->area);
/*输出:
当在类外部使用unset()函数来删除私有成员时自动调用的
1
*/

__sleep(),执行serialize()时,先会调用这个函数

__wakeup(),执行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
27
28
29
30
<?php
class Magic {
public $name = '张三';
private $area = '北京';

public function __sleep(){
echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
$this->name = base64_encode($this->name);
return array('name', 'age'); // 这里必须返回一个数值,里边的元素表示返回的属性名称
}

public function __wakeup(){
echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
$this->name = '李四';
$this->area = '上海';
}
}
$magic = new Magic;
var_dump(serialize($magic));
/*输出:
当在类外部使用serialize()时会调用这里的__sleep()方法
O:5:"Magic":2:{s:4:"name";s:8:"5byg5LiJ";s:3:"age";N;}
*/

var_dump(unserialize(serialize($magic)));
/*输出:
当在类外部使用serialize()时会调用这里的__sleep()方法
当在类外部使用unserialize()时会调用这里的__wakeup()方法
object(Magic)#2 (3) { ["name"]=> string(6) "李四" ["area":"Magic":private]=> string(6) "上海" ["age"]=> NULL }
*/

__toString(),类被当成字符串时的回应方法

1
2
3
4
5
6
7
8
9
10
11
<?php
class Magic{
public function __toString(){
return '我是类呀,不是字符串';
}
}
$magic = new Magic;
echo $magic;
/*输出:
我是类呀,不是字符串
*/

__invoke(),调用函数的方式调用一个对象时的回应方法

1
2
3
4
5
6
7
8
9
10
11
<?php
class Magic{
public function __invoke(){
echo '这可是一个对象哦';
}
}
$magic = new Magic;
$magic();
/*输出:
这可是一个对象哦
*/

__set_state(),调用var_export()导出类时,此静态方法会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Magic{
public $name;
public $age;

public function __construct($name = "", $age = 25){
$this->name = $name;
$this->age = $age;
}

public static function __set_state($an_array){
$a = new Magic();
$a->name = $an_array['name'];
return $a;
}
}
$person = new magic('小明'); // 初始赋值
$person->name = '小红';
var_export($person);
/*输出:
Magic::__set_state(array( 'name' => '小红', 'age' => 25, ))
*/

__clone(),当对象复制完成时调用

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
<?php
class Magic{
public $name = '张三';
public function __clone() {
echo __METHOD__ . "你正在克隆对象<br>";
}
}
$magic = new magic();
$magic2 = clone $magic;

$magic->name = '李四';
echo $magic->name . "<br>"; // 李四
echo $magic2->name . "<br>"; // 张三

$magic2->name = '王五';
echo $magic->name . "<br>"; // 李四
echo $magic2->name . "<br>"; // 王五

/*输出:
Magic::__clone你正在克隆对象
李四
张三
李四
王五
*/

__autoload(),尝试加载未定义的类

警告 本函数已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除。 强烈建议不要依赖本函数。spl_autoload_register — 注册给定的函数作为 __autoload 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
function __autoload($className)
{
$filePath = "project/class/{$className}.php";
die($filePath);
if (is_readable($filePath)) {
require($filePath);
}
}
$a = new TestClass();

/*返回:
Deprecated: __autoload() is deprecated, use spl_autoload_register() instead in F:\www\aaa.php on line 2
project/class/TestClass.php
*/

__debugInfo(),打印所需调试信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Magic{
private $prop;

public function __construct($val){
$this->prop = $val;
}

public function __debugInfo(){
return [
'propSquared' => $this->prop ** 2, // 这里的 `**` 是乘方的意思
];
}
}
var_dump(new Magic(666));

/*输出:
object(Magic)#1 (1) { ["propSquared"]=> int(443556) }
*/