
PHP魔术方法深入理解与实战魔术方法是PHP中特殊的方法以两个下划线开头在特定情况下自动调用。理解魔术方法可以写出更灵活的代码。今天详细说说各种魔术方法的使用场景。__construct和__destruct是最常用的魔术方法。对象创建时自动调用__construct销毁时调用__destruct。phpclass Connection{private ?PDO $pdo null;public function __construct(private string $dsn,private string $user,private string $pass) {echo 连接对象创建\n;}public function connect(): PDO{if ($this-pdo null) {$this-pdo new PDO($this-dsn, $this-user, $this-pass);}return $this-pdo;}public function __destruct(){$this-pdo null;echo 连接对象销毁\n;}}?属性重载的魔术方法。__get、__set、__isset、__unset用于拦截属性访问。phpclass DynamicProperties{private array $data [];private array $readonly [id, created_at];public function __get(string $name): mixed{echo 读取属性: $name\n;return $this-data[$name] ?? null;}public function __set(string $name, mixed $value): void{if (in_array($name, $this-readonly)) {throw new RuntimeException(只读属性: $name);}echo 设置属性: $name . json_encode($value) . \n;$this-data[$name] $value;}public function __isset(string $name): bool{echo 检查属性是否存在: $name\n;return isset($this-data[$name]);}public function __unset(string $name): void{echo 删除属性: $name\n;unset($this-data[$name]);}}$obj new DynamicProperties();$obj-name 张三;echo $obj-name . \n;echo isset($obj-name) ? 存在 : 不存在 . \n;unset($obj-name);?方法重载的__call和__callStatic。当调用不存在的方法时触发。phpclass QueryBuilder{private array $conditions [];private array $orders [];private ?int $limit null;public function __call(string $name, array $arguments): self{if (str_starts_with($name, where)) {$field lcfirst(substr($name, 5));$this-conditions[] [$field, $arguments[0]];return $this;}if (str_starts_with($name, orderBy)) {$field lcfirst(substr($name, 7));$this-orders[] [$field, $arguments[0] ?? asc];return $this;}throw new BadMethodCallException(方法不存在: $name);}public function limit(int $limit): self{$this-limit $limit;return $this;}public function getQuery(): string{$sql SELECT * FROM table;if (!empty($this-conditions)) {$parts array_map(fn($c) {$c[0]} :{$c[0]}, $this-conditions);$sql . WHERE . implode( AND , $parts);}if (!empty($this-orders)) {$parts array_map(fn($o) {$o[0]} {$o[1]}, $this-orders);$sql . ORDER BY . implode(, , $parts);}if ($this-limit ! null) {$sql . LIMIT {$this-limit};}return $sql;}}$query (new QueryBuilder())-whereName(张三)-whereAge(28)-orderById(desc)-orderByName()-limit(10);echo $query-getQuery() . \n;?__toString方法在对象被当作字符串使用时调用。phpclass Money{public function __construct(private float $amount,private string $currency CNY) {}public function __toString(): string{$symbols [CNY ¥, USD $, EUR €];$symbol $symbols[$this-currency] ?? $this-currency;return $symbol . number_format($this-amount, 2);}public function add(Money $other): Money{if ($this-currency ! $other-currency) {throw new InvalidArgumentException(货币不匹配);}return new Money($this-amount $other-amount, $this-currency);}}$price new Money(99.99);$tax new Money(13.00);$total $price-add($tax);echo 价格: $price\n;echo 税费: $tax\n;echo 总计: $total\n;?__invoke让对象可以作为函数调用。phpclass Logger{private string $logFile;public function __construct(string $logFile /tmp/app.log){$this-logFile $logFile;}public function __invoke(string $message, string $level INFO): void{$line sprintf([%s] %s: %s\n, date(Y-m-d H:i:s), $level, $message);file_put_contents($this-logFile, $line, FILE_APPEND | LOCK_EX);}}$log new Logger();$log(用户登录成功); // 对象作为函数调用$log(数据库连接失败, ERROR);echo 日志已写入\n;?__clone魔术方法在clone对象时调用控制深拷贝行为。phpclass Order{public function __construct(public string $id,public array $items,public \DateTime $createdAt) {}public function __clone(): void{// 深拷贝对象属性$this-createdAt clone $this-createdAt;// 深拷贝数组中的对象foreach ($this-items as $item) {if (is_object($item)) {$item clone $item;}}unset($item);}}$order1 new Order(ORD-001, [商品A, 商品B], new \DateTime());$order2 clone $order1;$order2-id ORD-002;echo 原始: {$order1-id}\n;echo 克隆: {$order2-id}\n;echo 时间不同: . ($order1-createdAt ! $order2-createdAt ? 是 : 否) . \n;?__serialize和__unserializePHP7.4控制序列化行为。phpclass SecureUser{public function __construct(public string $name,private string $password,private string $token) {}public function __serialize(): array{return [name $this-name,// 不序列化敏感信息];}public function __unserialize(array $data): void{$this-name $data[name];$this-password ;$this-token ;}}?魔术方法让PHP的对象系统更加灵活。属性重载可以实现动态属性方法重载可以实现DSL风格的接口序列化控制可以保护敏感数据。但魔术方法也有一些缺点比如IDE自动补全可能不准确增加理解难度。使用时要权衡灵活性和可维护性。