重载
概述
PHP 中的"重载"(Overloading)与其他语言中的方法重载不同。在 PHP 中,重载指的是动态创建属性和方法的能力,通过魔术方法 __get、__set、__call、__callStatic 等实现。这使得对象可以响应不存在的属性访问和方法调用。
PHP 8.2 变更
PHP 8.2 起,动态属性已被废弃。在未声明 __get/__set 方法的类中动态设置属性会产生 E_DEPRECATED 警告。PHP 9.0 将移除该特性。
基础概念
PHP 重载魔术方法
| 方法 | 触发场景 | PHP 版本 |
|---|---|---|
__get($name) | 访问不可访问属性 | PHP 5.0+ |
__set($name, $value) | 设置不可访问属性 | PHP 5.0+ |
__isset($name) | 检查不可访问属性 | PHP 5.1+ |
__unset($name) | 销毁不可访问属性 | PHP 5.1+ |
__call($name, $args) | 调用不可访问方法 | PHP 5.0+ |
__callStatic($name, $args) | 调用不可访问静态方法 | PHP 5.3+ |
语法与代码
属性重载 __get / __set
php
<?php
declare(strict_types=1);
class DynamicContainer
{
private array $data = [];
public function __get(string $name): mixed
{
return $this->data[$name] ?? null;
}
public function __set(string $name, mixed $value): void
{
$this->data[$name] = $value;
}
public function __isset(string $name): bool
{
return isset($this->data[$name]);
}
public function __unset(string $name): void
{
unset($this->data[$name]);
}
}
$obj = new DynamicContainer();
$obj->name = 'Alice'; // 触发 __set
echo $obj->name; // 触发 __get -> "Alice"
isset($obj->name); // 触发 __isset -> true
unset($obj->name); // 触发 __unset
echo $obj->name; // null方法重载 __call
php
<?php
declare(strict_types=1);
class ApiClient
{
public function __call(string $name, array $arguments): mixed
{
$endpoint = '/' . strtolower(preg_replace('/([A-Z])/', '_$1', $name));
$method = $arguments[0] ?? 'GET';
return $this->request($method, $endpoint, $arguments[1] ?? []);
}
public function __callStatic(string $name, array $arguments): mixed
{
return (new self())->$name(...$arguments);
}
private function request(string $method, string $endpoint, array $data): array
{
// 模拟 API 请求
return ['method' => $method, 'endpoint' => $endpoint, 'data' => $data];
}
}
$client = new ApiClient();
$result = $client->getUser(['id' => 1]);
// ['method' => 'GET', 'endpoint' => '/get_user', 'data' => ['id' => 1]]与 ArrayAccess 接口对比
php
<?php
declare(strict_types=1);
class Config implements \ArrayAccess
{
private array $values = [];
public function offsetExists(mixed $offset): bool
{
return isset($this->values[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->values[$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->values[$offset] = $value;
}
public function offsetUnset(mixed $offset): void
{
unset($this->values[$offset]);
}
}
$config = new Config();
$config['db.host'] = 'localhost'; // ArrayAccess 风格
$config['db.port'] = 3306;
echo $config['db.host']; // localhost| 特性 | __get/__set | ArrayAccess |
|---|---|---|
| 访问方式 | $obj->prop | $obj['key'] |
| 语法风格 | 对象属性 | 数组 |
| 适用场景 | 动态属性代理 | 集合/配置类 |
| foreach 支持 | 否(除非实现 Iterator) | 否 |
PHP 8.2 废弃动态属性
php
<?php
declare(strict_types=1);
class User
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
$user = new User('Alice');
$user->age = 30; // PHP 8.2: Deprecated: Creation of dynamic property User::$age解决方案——使用 #[AllowDynamicProperties] 属性(PHP 8.2+):
php
<?php
declare(strict_types=1);
#[\AllowDynamicProperties]
class User
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
$user = new User('Alice');
$user->age = 30; // 正常工作详细说明
__call 的典型应用
php
<?php
declare(strict_types=1);
class FluentBuilder
{
private array $config = [];
public function __call(string $method, array $args): static
{
if (str_starts_with($method, 'set')) {
$property = strtolower(substr($method, 3));
$this->config[$property] = $args[0] ?? null;
return $this;
}
throw new \BadMethodCallException("Method {$method} not found");
}
public function toArray(): array
{
return $this->config;
}
}
$config = (new FluentBuilder())
->setHost('localhost')
->setPort(3306)
->setDbName('myapp')
->toArray();
// ['host' => 'localhost', 'port' => 3306, 'db_name' => 'myapp']__callStatic 的应用
php
<?php
declare(strict_types=1);
class EntityFactory
{
private array $instances = [];
public static function __callStatic(string $name, array $arguments): object
{
$factory = new self();
return $factory->create($name, $arguments);
}
private function create(string $name, array $args): object
{
$class = 'App\\Models\\' . ucfirst($name);
if (!class_exists($class)) {
throw new \RuntimeException("Class {$class} not found");
}
return new $class(...$args);
}
}
// 静态方法调用
$user = EntityFactory::user('Alice', 'alice@example.com');实战示例
场景一:代理对象
php
<?php
declare(strict_types=1);
class PropertyProxy
{
private object $target;
private array $allowed;
public function __construct(object $target, array $allowed)
{
$this->target = $target;
$this->allowed = $allowed;
}
public function __get(string $name): mixed
{
if (!in_array($name, $this->allowed, true)) {
throw new \RuntimeException("Access denied: {$name}");
}
return $this->target->$name;
}
public function __set(string $name, mixed $value): void
{
if (!in_array($name, $this->allowed, true)) {
throw new \RuntimeException("Access denied: {$name}");
}
$this->target->$name = $value;
}
}场景二:模型属性访问器
php
<?php
declare(strict_types=1);
class Model
{
private array $attributes = [];
private array $casts = [];
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
public function __get(string $name): mixed
{
$value = $this->attributes[$name] ?? null;
if (isset($this->casts[$name])) {
return $this->castValue($value, $this->casts[$name]);
}
return $value;
}
private function castValue(mixed $value, string $type): mixed
{
return match ($type) {
'int' => (int) $value,
'float' => (float) $value,
'bool' => (bool) $value,
'json' => json_decode($value, true),
default => $value,
};
}
}注意事项
注意事项
- PHP 8.2+ 废弃了未声明
__get/__set的动态属性 - 使用
#[AllowDynamicProperties]可以在 PHP 8.2+ 保持动态属性兼容 __call和__callStatic只在方法不存在时触发- 重载方法会降低代码可读性,IDE 无法自动补全
小贴士
- 优先使用声明属性而非动态属性
- 对于动态数据存储,使用
ArrayAccess或stdClass __call适用于代理、构建器、API 客户端等场景
最佳实践
1. 避免滥用动态属性
php
<?php
declare(strict_types=1);
// 推荐 - 声明属性
class User
{
public function __construct(
public readonly string $name,
public readonly string $email
) {}
}
// 不推荐 - 动态属性
class BadUser
{
public string $name;
// $age, $email 等动态属性
}2. 使用 __call 实现有意义的代理
php
<?php
declare(strict_types=1);
// __call 适合做方法转发/代理
class LazyService
{
private ?object $service = null;
public function __call(string $method, array $args): mixed
{
$this->service ??= $this->createService();
return $this->service->$method(...$args);
}
private function createService(): object
{
return new SomeService();
}
}