Skip to content

内置注解

概述

PHP 自带了一些内置注解(Built-in Attributes),用于标记类的特殊行为。这些注解可以直接在代码中使用,无需额外声明。本章将介绍 PHP 标准库提供的所有内置注解,包括 AttributeAllowDynamicPropertiesDeprecatedOverrideReturnTypeWillChangeSensitiveParameter

版本标注

不同内置注解在不同 PHP 版本中引入。使用时请注意版本兼容性。

基础概念

PHP 内置注解列表

注解PHP 版本用途
#[Attribute]8.0+标记类为注解类
#[AllowDynamicProperties]8.2+允许动态属性
#[Deprecated]8.4+标记为已弃用
#[Override]8.3+标记方法覆盖父类
#[ReturnTypeWillChange]8.1+兼容返回类型变更
#[SensitiveParameter]8.2+敏感参数(不显示在错误追踪中)

语法与代码

Attribute(PHP 8.0+)

Attribute 是用于声明注解类的内置标记。

php
<?php
declare(strict_types=1);

use Attribute;

#[Attribute(Attribute::TARGET_METHOD)]
class MyAnnotation
{
    public function __construct(public readonly string $value = '') {}
}

AllowDynamicProperties(PHP 8.2+)

PHP 8.2 默认禁止动态属性。#[AllowDynamicProperties] 允许类的实例动态添加属性。

php
<?php
declare(strict_types=1);

// PHP 8.2 中,动态属性默认被禁止
// $obj = new stdClass();
// $obj->name = 'Alice'; // Deprecated in 8.2, Error in 9.0

// 使用 AllowDynamicProperties 允许
#[\AllowDynamicProperties]
class ConfigBag {}

$bag = new ConfigBag();
$bag->appName = 'MyApp';    // 允许
$bag->version = '1.0';      // 允许

echo $bag->appName; // MyApp

弃用警告

在 PHP 8.2 中,不使用 #[AllowDynamicProperties] 而动态添加属性会触发弃用警告。在 PHP 9.0 中将成为错误。

Deprecated(PHP 8.4+)

#[Deprecated] 标记类、方法、函数等为已弃用。使用弃用的 API 会触发编译警告。

php
<?php
declare(strict_types=1);

#[\Deprecated(message: 'use newUser() instead', since: '2.0')]
function oldCreateUser(): array
{
    return [];
}

// 调用已弃用的函数
$result = oldCreateUser();
// Deprecated: Function oldCreateUser() is deprecated (since 2.0, use newUser() instead)

版本

#[Deprecated] 从 PHP 8.4 开始可用。PHP 8.3 及以下版本中,弃用信息只能通过 PHPDoc 的 @deprecated 标注。

Override(PHP 8.3+)

#[Override] 标记方法覆盖父类或实现接口的方法。如果父类/接口中没有对应的方法,会触发编译错误。

php
<?php
declare(strict_types=1);

abstract class BaseService
{
    abstract public function process(): void;

    public function initialize(): void
    {
        echo "BaseService: initialize\n";
    }
}

class UserService extends BaseService
{
    #[\Override]
    public function process(): void
    {
        echo "UserService: process\n";
    }

    #[\Override]
    public function initialize(): void
    {
        parent::initialize();
        echo "UserService: initialize\n";
    }

    // 如果父类没有此方法,编译器会报错
    // #[\Override]
    // public function cleanup(): void {}
    // Error: Method UserService::cleanup() has #[Override] attribute,
    // but no corresponding method in a parent class or implemented interface
}

ReturnTypeWillChange(PHP 8.1+)

#[ReturnTypeWillChange] 用于标记实现了 ArrayAccessIterator 等接口的方法,表示该方法的返回类型在 PHP 9.0 中将会变更。使用此注解可以抑制 PHP 8.1+ 中的弃用警告。

php
<?php
declare(strict_types=1);

class MyArrayAccess implements \ArrayAccess
{
    private array $data = [];

    public function offsetExists(mixed $offset): bool
    {
        return isset($this->data[$offset]);
    }

    public function offsetGet(mixed $offset): mixed
    {
        return $this->data[$offset] ?? null;
    }

    public function offsetSet(mixed $offset, mixed $value): void
    {
        $this->data[$offset] = $value;
    }

    #[\ReturnTypeWillChange]
    public function offsetUnset(mixed $offset): void
    {
        unset($this->data[$offset]);
    }
}

SensitiveParameter(PHP 8.2+)

#[SensitiveParameter] 标记函数/方法的参数为敏感信息,该参数的值不会出现在错误追踪(stack trace)和异常消息中。

php
<?php
declare(strict_types=1);

function connectDatabase(
    string $host,
    int $port,
    #[\SensitiveParameter] string $password
): void {
    throw new \RuntimeException('Connection failed');
}

try {
    connectDatabase('localhost', 3306, 'super_secret_password');
} catch (\RuntimeException $e) {
    echo $e->getTraceAsString();
    // password 参数值会被替换为 [sensitive]
}

详细说明

各内置注解的目标限制

注解可用目标
AttributeCLASS(类声明)
AllowDynamicPropertiesCLASS
DeprecatedCLASS, FUNCTION, METHOD, PROPERTY, CLASS_CONSTANT, ENUM_CASE
OverrideMETHOD
ReturnTypeWillChangeMETHOD, FUNCTION
SensitiveParameterPARAMETER(函数参数)

Deprecated 注解的参数

php
<?php
declare(strict_types=1);

#[\Deprecated(
    message: '请使用 newApi() 替代',
    since: '3.0.0'
)]
function oldApi(): void {}

SensitiveParameter 的安全性

#[SensitiveParameter] 只影响错误追踪和异常消息,不影响正常代码中的变量值。它不是加密或访问控制的替代品。

实战示例

实战:使用 Override 确保接口一致性

php
<?php
declare(strict_types=1);

interface RepositoryInterface
{
    public function find(int $id): ?array;
    public function save(array $data): int;
    public function delete(int $id): bool;
}

abstract class BaseRepository implements RepositoryInterface
{
    abstract protected function getTable(): string;

    public function find(int $id): ?array
    {
        // 通用实现
        return null;
    }
}

class UserRepository extends BaseRepository
{
    #[\Override]
    protected function getTable(): string
    {
        return 'users';
    }

    #[\Override]
    public function save(array $data): int
    {
        return 1;
    }

    #[\Override]
    public function delete(int $id): bool
    {
        return true;
    }
}

实战:使用 SensitiveParameter 保护密码

php
<?php
declare(strict_types=1);

class AuthService
{
    public function login(
        string $username,
        #[\SensitiveParameter] string $password
    ): bool {
        try {
            $this->validate($username, $password);
            return true;
        } catch (\Throwable $e) {
            // 密码不会出现在异常追踪中
            throw $e;
        }
    }

    private function validate(string $username, string $password): void
    {
        if ($password === '') {
            throw new \InvalidArgumentException('密码不能为空');
        }
    }
}

实战:标记弃用的 API

php
<?php
declare(strict_types=1);

#[\Deprecated(message: '使用 UserService::create() 替代', since: '2.5')]
class UserManager
{
    public function createUser(string $name): array
    {
        return ['id' => 1, 'name' => $name];
    }
}

#[\Deprecated(message: '使用 $user->isAdmin() 替代', since: '2.5')]
function isUserAdmin(array $user): bool
{
    return ($user['role'] ?? '') === 'admin';
}

注意事项

Override 不检查可见性

#[Override] 只检查父类/接口中是否存在同名方法,不检查方法的可见性或签名是否兼容。

ReturnTypeWillChange 仅用于特定接口

#[ReturnTypeWillChange] 只应用于 ArrayAccess::offsetUnset()Iterator 的某些方法等返回类型将要变更的接口方法。

Deprecated 不会阻止调用

#[Deprecated] 只是触发编译警告,不会阻止代码运行。如果需要阻止调用,应使用异常。

最佳实践

  1. 在所有覆盖方法上使用 Override:减少因方法签名不匹配导致的 Bug。
  2. 避免 AllowDynamicProperties:优先使用构造函数属性提升或明确的类属性定义。
  3. 标记敏感参数:在处理密码、API Key 等敏感数据时使用 #[SensitiveParameter]
  4. 及时标记弃用 API:使用 #[Deprecated] 标记不再推荐的 API。

内置注解详解

Attribute 注解类

Attribute 本身也是一个注解类,用于声明其他类可以作为注解使用。这是所有自定义注解的基础。

php
<?php
declare(strict_types=1);

use Attribute;

// 声明一个可以用于类和方法的注解
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class MyCustomAttribute
{
    public function __construct(public string $value = '') {}
}

AllowDynamicProperties(PHP 8.2+)

PHP 8.2 开始弃用动态属性。使用 #[AllowDynamicProperties] 可以允许特定类继续使用动态属性:

php
<?php
declare(strict_types=1);

#[\AllowDynamicProperties]
class FlexibleConfig
{
    public string $name = 'default';
}

$config = new FlexibleConfig();
$config->customField = 'value';  // PHP 8.2+ 不会发出弃用警告
echo $config->customField;      // 'value'

过渡方案

#[AllowDynamicProperties] 应该作为 PHP 8.2 迁移的过渡方案。长远来看,应该使用 stdClass、数组或专门的 DTO 类来替代动态属性。

Override(PHP 8.3+)

#[Override] 注解标记那些本意是重写父类方法的方法。如果父类中没有同名方法,PHP 会抛出错误:

php
<?php
declare(strict_types=1);

class ParentService
{
    public function process(array $data): void
    {
        echo "Parent processing\n";
    }

    public function validate(array $data): bool
    {
        return true;
    }
}

class ChildService extends ParentService
{
    #[\Override]
    public function process(array $data): void
    {
        echo "Child processing\n";
        parent::process($data);
    }

    // 如果拼写错误,PHP 8.3+ 会在编译时报错
    // #[\Override]
    // public function valdate(array $data): bool { ... }
    // Error: Method ChildService::valdate() has #[Override] attribute,
    // but no matching method found in parent class ParentService
}

为什么使用 Override

#[Override] 可以帮助捕获方法名拼写错误、父类方法被重命名等常见 bug,提升代码的可维护性。

ReturnTypeWillChange

标记方法返回类型可能在未来版本中变更,主要用于实现 PHP 内部接口时抑制弃用通知:

php
<?php
declare(strict_types=1);

class MyArrayObject extends \ArrayObject
{
    #[\ReturnTypeWillChange]
    public function offsetGet(mixed $key): mixed
    {
        return parent::offsetGet($key);
    }

    #[\ReturnTypeWillChange]
    public function offsetSet(mixed $key, mixed $value): void
    {
        parent::offsetSet($key, $value);
    }
}

SensitiveParameter(PHP 8.2+)

标记敏感参数,在堆栈追踪和错误日志中自动隐藏参数值:

php
<?php
declare(strict_types=1);

class DatabaseConnection
{
    public function connect(
        string $host,
        int $port,
        #[\SensitiveParameter]
        string $password,
    ): void {
        try {
            // 连接数据库
            throw new \RuntimeException('Connection failed');
        } catch (\RuntimeException $e) {
            // 堆栈追踪中 $password 的值会被替换为 [sensitive]
            throw $e;
        }
    }
}

$db = new DatabaseConnection();
try {
    $db->connect('localhost', 3306, 'super_secret_password');
} catch (\RuntimeException $e) {
    echo $e->getTraceAsString();
    // password 参数值显示为 [sensitive] 而非明文密码
}

安全第一

#[SensitiveParameter] 是保护敏感数据(密码、密钥、token 等)不被意外泄露到日志中的重要机制。所有涉及敏感数据的参数都应该标记此注解。

常见误区与 FAQ

Deprecated 注解在 PHP 8.4 中的变化?

PHP 8.4 引入了内置的 #[Deprecated] 注解,替代了在 doc-comment 中使用 @deprecated 的做法。它会在代码中使用被标记的类、方法或属性时触发弃用警告。

什么时候应该使用 SensitiveParameter?

任何涉及密码、API 密钥、access token、个人信息等敏感数据的函数/方法参数都应该标记 #[SensitiveParameter]。这不仅仅是最佳实践,更应该是安全标准的一部分。

Override 是强制的吗?

不是。#[Override] 是可选的,但强烈推荐在重写父类方法时使用。它能帮助编译器检测错误。

参考链接