内置注解
概述
PHP 自带了一些内置注解(Built-in Attributes),用于标记类的特殊行为。这些注解可以直接在代码中使用,无需额外声明。本章将介绍 PHP 标准库提供的所有内置注解,包括 Attribute、AllowDynamicProperties、Deprecated、Override、ReturnTypeWillChange 和 SensitiveParameter。
版本标注
不同内置注解在不同 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
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
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
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
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] 用于标记实现了 ArrayAccess、Iterator 等接口的方法,表示该方法的返回类型在 PHP 9.0 中将会变更。使用此注解可以抑制 PHP 8.1+ 中的弃用警告。
<?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
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]
}详细说明
各内置注解的目标限制
| 注解 | 可用目标 |
|---|---|
Attribute | CLASS(类声明) |
AllowDynamicProperties | CLASS |
Deprecated | CLASS, FUNCTION, METHOD, PROPERTY, CLASS_CONSTANT, ENUM_CASE |
Override | METHOD |
ReturnTypeWillChange | METHOD, FUNCTION |
SensitiveParameter | PARAMETER(函数参数) |
Deprecated 注解的参数
<?php
declare(strict_types=1);
#[\Deprecated(
message: '请使用 newApi() 替代',
since: '3.0.0'
)]
function oldApi(): void {}SensitiveParameter 的安全性
#[SensitiveParameter] 只影响错误追踪和异常消息,不影响正常代码中的变量值。它不是加密或访问控制的替代品。
实战示例
实战:使用 Override 确保接口一致性
<?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
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
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] 只是触发编译警告,不会阻止代码运行。如果需要阻止调用,应使用异常。
最佳实践
- 在所有覆盖方法上使用 Override:减少因方法签名不匹配导致的 Bug。
- 避免 AllowDynamicProperties:优先使用构造函数属性提升或明确的类属性定义。
- 标记敏感参数:在处理密码、API Key 等敏感数据时使用
#[SensitiveParameter]。 - 及时标记弃用 API:使用
#[Deprecated]标记不再推荐的 API。
内置注解详解
Attribute 注解类
Attribute 本身也是一个注解类,用于声明其他类可以作为注解使用。这是所有自定义注解的基础。
<?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
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
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
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
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] 是可选的,但强烈推荐在重写父类方法时使用。它能帮助编译器检测错误。