魔术方法详解
__invoke(),调用函数的方式调用一个对象时的回应方法
它的核心作用是:让你像调用"函数"一样去调用一个"对象"。当你试图在对象名后面加一对括号 () 时,PHP 就会自动跑去执行这个方法
它的核心作用是:让你像调用"函数"一样去调用一个"对象"。当你试图在对象名后面加一对括号 () 时,PHP 就会自动跑去执行这个方法
你可以根据需要定义任意数量的参数
public function __invoke(...$arguments)
{
// 方法体
}
class Greeter {
public function __invoke($name) {
echo "你好,{$name}!我是一个被当成函数调用的对象。";
}
}
$greet = new Greeter();
// 像调函数一样调对象
$greet("小明");
class Accumulator {
private $count = 0;
public function __invoke($n) {
$this->count += $n;
return $this->count;
}
}
$adder = new Accumulator();
echo $adder(10) . "<br>"; // 输出 10
echo $adder(5) . "<br>"; // 输出 15 (它记住了之前的10)

class UpdateUserAvatar {
public function __invoke($userId, $imagePath) {
echo "正在为用户 {$userId} 更新头像:{$imagePath}";
// 执行复杂的逻辑...
}
}
$action = new UpdateUserAvatar();
// 语义非常明确:执行这个动作
$action(1, 'avatar.jpg');
class Double {
public function __invoke($n) {
return $n * 2;
}
}
$numbers = [1, 2, 3];
// 以前只能传函数名或闭包,现在可以传对象
$result = array_map(new Double(), $numbers);
print_r($result); // [2, 4, 6]

一句话总结: __invoke() 给对象穿上了一件函数的"外衣",让它在保持对象强大功能的同时,拥有函数般的简洁调用方式
__set_state(),调用var_export()导出类时,此静态方法会被调用
__set_state() 与之前那些方法最大的不同点在于:它是一个静态方法(static)。
当你想用 var_export() 把一个对象转换成可以直接运行的 PHP 代码时,这个方法就是那个"编译器"
基本语法
public static function __set_state(array $properties): object
{
// 逻辑处理
return new self(...);
}
$properties:一个关联数组,包含了该对象所有的成员变量及其当前值。
返回值:必须返回一个对象实例
假设你有一个配置类,你想把它导出到文件中,以后直接通过 include 引入。
class Config {
public $theme;
public $api_key;
public function __construct($theme, $api_key) {
$this->theme = $theme;
$this->api_key = $api_key;
}
// 当执行 var_export($obj) 时,PHP 会生成调用此方法的代码
public static function __set_state($props) {
$obj = new Config($props['theme'], $props['api_key']);
return $obj;
}
}
$setup = new Config('dark', 'secret-123');
// 使用 var_export 导出
$code = var_export($setup, true);
echo "<pre>导出的代码如下:\n" . htmlspecialchars($code) . "</pre>";
__set_state() 就像是对象的"代码化说明书"。它告诉 PHP:"如果你想在代码里重新造一个我,请按照这组属性参数来调用这个静态方法。"
__clone(),当对象复制完成时调用
__clone() 的核心作用是:处理"深拷贝"问题
在 PHP 中,如果你直接用等号赋值 b = a,它们指向的是同一个对象(引用)。
如果你使用 clone 关键字:b = clone a,PHP 会创建一个新对象,并将原对象的属性值原封不动地复制过去。
麻烦在于: 如果原对象的属性里还包含着另一个对象,clone 只会复制那个对象的"引用地址"。结果就是:改了克隆件里的子对象,原件里的子对象也会跟着变。这就是"浅拷贝"。
__clone() 就是用来把这种"浅拷贝"变成"深拷贝"的。
public function __clone()
{
// 在这里对副本进行最后的"整型"
}
class Heart {
public $status = "跳动中";
}
class Person {
public $name;
public $heart;
public function __construct($name) {
$this->name = $name;
$this->heart = new Heart();
}
public function __clone() {
// 关键:如果不写这一行,克隆人的心脏和原主人的心脏是同一颗!
$this->heart = clone $this->heart;
}
}
$p1 = new Person("张三");
$p2 = clone $p1;
$p2->heart->status = "停止了";
echo "张三的心脏:{$p1->heart->status}<br>"; // 依然跳动
echo "副本的心脏:{$p2->heart->status}<br>"; // 已停止
class User {
public $id;
public $username;
public function __clone() {
// 克隆出来后,把 ID 设为 null,方便后面作为新记录插入数据库
$this->id = null;
$this->username .= " (副本)";
}
}
$user1 = new User();
$user1->id = 1;
$user1->username = "Admin";
$user2 = clone $user1;
echo $user2->username; // 输出:Admin (副本)
echo $user2->id; // 输出:空
总结:克隆的生命周期
调用 clone $obj:PHP 内存开辟新空间,复制所有标量属性(字符串、数字等)。
触发 __clone():在新对象上执行你的自定义逻辑。
完成:得到一个独立的副本
__autoload(),尝试加载未定义的类
__autoload() 曾经是 PHP 处理类自动加载的"老大哥",但由于它一次只能定义一个,扩展性很差,在 PHP 7.2 中已被废弃,在 PHP 8.0 中已正式被移除。
现在,我们统一使用更强大的 spl_autoload_register() 来代替它。不过,理解它的原理对你掌握 PHP 的加载机制非常有帮助
当你代码中写了 new User(),但当前文件并没有 include 这个 User.php 时,PHP 不会立刻报错,而是会垂死挣扎一下,调用这个自动加载函数,把类名传进去,问你:"喂,这个类你在哪藏着呢
过去的方式:__autoload()
这个函数必须定义在全局作用域下
// 这是老掉牙写法,PHP 8 中会直接报错!
function __autoload($class_name) {
$file = __DIR__ . '/' . $class_name . '.php';
if (file_exists($file)) {
require_once $file;
}
}
$user = new User(); // 如果 User 类没加载,就会自动去当前目录找 User.php
现代的方式:spl_autoload_register()
这是目前的行业标准。它的好处是:你可以注册多个自动加载函数(比如一个加载框架代码,一个加载你自己的代码),它们会排队执行
// 例子:根据类名自动去文件夹里找文件
spl_autoload_register(function ($class_name) {
// 假设你的类文件都在 classes 目录下
$path = __DIR__ . '/classes/' . $class_name . '.php';
if (file_exists($path)) {
require_once $path;
echo "成功加载了:{$path}<br>";
}
});
// 现在 new 一个没见过的类
$tools = new ToolBox();
如果你有一个大型项目,里面有 1000 个类文件:
以前:你必须在文件顶端写 1000 行 require,或者每次用哪个就手动 require 哪个。
现在:你只需要写一个"规则"(比如类名等于文件名),PHP 就会在需要时自动去硬盘上帮你捞文件。
现在大家几乎都不手写自动加载了,而是使用 Composer。Composer 遵循一个叫 PSR-4 的规范,它本质上也是通过 spl_autoload_register 实现的,能把命名空间映射到文件夹路径如果你看到老的教程里还在教你用 __autoload,直接无视它,转而学习 spl_autoload_register 即可
__debugInfo(),打印所需调试信息
__debugInfo() 的核心作用是:当你使用 var_dump() 或 print_r() 打印一个对象时,由你来决定哪些属性应该显示,哪些应该隐藏。
如果没有定义这个方法,var_dump 会把对象的所有属性(包括私有属性和巨大的资源对象)全吐出来,这会让调试界面变得非常混乱
public function __debugInfo(): array
{
// 返回你希望在调试时看到的键值对
}
返回值:必须返回一个关联数组(array)
在调试用户对象时,我们绝对不想在屏幕上看到数据库密码或 API Token
class User {
public $username = "阿强";
private $password = "p@ssword123"; // 敏感数据
private $db_link;
public function __construct() {
$this->db_link = "Database Resource #1"; // 复杂的资源
}
public function __debugInfo() {
// 只暴露安全的、有用的信息
return [
'user_name' => $this->username,
'password' => '******', // 掩码处理
'is_connected' => isset($this->db_link)
];
}
}
$user = new User();
var_dump($user);

class BigData {
private $cache = []; // 假设这里存了 10MB 的临时数据
public function __debugInfo() {
return [
'cache_size' => count($this->cache) . ' items',
'note' => '为了性能,调试时不显示详细缓存内容'
];
}
}
有些对象内部嵌套了非常深的对象树(比如框架里的容器或 ORM 结果集),直接 var_dump 会导致页面直接卡死或显示几万行代码
这两个方法虽然都用于展示,但场景完全不同:
|----------|---------------------|-------------------|
| 特性 | __toString() | __debugInfo() |
| 触发方式 | echo $obj 或 字符串拼接 | var_dump($obj) |
| 返回类型 | 字符串 (string) | 数组 (array) |
| 主要用途 | 业务逻辑展示(给用户看) | 开发调试输出(给程序员看) |
生命周期类:__construct (生), __destruct (卒)
属性控制类:__get, __set, __isset, __unset
方法拦截类:__call, __callStatic
数据加工类:__toString (转文本), __invoke (转函数), __clone (克隆)
持久化类:__sleep, __wakeup, __serialize, __unserialize
工具辅助类:__set_state (导出), __debugInfo (调试)
知道这些就差不多了,之后开始更新一些漏洞和技巧