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

魔术方法详解

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

当你尝试给一个类中"不存在"或者"没有访问权限(private/protected)"的属性赋值时,这个方法会被自动调用

复制代码
public function __set(string $name, mixed $value)
{
    // 方法体
}
$name: 你尝试设置的属性名
$value: 你想要赋给它的值

<?php
class Robot {
    public function __set($name, $value) {
        echo "警告:你尝试给不存在的属性【{$name}】赋值为【{$value}】,操作已被拦截。<br>";
    }
}

$bot = new Robot();
$bot->powerLevel = 9000; // 类里没有这个属性,触发 __set

<?php
class User {
    private int $age = 0;

    public function __set($name, $value) {
        if ($name === 'age') {
            if ($value < 0 || $value > 150) {
                echo "错误:年龄不合法!<br>";
            } else {
                $this->age = $value;
                echo "年龄已成功设置为:{$this->age}<br>";
            }
        }
    }
}

$user = new User();
$user->age = 25;  // 成功
$user->age = -5;  // 触发验证,报错

注意奥,不是代码的问题,我没分行,这是两个值

复制代码
class DataStore {
    private array $storage = [];

    public function __set($name, $value) {
        $this->storage[$name] = $value;
    }

    public function __get($name) {
        return $this->storage[$name] ?? null;
    }
}

$data = new DataStore();
$data->title = "PHP进阶";  // 存入数组
$data->author = "Gemini"; // 存入数组

echo $data->title; // 配合 __get 取出

注意事项

复制代码
性能:魔术方法比直接访问 public 属性要稍微慢一点点(因为多了函数调用的开销)
但在现代开发中这点差异通常可以忽略。
严谨性:使用 __set 时,建议配合类型检查(如 is_int()),以免被塞入奇怪的数据。

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

在 PHP 中,如果你对一个 private(私有)、protected(受保护) 或 不存在 的属性使用 isset()

正常情况下会直接返回 false。但如果你定义了 __isset(),PHP 就会把判断权交给你

复制代码
$name: 正在被检查的属性名。
返回值:必须返回 true 或 false。

默认情况下,外部是看不见私有属性的。

复制代码
class User {
    private $email = "test@example.com";

    public function __isset($name) {
        echo "正在检查属性【{$name}】是否存在...<br>";
        return isset($this->$name);
    }
}

$user = new User();
var_dump(isset($user->email));

如果你把数据存在一个私有数组里,isset() 默认是找不到它们的。这时必须用 __isset 转发

复制代码
class Settings {
    private array $data = [
        'theme' => 'dark',
        'is_active' => true
    ];

    public function __isset($name) {
        // 检查这个 key 是否在我们的数组里
        return array_key_exists($name, $this->data);
    }
}

$s = new Settings();
if (isset($s->theme)) {
    echo "主题已设置!";
}

这是一个冷知识:当你对不可访问属性使用 empty() 时,PHP 内部会先调用 __isset()。如果 __isset() 返回 false,empty() 就会直接判定为 true

复制代码
public function __isset($name) {
    return isset($this->data[$name]);
}

// 如果你调用 empty($obj->name),PHP 会先跑上面的 __isset 

小提示

如果你发现自己在写 __isset(),通常意味着你已经写了 __get()。为了逻辑一致,建议成对出现。否则会出现"你能用 __get 读到数据,但用 isset 判断却说没有"这种自相矛盾的情况

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

它的核心作用是:当你尝试对类中"不存在"或"没有访问权限(private/protected)"的属性执行操作时,这个方法会被自动调用

复制代码
public function __unset(string $name)
{
    // 执行清理逻辑
}
$name: 你想要销毁(删除)的那个属性的名字

class Bag {
    public function __unset($name) {
        echo "你想丢掉【{$name}】?没门,我拦截了销毁请求!<br>";
    }
}

$myBag = new Bag();
unset($myBag->money); // 触发 __unset
复制代码
class UserSession {
    private array $data = [
        'id' => 101,
        'token' => 'ABC-123'
    ];

    // 让外部可以 unset 掉私有数组里的 key
    public function __unset($name) {
        if (array_key_exists($name, $this->data)) {
            unset($this->data[$name]);
            echo "已从内部数组删除属性:{$name}<br>";
        }
    }

    public function showData() {
        print_r($this->data);
    }
}

$session = new UserSession();
unset($session->token); // 触发 __unset
$session->showData();   // 结果里只剩下 id

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

复制代码
__sleep() 的核心作用是:在对象被"冷冻"成字符串之前,由你来决定哪些属性需要被保留,哪些应该被丢弃
返回值:必须返回一个数组,数组里的值是你想保留的属性名称
public function __sleep(): array
{
    // 清理逻辑(如关闭连接)
    return ['属性名1', '属性名2']; 
}

有些对象属性是无法被序列化 的,或者没必要序列化

  1. 数据库连接(Resource):连接是活的,变成字符串再变回来就断了。
  2. 临时缓冲区:没必要浪费空间存临时数据。
  3. 日志句柄:文件指针在重新读取时会失效。

假设你有一个 User 对象,里面包含密码明文和数据库连接,你显然不想把这些存进缓存

复制代码
class User {
    public $username;
    public $password; // 敏感信息,不想序列化
    private $db_connection; // 资源类型,无法序列化

    public function __construct($name, $pwd) {
        $this->username = $name;
        $this->password = $pwd;
        $this->db_connection = "PDO Connection Resource"; // 模拟连接
    }

    // 序列化前自动调用
    public function __sleep() {
        echo "正在准备序列化...已剔除敏感数据和连接。<br>";
        // 只返回你想保留的属性名
        return ['username']; 
    }
}

$user = new User("阿强", "123456");
$serializedData = serialize($user);

echo $serializedData;

__sleep() 与 __wakeup() 的配合

序列化是个"往返跑"。__sleep() 负责"打包",而 __wakeup() 负责"拆箱"后的恢复工作。

__sleep(): 关掉数据库连接,返回要存的名单。

__wakeup(): 重新打开数据库连接,恢复初始化状态

__wakeup(),执行unserialize()时,先会调用这个函数

它的核心作用是:在对象从字符串还原回来的那一刻,重新初始化那些在序列化时丢失的资源(如数据库连接、文件句柄等)

析构函数不接收任何参数,也没有强制要求返回值

复制代码
public function __wakeup()
{
    // 重新建立连接或初始化
}

这是最经典的用法。因为连接资源(Resource)不能被保存到字符串里,所以我们要在对象苏醒时手动重连

复制代码
class Database {
    public $host;
    private $link;

    public function __construct($host) {
        $this->host = $host;
        $this->connect();
    }

    private function connect() {
        $this->link = "连接到 {$this->host} 的资源"; // 模拟连接
        echo "连接成功!<br>";
    }

    // 序列化时只保存 host 地址
    public function __sleep() {
        return ['host'];
    }

    // 反序列化时,PHP 帮我们还原了 host,但 link 是空的
    // 于是我们利用 __wakeup 重新连接
    public function __wakeup() {
        $this->connect();
    }
}

// 1. 创建对象并序列化
$db = new Database("127.0.0.1");
$data = serialize($db);

echo "--- 此时对象变成了字符串,存入缓存 ---<br>";

// 2. 反序列化回对象
$newDb = unserialize($data); 
// 此时会自动触发 __wakeup(),屏幕上会再次打印"连接成功!"

__sleep 与 __wakeup 的"接力"过程

serialize() ->调用 __sleep():确认要带走的名单(清理无用数据)。

数据传输/存储:对象处于字符串状态(休眠)。

unserialize() ->调用 __wakeup():重新恢复装备(连接数据库、初始化配置)

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

它定义了当一个对象被当作字符串对待时(比如被 echo、print 或者在字符串连接符 . 中使用)该如何表现,如果没有定义这个方法,直接 echo 一个对象会抛出 Fatal error

要求:必须返回一个字符串(String),不能返回数组、对象或 null

复制代码
public function __toString(): string
{
    return "你想展示的内容";
}

假设你有一个"书籍"类,你想直接打印它时显示书名和作者

复制代码
class Book {
    public function __construct(
        public string $title,
        public string $author
    ) {}

    // 当 echo $book 时自动触发
    public function __toString() {
        return "《{$this->title}》------ 作者:{$this->author}";
    }
}

$myBook = new Book("三体", "刘慈欣");

// 就像打印普通字符串一样简单
echo $myBook;

在复杂的对象中,你可以用 __toString 返回一个 JSON 格式,方便在调试日志里查看对象状态。

复制代码
class ApiResult {
    public function __construct(
        private int $code,
        private string $msg,
        private array $data = []
    ) {}

    public function __toString() {
        return json_encode([
            'status_code' => $this->code,
            'message' => $this->msg,
            'payload' => $this->data
        ], JSON_UNESCAPED_UNICODE);
    }
}

$res = new ApiResult(200, "操作成功", ["id" => 1]);
echo "响应内容: " . $res;

如果你在写一个 UI 组件库,可以用对象直接输出 HTML 标签

复制代码
class HtmlButton {
    public function __toString() {
        return '<button type="submit" class="btn-primary">点击提交</button>';
    }
}

$btn = new HtmlButton();
?>

<div>
    <?= $btn ?>
</div>

在 PHP 8 之前,如果在 __toString 中抛出异常会导致程序崩溃。但在 PHP 8.0+ 之后,你可以放心地在里面抛出异常,你也可以通过 (string)$obj 显式地触发这个方法

相关推荐
mutian.wang2 小时前
seata 配置demo
学习
说实话起个名字真难啊2 小时前
Docker 入门之网络基础
网络·docker·php
echome8882 小时前
Python 装饰器详解:从入门到精通的 7 个实用案例
开发语言·python
D4c-lovetrain2 小时前
linux个人心得24 (mysql③,AI排版尝试)
android·adb
竹之却2 小时前
【Agent-阿程】openclaw v2026.4.9更新内容介绍
开发语言·php·openclaw·openclaw 更新
熊猫笔记2 小时前
PHP将Word文件转换为PDF文件的三种方式,以及中文乱码解决
php
米优2 小时前
qt+vlc实现解码h264/h265裸码流播放
开发语言·qt·vlc
CHU7290352 小时前
知识触手可及:在线教学课堂APP的沉浸式学习体验
前端·学习·小程序
xyq20242 小时前
W3C CSS 活动
开发语言