PHP 完全指南 / 第 13 章 — 属性 (Attributes)
第 13 章 — 属性 (Attributes):内置属性、自定义属性与 PHP 8 特性
13.1 什么是 Attributes
Attributes(属性注解)是 PHP 8.0 引入的元数据机制,替代了传统的注释式注解(如 Doctrine 的 @ORM\Entity),使用 #[...] 语法。
<?php
// 传统注解(旧方式)
/**
* @Route("/users", methods={"GET"})
* @IsGranted("ROLE_ADMIN")
*/
// Attributes(新方式)
#[Route('/users', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
class UserController {}
13.2 内置 Attributes
13.2.1 #[Deprecated] — PHP 8.4+
<?php
class OldService
{
#[\Deprecated(
message: 'Use NewService::process() instead',
since: '2.0.0',
)]
public function oldMethod(): void
{
// ...
}
}
$service = new OldService();
$service->oldMethod(); // PHP Deprecated: Method OldService::oldMethod() is deprecated since 2.0.0
13.2.2 #[Override] — PHP 8.3+
<?php
class ParentClass
{
public function doSomething(): void {}
}
class ChildClass extends ParentClass
{
#[\Override] // 如果父类没有此方法,编译时报错
public function doSomething(): void
{
parent::doSomething();
}
// #[\Override]
// public function typoMethd(): void {} // Error: ChildClass::typoMethd() does not override
}
13.2.3 #[Attribute] — 定义自定义 Attribute
<?php
// 标记一个类可以作为 Attribute 使用
#[\Attribute(\Attribute::TARGET_METHOD)] // 只能用于方法
class Cache
{
public function __construct(
public readonly int $ttl = 3600,
public readonly string $key = '',
) {}
}
13.2.4 #[AllowDynamicProperties]
<?php
#[\AllowDynamicProperties]
class LegacyClass
{
// 允许动态设置属性
// PHP 8.2+ 默认禁止此行为
}
$obj = new LegacyClass();
$obj->dynamicProp = 'value'; // 允许
13.2.5 其他内置属性
| 属性 | PHP 版本 | 说明 |
|---|---|---|
#[\Attribute] | 8.0 | 标记自定义 Attribute 类 |
#[\ReturnTypeWillChange] | 8.1 | 标记返回类型将在未来更改 |
#[\SensitiveParameter] | 8.2 | 在堆栈跟踪中隐藏敏感参数 |
#[\Override] | 8.3 | 确认方法重写了父类方法 |
#[\Deprecated] | 8.4 | 标记弃用的方法/函数 |
#[\AllowDynamicProperties] | 8.2 | 允许动态属性 |
13.3 自定义 Attributes
13.3.1 定义 Attribute 类
<?php
declare(strict_types=1);
namespace App\Attributes;
use Attribute;
// 限制 Attribute 可使用的位置
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION)]
class Route
{
public function __construct(
public readonly string $path,
public readonly array $methods = ['GET'],
public readonly string $name = '',
) {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Required
{
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class MaxLength
{
public function __construct(
public readonly int $length,
) {}
}
#[Attribute(Attribute::TARGET_CLASS)]
class Entity
{
public function __construct(
public readonly string $table = '',
) {}
}
// 多次使用(需要声明 Attribute::IS_REPEATABLE)
#[Attribute(Attribute::TARGET_METHOD | Attribute::Attribute::IS_REPEATABLE)]
class Middleware
{
public function __construct(
public readonly string $class,
) {}
}
13.3.2 Attribute 使用位置
| 常量 | 说明 |
|---|---|
TARGET_CLASS | 类 |
TARGET_METHOD | 方法 |
TARGET_FUNCTION | 函数 |
TARGET_PROPERTY | 属性 |
TARGET_CLASS_CONSTANT | 类常量 |
TARGET_PARAMETER | 参数 |
TARGET_ALL | 所有位置 |
IS_REPEATABLE | 可重复使用 |
13.3.3 使用自定义 Attribute
<?php
namespace App\Controllers;
use App\Attributes\Route;
use App\Attributes\Middleware;
class UserController
{
#[Route('/users', methods: ['GET'], name: 'user.index')]
#[Middleware(AuthMiddleware::class)]
#[Middleware(CorsMiddleware::class)]
public function index(): void
{
// ...
}
#[Route('/users/{id}', methods: ['GET'], name: 'user.show')]
public function show(int $id): void
{
// ...
}
#[Route('/users', methods: ['POST'], name: 'user.create')]
#[Middleware(AuthMiddleware::class)]
#[Middleware(ValidateMiddleware::class)]
public function create(): void
{
// ...
}
}
13.4 反射读取 Attributes
<?php
declare(strict_types=1);
class AttributeReader
{
/**
* 获取类上的指定 Attribute
*/
public static function getClassAttribute(
string $class,
string $attributeClass,
): ?object {
$reflection = new \ReflectionClass($class);
$attributes = $reflection->getAttributes($attributeClass);
return $attributes[0]?->newInstance();
}
/**
* 获取方法上的所有指定 Attributes
*/
public static function getMethodAttributes(
string $class,
string $method,
string $attributeClass,
): array {
$reflection = new \ReflectionMethod($class, $method);
return array_map(
fn(\ReflectionAttribute $attr) => $attr->newInstance(),
$reflection->getAttributes($attributeClass)
);
}
/**
* 获取属性上的 Attribute
*/
public static function getPropertyAttribute(
string $class,
string $property,
string $attributeClass,
): ?object {
$reflection = new \ReflectionProperty($class, $property);
$attributes = $reflection->getAttributes($attributeClass);
return $attributes[0]?->newInstance();
}
}
// 使用示例
$routes = [];
$reflection = new \ReflectionClass(UserController::class);
foreach ($reflection->getMethods() as $method) {
foreach ($method->getAttributes(Route::class) as $routeAttr) {
$route = $routeAttr->newInstance();
$routes[] = [
'path' => $route->path,
'methods' => $route->methods,
'name' => $route->name,
'handler' => [$reflection->getName(), $method->getName()],
];
}
}
print_r($routes);
13.5 属性验证系统
<?php
declare(strict_types=1);
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class Validate
{
public function __construct(
public readonly ?int $minLength = null,
public readonly ?int $maxLength = null,
public readonly ?string $pattern = null,
public readonly ?string $email = null,
public readonly ?int $min = null,
public readonly ?int $max = null,
) {}
}
// 使用验证属性
class UserDTO
{
#[Validate(minLength: 2, maxLength: 50)]
public string $name = '';
#[Validate(email: true)]
public string $email = '';
#[Validate(min: 0, max: 150)]
public int $age = 0;
#[Validate(pattern: '/^\+?[0-9]{10,15}$/')]
public string $phone = '';
}
// 验证器
class Validator
{
public static function validate(object $dto): array
{
$errors = [];
$reflection = new \ReflectionClass($dto);
foreach ($reflection->getProperties() as $property) {
$attrs = $property->getAttributes(Validate::class);
if (empty($attrs)) continue;
$validate = $attrs[0]->newInstance();
$value = $property->getValue($dto);
$name = $property->getName();
if ($validate->minLength !== null && mb_strlen($value) < $validate->minLength) {
$errors[$name][] = "{$name} 至少 {$validate->minLength} 个字符";
}
if ($validate->maxLength !== null && mb_strlen($value) > $validate->maxLength) {
$errors[$name][] = "{$name} 最多 {$validate->maxLength} 个字符";
}
if ($validate->email !== null && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$name][] = "{$name} 必须是有效的邮箱地址";
}
if ($validate->min !== null && $value < $validate->min) {
$errors[$name][] = "{$name} 不能小于 {$validate->min}";
}
if ($validate->max !== null && $value > $validate->max) {
$errors[$name][] = "{$name} 不能大于 {$validate->max}";
}
if ($validate->pattern !== null && !preg_match($validate->pattern, $value)) {
$errors[$name][] = "{$name} 格式不正确";
}
}
return $errors;
}
}
// 使用
$user = new UserDTO();
$user->name = 'A';
$user->email = 'invalid';
$user->age = 200;
$errors = Validator::validate($user);
print_r($errors);
// [name] => Array ([0] => name 至少 2 个字符)
// [email] => Array ([0] => email 必须是有效的邮箱地址)
// [age] => Array ([0] => age 不能大于 150)
13.6 常见框架 Attributes
Laravel
<?php
// 路由
#[Route('/api/users')]
class UserController extends Controller
{
#[Get('/')]
public function index() {}
#[Post('/')]
public function store(StoreUserRequest $request) {}
#[Get('/{user}')]
public function show(User $user) {}
}
// 事件监听
class UserRegistered
{
#[Handle('send-welcome-email')]
public function sendWelcomeEmail(User $event): void {}
}
Symfony
<?php
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/api/users')]
class UserController
{
#[Route('', name: 'user_index', methods: ['GET'])]
#[IsGranted('ROLE_ADMIN')]
public function index(): Response {}
}
Doctrine
<?php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private int $id;
#[ORM\Column(length: 255)]
private string $name;
#[ORM\Column(unique: true)]
private string $email;
}
13.7 属性实现缓存驱动
<?php
declare(strict_types=1);
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Cache
{
public function __construct(
public readonly int $ttl = 3600,
public readonly string $pool = 'default',
) {}
}
// AOP 风格的缓存代理
class CachedService
{
public function __construct(
private readonly UserService $service,
private readonly CacheInterface $cache,
) {}
public function __call(string $method, array $args): mixed
{
$reflection = new \ReflectionMethod($this->service, $method);
$attrs = $reflection->getAttributes(Cache::class);
if (empty($attrs)) {
return $this->service->$method(...$args);
}
$cache = $attrs[0]->newInstance();
$key = sprintf('%s:%s:%s', get_class($this->service), $method, md5(serialize($args)));
$cached = $this->cache->get($key);
if ($cached !== null) {
return $cached;
}
$result = $this->service->$method(...$args);
$this->cache->set($key, $result, $cache->ttl);
return $result;
}
}
13.8 扩展阅读
上一章:第 12 章 — 异常处理 下一章:第 14 章 — 生成器