PHP 8.0 属性(Attributes)实战应用:元数据编程的新时代
Orion K Lv6

引言

PHP 8.0引入的属性(Attributes)功能让我们终于可以在PHP中使用类似Java注解或C#特性的元数据编程了。作为一个从Java转到PHP的开发者,这个特性让我感到非常兴奋。经过一年多的实践,我想分享一些属性的实际应用场景和最佳实践。

什么是属性

属性是一种结构化的元数据,可以附加到类、方法、属性、参数等代码元素上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 基本语法
#[Attribute]
class MyAttribute {
public function __construct(
public string $value,
public array $options = []
) {}
}

// 使用属性
#[MyAttribute('example', ['key' => 'value'])]
class ExampleClass {
#[MyAttribute('property')]
public string $property;

#[MyAttribute('method')]
public function method(#[MyAttribute('param')] string $param): void {
// 方法实现
}
}

从DocBlock注释到属性的进化

传统的DocBlock方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @Route("/api/users", methods={"GET", "POST"})
* @Middleware("auth")
* @RateLimit(requests=100, window=3600)
*/
class UserController {
/**
* @Validate(rules={"name": "required", "email": "email"})
* @Cache(ttl=300, tags={"users"})
*/
public function index() {
// 控制器逻辑
}
}

使用属性的现代方式

1
2
3
4
5
6
7
8
9
10
#[Route('/api/users', methods: ['GET', 'POST'])]
#[Middleware('auth')]
#[RateLimit(requests: 100, window: 3600)]
class UserController {
#[Validate(rules: ['name' => 'required', 'email' => 'email'])]
#[Cache(ttl: 300, tags: ['users'])]
public function index() {
// 控制器逻辑
}
}

实际应用场景

1. 路由定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Route {
public function __construct(
public string $path,
public array $methods = ['GET'],
public ?string $name = null,
public array $middleware = []
) {}
}

#[Route('/api')]
class ApiController {
#[Route('/users', methods: ['GET'], name: 'users.index')]
public function getUsers(): array {
return User::all();
}

#[Route('/users', methods: ['POST'], name: 'users.store')]
public function createUser(): User {
// 创建用户逻辑
}
}

// 路由解析器
class RouteParser {
public function parseController(string $controllerClass): array {
$reflection = new ReflectionClass($controllerClass);
$routes = [];

// 获取类级别的路由前缀
$classRoutes = $reflection->getAttributes(Route::class);
$prefix = '';
if (!empty($classRoutes)) {
$prefix = $classRoutes[0]->newInstance()->path;
}

// 解析方法路由
foreach ($reflection->getMethods() as $method) {
$routeAttrs = $method->getAttributes(Route::class);
foreach ($routeAttrs as $routeAttr) {
$route = $routeAttr->newInstance();
$routes[] = [
'path' => $prefix . $route->path,
'methods' => $route->methods,
'handler' => [$controllerClass, $method->getName()],
'name' => $route->name
];
}
}

return $routes;
}
}

2. 数据验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#[Attribute(Attribute::TARGET_PROPERTY)]
class Validate {
public function __construct(
public array $rules = [],
public ?string $message = null
) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Required {
public function __construct(public ?string $message = null) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Email {
public function __construct(public ?string $message = null) {}
}

class UserRequest {
#[Required(message: '姓名不能为空')]
#[Validate(rules: ['min:2', 'max:50'])]
public string $name;

#[Required]
#[Email(message: '请输入有效的邮箱地址')]
public string $email;

#[Validate(rules: ['min:18', 'max:100'])]
public ?int $age = null;
}

// 验证器
class Validator {
public function validate(object $object): array {
$reflection = new ReflectionClass($object);
$errors = [];

foreach ($reflection->getProperties() as $property) {
$value = $property->getValue($object);

// 检查Required属性
$requiredAttrs = $property->getAttributes(Required::class);
if (!empty($requiredAttrs) && empty($value)) {
$required = $requiredAttrs[0]->newInstance();
$errors[$property->getName()][] = $required->message ?? '此字段为必填项';
continue;
}

// 检查Email属性
$emailAttrs = $property->getAttributes(Email::class);
if (!empty($emailAttrs) && !empty($value) && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$email = $emailAttrs[0]->newInstance();
$errors[$property->getName()][] = $email->message ?? '邮箱格式不正确';
}

// 检查其他验证规则
$validateAttrs = $property->getAttributes(Validate::class);
foreach ($validateAttrs as $validateAttr) {
$validate = $validateAttr->newInstance();
$errors = array_merge($errors, $this->validateRules($property->getName(), $value, $validate->rules));
}
}

return $errors;
}

private function validateRules(string $field, mixed $value, array $rules): array {
$errors = [];
foreach ($rules as $rule) {
if (str_starts_with($rule, 'min:')) {
$min = (int)substr($rule, 4);
if (is_string($value) && strlen($value) < $min) {
$errors[$field][] = "最少需要{$min}个字符";
} elseif (is_int($value) && $value < $min) {
$errors[$field][] = "最小值为{$min}";
}
}
// 更多验证规则...
}
return $errors;
}
}

3. 数据库映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#[Attribute(Attribute::TARGET_CLASS)]
class Table {
public function __construct(public string $name) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Column {
public function __construct(
public ?string $name = null,
public string $type = 'string',
public bool $nullable = false,
public bool $primaryKey = false
) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Relationship {
public function __construct(
public string $type,
public string $related,
public ?string $foreignKey = null
) {}
}

#[Table('users')]
class User {
#[Column(name: 'id', type: 'integer', primaryKey: true)]
public int $id;

#[Column(name: 'username', type: 'string')]
public string $username;

#[Column(name: 'email', type: 'string')]
public string $email;

#[Column(name: 'created_at', type: 'datetime')]
public DateTime $createdAt;

#[Relationship(type: 'hasMany', related: Post::class, foreignKey: 'user_id')]
public array $posts = [];
}

// ORM查询构建器
class QueryBuilder {
public function getTableName(string $modelClass): string {
$reflection = new ReflectionClass($modelClass);
$tableAttrs = $reflection->getAttributes(Table::class);

if (!empty($tableAttrs)) {
return $tableAttrs[0]->newInstance()->name;
}

// 默认使用类名的复数形式
return strtolower($reflection->getShortName()) . 's';
}

public function getColumns(string $modelClass): array {
$reflection = new ReflectionClass($modelClass);
$columns = [];

foreach ($reflection->getProperties() as $property) {
$columnAttrs = $property->getAttributes(Column::class);
if (!empty($columnAttrs)) {
$column = $columnAttrs[0]->newInstance();
$columns[$property->getName()] = [
'name' => $column->name ?? $property->getName(),
'type' => $column->type,
'nullable' => $column->nullable,
'primaryKey' => $column->primaryKey
];
}
}

return $columns;
}
}

4. 缓存配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#[Attribute(Attribute::TARGET_METHOD)]
class Cache {
public function __construct(
public int $ttl = 3600,
public array $tags = [],
public ?string $key = null,
public bool $enabled = true
) {}
}

#[Attribute(Attribute::TARGET_METHOD)]
class CacheEvict {
public function __construct(
public array $tags = [],
public ?string $key = null
) {}
}

class ProductService {
#[Cache(ttl: 7200, tags: ['products', 'catalog'])]
public function getProducts(string $category): array {
// 获取产品列表
return $this->repository->findByCategory($category);
}

#[Cache(ttl: 3600, key: 'product_{id}')]
public function getProduct(int $id): ?Product {
return $this->repository->find($id);
}

#[CacheEvict(tags: ['products'])]
public function updateProduct(int $id, array $data): Product {
// 更新产品,清除相关缓存
return $this->repository->update($id, $data);
}
}

// 缓存拦截器
class CacheInterceptor {
public function __construct(private CacheInterface $cache) {}

public function intercept(object $instance, string $method, array $args): mixed {
$reflection = new ReflectionMethod($instance, $method);

// 处理缓存读取
$cacheAttrs = $reflection->getAttributes(Cache::class);
if (!empty($cacheAttrs)) {
$cache = $cacheAttrs[0]->newInstance();
if ($cache->enabled) {
$key = $this->generateCacheKey($cache->key, $method, $args);

if ($this->cache->has($key)) {
return $this->cache->get($key);
}

$result = $instance->$method(...$args);
$this->cache->set($key, $result, $cache->ttl, $cache->tags);
return $result;
}
}

// 处理缓存清除
$evictAttrs = $reflection->getAttributes(CacheEvict::class);
if (!empty($evictAttrs)) {
$result = $instance->$method(...$args);

foreach ($evictAttrs as $evictAttr) {
$evict = $evictAttr->newInstance();
if ($evict->key) {
$this->cache->delete($evict->key);
}
if (!empty($evict->tags)) {
$this->cache->deleteByTags($evict->tags);
}
}

return $result;
}

return $instance->$method(...$args);
}

private function generateCacheKey(?string $template, string $method, array $args): string {
if ($template) {
// 替换模板中的占位符
return preg_replace_callback('/\{(\w+)\}/', function($matches) use ($args) {
$param = $matches[1];
return $args[$param] ?? $matches[0];
}, $template);
}

return $method . '_' . md5(serialize($args));
}
}

5. 权限控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class RequirePermission {
public function __construct(
public string|array $permissions,
public string $operator = 'AND'
) {}
}

#[Attribute(Attribute::TARGET_METHOD)]
class RequireRole {
public function __construct(public string|array $roles) {}
}

#[RequirePermission('admin')]
class AdminController {
#[RequirePermission(['user.read', 'user.list'])]
public function getUsers(): array {
return User::all();
}

#[RequirePermission('user.create')]
#[RequireRole('admin')]
public function createUser(array $data): User {
return User::create($data);
}

#[RequirePermission(['user.update', 'user.delete'], operator: 'OR')]
public function updateUser(int $id, array $data): User {
return User::update($id, $data);
}
}

// 权限检查中间件
class PermissionMiddleware {
public function handle(object $controller, string $method): bool {
$reflection = new ReflectionMethod($controller, $method);
$user = $this->getCurrentUser();

// 检查类级别权限
$classReflection = new ReflectionClass($controller);
if (!$this->checkClassPermissions($classReflection, $user)) {
return false;
}

// 检查方法级别权限
return $this->checkMethodPermissions($reflection, $user);
}

private function checkMethodPermissions(ReflectionMethod $method, User $user): bool {
// 检查权限要求
$permissionAttrs = $method->getAttributes(RequirePermission::class);
foreach ($permissionAttrs as $permissionAttr) {
$permission = $permissionAttr->newInstance();
if (!$this->hasPermissions($user, $permission->permissions, $permission->operator)) {
return false;
}
}

// 检查角色要求
$roleAttrs = $method->getAttributes(RequireRole::class);
foreach ($roleAttrs as $roleAttr) {
$role = $roleAttr->newInstance();
if (!$this->hasRoles($user, $role->roles)) {
return false;
}
}

return true;
}
}

创建自定义属性

1. 基本属性类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#[Attribute]
class ApiVersion {
public function __construct(
public string $version,
public bool $deprecated = false
) {}
}

#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class RateLimit {
public function __construct(
public int $requests,
public int $window,
public string $key = 'ip'
) {}
}

// 使用示例
class ApiController {
#[ApiVersion('1.0')]
#[RateLimit(requests: 100, window: 3600)]
#[RateLimit(requests: 10, window: 60, key: 'user')]
public function getData(): array {
return ['data' => 'example'];
}
}

2. 复杂属性处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#[Attribute]
class EventListener {
public function __construct(
public string $event,
public int $priority = 0,
public bool $once = false
) {}
}

class OrderService {
#[EventListener('order.created', priority: 10)]
public function sendConfirmationEmail(OrderCreatedEvent $event): void {
// 发送确认邮件
}

#[EventListener('order.created', priority: 5)]
public function updateInventory(OrderCreatedEvent $event): void {
// 更新库存
}

#[EventListener('order.cancelled', once: true)]
public function handleCancellation(OrderCancelledEvent $event): void {
// 处理订单取消
}
}

// 事件监听器注册
class EventDispatcher {
private array $listeners = [];

public function registerListeners(object $service): void {
$reflection = new ReflectionClass($service);

foreach ($reflection->getMethods() as $method) {
$listenerAttrs = $method->getAttributes(EventListener::class);

foreach ($listenerAttrs as $listenerAttr) {
$listener = $listenerAttr->newInstance();

$this->listeners[$listener->event][] = [
'callback' => [$service, $method->getName()],
'priority' => $listener->priority,
'once' => $listener->once
];
}
}

// 按优先级排序
foreach ($this->listeners as $event => $listeners) {
usort($this->listeners[$event], fn($a, $b) => $b['priority'] <=> $a['priority']);
}
}
}

性能考虑

属性的性能影响主要在反射操作上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 性能优化:缓存反射结果
class AttributeCache {
private static array $cache = [];

public static function getClassAttributes(string $class, string $attributeClass): array {
$key = $class . '::' . $attributeClass;

if (!isset(self::$cache[$key])) {
$reflection = new ReflectionClass($class);
self::$cache[$key] = $reflection->getAttributes($attributeClass);
}

return self::$cache[$key];
}
}

// 使用缓存
$routeAttrs = AttributeCache::getClassAttributes(UserController::class, Route::class);

最佳实践

1. 属性设计原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 好的属性设计
#[Attribute]
class Cache {
public function __construct(
public int $ttl = 3600, // 明确的参数类型
public array $tags = [], // 合理的默认值
public ?string $key = null // 可选参数使用nullable
) {}
}

// 避免的设计
#[Attribute]
class BadCache {
public function __construct(
public $ttl, // 缺少类型声明
public $tags = null, // 不一致的默认值类型
public $key = '' // 空字符串而不是null
) {}
}

2. 文档和验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#[Attribute(Attribute::TARGET_METHOD)]
class ApiEndpoint {
public function __construct(
public string $path,
public array $methods = ['GET'],
public ?string $summary = null,
public ?string $description = null,
public array $parameters = [],
public array $responses = []
) {
// 参数验证
if (empty($path)) {
throw new InvalidArgumentException('Path cannot be empty');
}

$validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'];
foreach ($methods as $method) {
if (!in_array($method, $validMethods)) {
throw new InvalidArgumentException("Invalid HTTP method: $method");
}
}
}
}

总结

PHP 8.0的属性功能为我们带来了强大的元数据编程能力,它让代码更加声明式和易于理解。在实际项目中,我发现属性特别适用于:

  1. 框架开发:路由、中间件、验证等
  2. 配置管理:缓存、权限、API文档等
  3. 代码生成:ORM映射、序列化等
  4. 横切关注点:日志、监控、性能分析等

使用属性时要注意:

  • 保持属性的简单性和专一性
  • 合理使用缓存避免性能问题
  • 提供清晰的文档和示例
  • 考虑向后兼容性

属性让PHP更加现代化,是构建企业级应用的重要工具!

本站由 提供部署服务