首页 / PHP 入门教程 / PHP 魔术方法

PHP 入门教程

PHP 魔术方法

本教程共 65 篇 · 第 22 篇 · 更新于 2026-07-24 · 约 6 分钟阅读

PHPPHP8魔术方法__toString__get__call__construct

22. PHP 魔术方法

本节目标:掌握 PHP 的魔术方法,了解它们在特定场景下的自动调用机制。学完你能让类更智能,与 PHP 内置机制深度整合。

22.1 什么是魔术方法

魔术方法是以双下划线 __ 开头的方法。它们不需要显式调用,PHP 会在特定时机自动触发。

22.2 __toString()

对象被当作字符串使用时自动调用:

<?php
class User {
    public function __construct(
        public string $name,
        public int $age
    ) {}
    
    public function __toString(): string {
        return "User: {$this->name}, {$this->age}岁";
    }
}

$user = new User("张三", 25);
echo $user;  // User: 张三, 25岁
?>
Note

PHP 7.0 起,__toString() 可以声明返回类型 string。如果方法内抛出异常,PHP 会将其包装为 Error 抛出。

22.3 __get()__set()

访问或设置不存在(或不可访问)的属性时自动调用:

<?php
class Product {
    private array $data = [];
    
    public function __set(string $name, mixed $value): void {
        $this->data[$name] = $value;
    }
    
    public function __get(string $name): mixed {
        return $this->data[$name] ?? null;
    }
}

$product = new Product();
$product->name = "手机";      // 调用 __set
$product->price = 2999;      // 调用 __set
echo $product->name;         // 调用 __get
?>

22.4 __isset()__unset()

对不存在(或不可访问)的属性使用 isset()unset() 时自动调用:

<?php
class Config {
    private array $settings = [
        'debug' => true,
        'timezone' => 'Asia/Shanghai'
    ];
    
    public function __isset(string $name): bool {
        return isset($this->settings[$name]);
    }
    
    public function __unset(string $name): void {
        unset($this->settings[$name]);
    }
}

$config = new Config();
var_dump(isset($config->debug));   // true
unset($config->debug);
var_dump(isset($config->debug));   // false
?>

22.5 __call()__callStatic()

调用不存在(或不可访问)的方法时自动调用:

<?php
class MagicMethods {
    public function __call(string $name, array $arguments): mixed {
        return "调用了实例方法: $name,参数: " . implode(', ', $arguments);
    }
    
    public static function __callStatic(string $name, array $arguments): mixed {
        return "调用了静态方法: $name";
    }
}

$obj = new MagicMethods();
echo $obj->nonExistentMethod("a", "b");
// 调用了实例方法: nonExistentMethod,参数: a, b

echo MagicMethods::anotherMethod();
// 调用了静态方法: anotherMethod
?>
Tip

__call() 常用于实现方法委托或流畅接口(Fluent Interface)。

22.6 __clone()

使用 clone 关键字复制对象时自动调用:

<?php
class Document {
    public string $title;
    public DateTime $createdAt;
    
    public function __construct(string $title) {
        $this->title = $title;
        $this->createdAt = new DateTime();
    }
    
    public function __clone(): void {
        // 深拷贝:创建新的 DateTime 对象
        $this->createdAt = clone $this->createdAt;
        $this->title .= " (副本)";
    }
}

$doc1 = new Document("报告");
$doc2 = clone $doc1;

echo $doc2->title;  // 报告 (副本)
?>
Note

PHP 默认是浅拷贝。如果对象属性包含对象引用,__clone() 中需要手动深拷贝。

22.7 __sleep()__wakeup()

对象被序列化或反序列化时自动调用:

<?php
class Connection {
    public string $host;
    private $socket;  // 不可序列化的资源
    
    public function __construct(string $host) {
        $this->host = $host;
        $this->socket = fopen("php://memory", "r");
    }
    
    // 序列化前调用:返回需要序列化的属性名
    public function __sleep(): array {
        return ['host'];  // 只序列化 host,不序列化 socket
    }
    
    // 反序列化后调用:重建资源
    public function __wakeup(): void {
        $this->socket = fopen("php://memory", "r");
    }
}

$conn = new Connection("localhost");
$serialized = serialize($conn);
$restored = unserialize($serialized);
echo $restored->host;  // localhost
?>
Tip

PHP 7.4+ 推荐使用 __serialize()__unserialize() 替代 __sleep() / __wakeup(),它们更灵活,能直接返回序列化数据数组。

22.8 __invoke()

把对象当作函数调用时自动触发:

<?php
class Multiplier {
    public function __construct(private int $factor) {}
    
    public function __invoke(int $number): int {
        return $number * $this->factor;
    }
}

$double = new Multiplier(2);
echo $double(5);   // 10

$triple = new Multiplier(3);
echo $triple(5);   // 15
?>
Note

__invoke() 让对象可调用,常用于创建可调用的策略对象或闭包替代方案。

22.9 __debugInfo()

使用 var_dump() 输出对象时自动调用:

<?php
class BankAccount {
    public string $owner;
    private float $balance;
    private string $password;
    
    public function __construct(string $owner, float $balance) {
        $this->owner = $owner;
        $this->balance = $balance;
        $this->password = "secret123";
    }
    
    public function __debugInfo(): array {
        // 隐藏敏感信息
        return [
            'owner' => $this->owner,
            'balance' => $this->balance,
            'password' => '***隐藏***'
        ];
    }
}

$account = new BankAccount("张三", 10000);
var_dump($account);
// 不会暴露真实密码
?>

22.10 魔术方法速查表

魔术方法触发时机常用场景
__construct()new 创建对象初始化属性
__destruct()对象销毁资源清理
__toString()对象转字符串输出格式化
__get($name)读取不可访问属性动态属性
__set($name, $val)设置不可访问属性动态属性
__isset($name)对不可访问属性用 isset动态属性
__unset($name)对不可访问属性用 unset动态属性
__call($name, $args)调用不可访问方法方法委托
__callStatic($name, $args)静态调用不可访问方法静态委托
__clone()clone 对象深拷贝
__sleep()serialize()选择序列化字段
__wakeup()unserialize()重建资源
__invoke()对象当作函数调用可调用对象
__debugInfo()var_dump()控制调试输出

22.11 使用建议

<?php
// 1. 不要过度使用魔术方法,会降低代码可读性
// 2. 优先使用显式方法,魔术方法作为补充
// 3. 注意性能:魔术方法比直接访问稍慢
// 4. IDE 提示:魔术方法中的动态属性/方法无法被 IDE 自动补全

// 好的实践:在框架中封装魔术方法,业务代码保持显式
class ActiveRecord {
    protected array $attributes = [];
    
    public function __get(string $name): mixed {
        return $this->attributes[$name] ?? null;
    }
    
    // 同时提供显式方法
    public function getAttribute(string $name): mixed {
        return $this->attributes[$name] ?? null;
    }
}
?>

来源:参考了 w3cschool「PHP 面向对象」、runoob「PHP 面向对象」以及 php.net 官方文档,综合改写后所得。