Skip to content

重载

概述

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/__setArrayAccess
访问方式$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 无法自动补全

小贴士

  • 优先使用声明属性而非动态属性
  • 对于动态数据存储,使用 ArrayAccessstdClass
  • __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();
    }
}

参考链接