php反序列化(复习)(第四章)

魔术方法详解

__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 (调试)

知道这些就差不多了,之后开始更新一些漏洞和技巧

相关推荐
XiaoLeisj2 小时前
Android 短视频项目首页开发实战:从广场页广告轮播与网格列表,到发现页分类、播单与话题广场的数据驱动实现
android·okhttp·mvvm·recyclerview·retrofit·databinding·xbanner 轮播
Jasmine_llq2 小时前
《B3923 [GESP202312 二级] 小杨做题》
开发语言·状态标记算法·顺序输入输出算法·递推迭代算法·循环遍历算法·条件终止算法·累加求和算法
此刻觐神2 小时前
IMX6ULL开发板学习-04(Linux磁盘管理相关命令)
linux·运维·学习
cch89182 小时前
Laravel vs ThinkPHP3.x:现代框架对决
php·laravel
Fairy要carry2 小时前
实习07-混合大模型的学习
学习
whatever who cares2 小时前
android中,全局管理数据/固定数据要不要放一起?
android·java·开发语言
华清远见IT开放实验室2 小时前
AI 算法核心知识清单(深度实战版1)
人工智能·python·深度学习·学习·算法·机器学习·ai
liu****2 小时前
第15届省赛蓝桥杯大赛C/C++大学B组
开发语言·数据结构·c++·算法·蓝桥杯·acm
_李小白2 小时前
【OSG学习笔记】Day 40: EventCallback(事件回调)
笔记·学习