kode/attributes

轻量级、健壮的 PHP 8.3+ 属性读取器,为 kodephp 框架和主流 PHP 框架提供基础组件支持

Maintainers

Package info

github.com/kodephp/attributes

Documentation

pkg:composer/kode/attributes

Transparency log

Fund package maintenance!

github.com/sponsors/kodephp

Statistics

Installs: 117

Dependents: 4

Suggesters: 0

Stars: 1

Open Issues: 0

2.1.1 2026-08-04 06:37 UTC

This package is auto-updated.

Last update: 2026-08-04 06:37:44 UTC


README

PHP Version License Latest Stable Version

一个轻量级、健壮的 PHP 8.3+ 属性(Attribute)读取器,为 kodephp 框架和主流 PHP 框架(Laravel、Symfony、ThinkPHP8、Webman 等)提供基础组件支持。

v2.0.0 重大修复(根因):1.x 的 Attr 门面只接受类名字符串或对象。一旦传入 ReflectionClass / ReflectionProperty / ReflectionParameter,由于它们本身也是“对象”,会被当作普通对象处理,转而读取 Reflection 类自身 的属性——永远返回空集合且不报错,整条属性注入链就此静默失效。2.0 通过 TargetRef 将所有目标归一化为 Reflector 实例并原样透传,从根因上杜绝该问题,并在目标不存在时抛出 TargetNotFoundException 而非静默返回空集合。

特性

  • 零依赖 - 仅使用 PHP 原生功能,无第三方依赖
  • 高性能 - 内置反射缓存机制,延迟实例化属性对象
  • 类型安全 - 利用 PHP 8.3+ 的枚举、泛型与 #[Override] 等特性
  • IDE 友好 - 提供完整的 PHPStorm 元数据支持
  • 协变支持 - MetaList<@template-covariant T> 支持类型安全的协变
  • 框架无关 - 可在任何 PHP 8.3+ 项目中使用
  • 安全封装 - 封装反射 API,避免直接暴露 ReflectionClass
  • 可扩展 - 可插拔缓存系统,支持自定义缓存驱动

安装

composer require kode/attributes

快速开始

基本用法

use Kode\Attributes\Attr;

// 检查类是否具有特定属性
if (Attr::has(MyClass::class, MyAttribute::class)) {
    // 获取属性实例
    $meta = Attr::get(MyClass::class, MyAttribute::class);
    $instance = $meta->getInstance();
}

// 获取类的所有属性
$attributes = Attr::of(MyClass::class);
foreach ($attributes as $meta) {
    echo $meta->name; // 属性类名
    print_r($meta->args); // 属性参数
}

高级用法

use Kode\Attributes\Reader;
use Kode\Attributes\ArrayCache;

// 创建带自定义缓存的读取器
$cache = new ArrayCache();
$reader = new Reader($cache);

// 获取类属性
$classAttrs = $reader->getClassAttrs(MyClass::class);

// 获取方法属性
$methodAttrs = $reader->getMethodAttrs(MyClass::class, 'myMethod');

// 获取属性属性
$propertyAttrs = $reader->getPropertyAttrs(MyClass::class, 'myProperty');

// 获取缓存统计
$stats = $cache->getStats();
echo "缓存命中率: " . ($stats['hitRate'] * 100) . "%";

过滤与映射

use Kode\Attributes\Attr;

// 过滤属性
$filtered = Attr::of(MyClass::class)
    ->filter(fn($meta) => is_subclass_of($meta->name, BaseAttribute::class));

// 映射属性为实例
$instances = Attr::of(MyClass::class)
    ->map(fn($meta) => $meta->getInstance());

// 获取所有指定类型的属性
$allRoutes = Attr::getAll(MyClass::class, Route::class);

// 分组
$grouped = Attr::of(MyClass::class)->groupByName();

实际示例

路由定义

use Attribute;

#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Route
{
    public function __construct(
        public readonly string $path,
        public readonly string $method = 'GET',
        public readonly string $name = ''
    ) {}
}

#[Route('/api/users', 'GET')]
#[Route('/api/users', 'POST')]
class UserController
{
    #[Route('/api/users/{id}', 'GET')]
    public function getUser(int $id): void
    {
        // 方法实现
    }
}

// 读取路由属性
$routes = Attr::of(UserController::class);

// 过滤 POST 路由
$postRoutes = $routes->filter(fn($meta) => $meta->getInstance()->method === 'POST');

// 获取所有路径
$paths = $routes->map(fn($meta) => $meta->getInstance()->path);

权限控制

use Attribute;

#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Role
{
    public function __construct(
        public readonly string $name,
        public readonly int $priority = 0
    ) {}
}

#[Role('admin', 100)]
#[Role('user', 50)]
class AdminController
{
    #[Role('super-admin', 200)]
    public function dangerousAction(): void
    {
        // 需要超级管理员权限
    }
}

// 检查权限
$roles = Attr::of(AdminController::class)
    ->filter(fn($meta) => $meta->name === Role::class)
    ->map(fn($meta) => $meta->getInstance()->name);

// 按优先级排序
$sortedRoles = Attr::of(AdminController::class)
    ->filter(fn($meta) => $meta->name === Role::class)
    ->sort(fn($a, $b) => $b->getInstance()->priority <=> $a->getInstance()->priority);

目录扫描

use Kode\Attributes\Attr;

// 创建扫描器
$scanner = Attr::scan(__DIR__ . '/src');

// 排除特定目录
$scanner->exclude('vendor', 'tests', '.git');

// 查找所有带 Route 属性的类
foreach ($scanner->find(__DIR__ . '/src', Route::class) as $class => $metas) {
    echo "发现路由类: $class\n";
    foreach ($metas as $meta) {
        $route = $meta->getInstance();
        echo "  - {$route->method} {$route->path}\n";
    }
}

// 深度扫描(包含方法、属性)
foreach ($scanner->scanDeep(__DIR__ . '/src') as $class => $info) {
    echo "类: $class\n";
    echo "  类属性: " . count($info['class']) . "\n";
    echo "  方法属性: " . count($info['methods']) . "\n";
    echo "  属性属性: " . count($info['properties']) . "\n";
}

API 参考

主要类

类名 职责
Attr 全局门面,提供静态访问入口
Reader 属性读取器核心实现
Meta 单个属性元数据封装
MetaList 属性集合,支持链式操作
ArrayCache 内存缓存实现
Scanner 目录扫描器
Target 属性目标类型枚举
Flags 属性行为标志
TargetRef 目标归一化与缓存键生成(2.0 核心修复层)
TargetSet 位掩码目标集合(精确表达组合目标,不再退化成 All)
Inspector 链式属性检查器(2.0 新增)
Cache\RedisCache 基于 Redis 的共享/分布式缓存适配器(2.1.0 新增,面向多进程/Fibers/分布式)
Exception\* 异常体系:AttributeException / InvalidTargetException / TargetNotFoundException / AttributeInstantiationException

关键接口

接口名 职责
ReaderInterface 属性读取器契约
CacheInterface 缓存驱动契约

Attr 门面

// 获取 Reader 实例
Attr::reader(): Reader

// 设置自定义 Reader
Attr::setReader(Reader $reader): void

// 获取目标的所有属性(接受任意 Reflector / 闭包 / 可调用数组 / 成员字符串,并支持继承链)
Attr::of(mixed $target, bool $inherited = false): MetaList

// 检查是否存在属性
Attr::has(object|string $target, string $attrClass): bool

// 获取单个属性
Attr::get(object|string $target, string $attrClass): ?Meta

// 获取所有指定类型属性
Attr::getAll(object|string $target, string $attrClass): MetaList

// 创建扫描器
Attr::scan(string $dir): Scanner

// 清除缓存
Attr::clear(): void

// ===== 2.0 新增 API =====

// 严格模式:实例化异常时抛出而非静默跳过
Attr::strict(bool $strict = true): void

// 针对单一目标创建链式检查器
Attr::on(mixed $target): Inspector

// 直接获取属性实例(非严格模式跳过损坏项,严格模式抛出)
Attr::instances(mixed $target, string $attrClass, bool $inherited = false): array

// 按成员类型精确读取
Attr::ofClass(object|string $class, bool $inherited = false): MetaList
Attr::ofMethod(object|string $class, string $method, bool $inherited = false): MetaList
Attr::ofProperty(object|string $class, string $property, bool $inherited = false): MetaList
Attr::ofConstant(object|string $class, string $constant): MetaList
Attr::ofFunction(string|Closure $function): MetaList
Attr::ofParameter(ReflectionParameter|string|array|Closure $target, ...): MetaList
Attr::ofEnumCase(UnitEnum $case): MetaList

// 批量按成员分组读取
Attr::methods(object|string $class, ?string $attrClass = null, bool $inherited = false): array
Attr::properties(object|string $class, ?string $attrClass = null, bool $inherited = false): array
Attr::constants(object|string $class, ?string $attrClass = null): array
Attr::parameters(object|string $target, ?string $method = null, ?string $attrClass = null): array

MetaList 集合方法

// 获取元素
$metaList->first(): ?Meta
$metaList->last(): ?Meta
$metaList->at(int $index): ?Meta
$metaList->all(): array

// 查询
$metaList->has(string $className): bool
$metaList->get(string $className): ?Meta
$metaList->getAll(string $className): MetaList
$metaList->count(): int
$metaList->isEmpty(): bool
$metaList->isNotEmpty(): bool

// 操作
$metaList->filter(callable $fn): MetaList
$metaList->map(callable $fn): array
$metaList->flatMap(callable $fn): array
$metaList->each(callable $fn): void
$metaList->some(callable $fn): bool
$metaList->every(callable $fn): bool

// 排序与分页
$metaList->sort(callable $comparator): MetaList
$metaList->reverse(): MetaList
$metaList->take(int $limit): MetaList
$metaList->skip(int $offset): MetaList

// 分组
$metaList->groupByName(): array
$metaList->groupBy(callable $fn): array

// 合并
$metaList->merge(MetaList $other): MetaList

Meta 元数据方法

// 基本信息
$meta->name: string           // 属性类名
$meta->args: array            // 属性参数
$meta->reflector: ?\Reflector // 所属反射对象

// 实例化
$meta->getInstance(): object
$meta->newInstance(): object

// 目标信息
$meta->getTarget(): Target
$meta->supportsTarget(Target $target): bool
$meta->isRepeatable(): bool

// 参数访问
$meta->getArgument(string $name, mixed $default = null): mixed
$meta->getNamedArguments(): array
$meta->getPositionalArguments(): array

// 其他
$meta->getDeclaringClass(): ?string
$meta->toArray(): array

Target 枚举

use Kode\Attributes\Target;

// 枚举值
Target::Clazz         // 类
Target::Function      // 函数
Target::Method        // 方法
Target::Property      // 属性
Target::ClassConstant // 类常量
Target::Parameter     // 参数
Target::All           // 全部

// 静态方法
Target::fromRef(\Reflector $ref): Target
Target::fromAttributeFlags(int $flags): Target

// 实例方法
$target->supports(Target $other): bool
$target->combine(Target ...$others): Target
$target->getTargets(): array
$target->toAttributeFlags(): int
$target->getLabel(): string      // 中文标签
$target->toString(): string

Flags 标志类

use Kode\Attributes\Flags;

// 创建实例
new Flags(
    inherit: bool,      // 是否继承
    compileTime: bool,  // 是否编译期处理
    priority: int,      // 优先级
    cacheable: bool     // 是否可缓存
)

// 静态工厂方法
Flags::inherit(int $priority = 0): Flags
Flags::compileTime(int $priority = 0): Flags
Flags::highPriority(int $priority = 100): Flags
Flags::nonCacheable(): Flags

// 实例方法
$flags->isDefault(): bool
$flags->merge(Flags $other): Flags
$flags->toArray(): array
$flags->__toString(): string

自定义缓存驱动

本包内置 Kode\Attributes\Cache\RedisCache(2.1.0 起),开箱即用地支持 多进程 / 协程 Fibers / 分布式 场景,无需自行实现。如需其它后端(APCu、文件、Memcached),实现 CacheInterface 即可。

内置 RedisCache 已实现 CacheInterface,并具备:键前缀隔离、TTL 过期、SCAN 前缀批量清理、跨进程保留属性实例(不可序列化时自动降级为快照)。

use Kode\Attributes\Attr;
use Kode\Attributes\Cache\RedisCache;

$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);

// 建议前缀包含应用名/版本,避免与其它键冲突
$cache = new RedisCache($redis, 'myapp:attr:v2:', 3600);

// 方式一:直接替换全局 Reader 的缓存
Attr::setCache($cache);

// 方式二:构建独立 Reader
$reader = new \Kode\Attributes\Reader($cache);
Attr::setReader($reader);

// 之后所有 Attr::of / Reader 读取都会经由该共享缓存,多进程/多节点共享反射结果
$routes = Attr::ofClass(UserController::class);

若需自行实现缓存驱动,实现 CacheInterfaceget/set/has/delete/clear)即可,签名与 ArrayCache 一致。

并发与分布式(多进程 / 协程 Fibers / 分布式)

kode/attributes 的读取器在常驻进程(Swoole / Workerman / FPM 多进程)、协程(Fibers)与分布式(多节点)环境下均保持正确与高效,关键在于「反射结果可跨进程复用」:

  • 多进程:每个 worker 通过共享缓存(如 RedisCache)复用反射元数据,避免重复反射;Attr::clearCache() 可在 fork 后的子进程中安全清空继承自父进程的缓存。
  • 协程 / Fibers:属性读取为纯反射、无全局可变状态,天然可重入,可在 Fiber 内并发读取。
  • 分布式:通过 RedisCache 将元数据缓存到共享 Redis,跨节点共享,降低集群整体反射开销。
  • 实例保留Meta / MetaList 支持原生序列化并保留已实例化的属性对象,因此从共享缓存取回后无需再次实例化即可直接使用;若某属性实例不可序列化(极少见,例如构造参数含闭包),会自动降级为「纯数据快照」写入,保证缓存写入永不抛异常。
use Kode\Attributes\Attr;
use Kode\Attributes\Cache\RedisCache;

// 多进程启动 / 节点接入时挂载共享缓存
Attr::setCache(new RedisCache($redis, 'myapp:attr:v2:', 3600));

// 多进程 / 分布式场景:一次反射,处处复用
$list = Attr::of(UserController::class);
$instance = $list->first()->getInstance(); // 跨进程取回的 Meta 仍可直接返回实例

// fork 后的子进程:清空继承自父进程的缓存(避免读到过期/错乱的父进程状态)
$pid = pcntl_fork();
if ($pid === 0) {
    Attr::clearCache();
    // ... 子进程逻辑
}

注意:ArrayCache 仅限单进程内存,跨进程 / 分布式请改用 RedisCache 或其它实现 CacheInterface 的共享驱动。

框架内属性定义与获取

kode/attributes 与具体框架无关——你可以像使用原生 PHP Attribute 一样在类、方法、属性、参数、常量上声明属性,再通过 Attr 门面(或 Inspector 链式 API)在框架的启动、路由收集、依赖注入、事件发现等环节读取。

use Attribute;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Route
{
    public function __construct(
        public readonly string $path,
        public readonly array $methods = ['GET']
    ) {}
}

#[Attribute(Attribute::TARGET_PROPERTY)]
class Inject
{
    public function __construct(public readonly string $service) {}
}

#[Route('/users', ['GET', 'POST'])]
class UserController
{
    #[Inject('App\Service\UserRepo')]
    private $repo;

    #[Route('/users/{id}', ['GET'])]
    public function show(int $id): void {}
}

按成员类型精确读取(框架路由收集器 / DI 容器常用):

use Kode\Attributes\Attr;

// 类级属性
Attr::ofClass(UserController::class)->first()->getInstance();      // Route('/users', ['GET','POST'])

// 方法级属性
Attr::ofMethod(UserController::class, 'show')->first()->getInstance(); // Route('/users/{id}', ['GET'])

// 属性级属性(DI 注入声明)
Attr::ofProperty(UserController::class, 'repo')->first()->getInstance(); // Inject('App\Service\UserRepo')

// 批量收集某类所有带 Route 的方法(路由注册表)
$routes = Attr::methods(UserController::class, Route::class);

链式读取Inspector,等价于框架内部的收集逻辑):

use Kode\Attributes\Attr;

$route = Attr::on([UserController::class, 'show'])
    ->get(Route::class)
    ?->getInstance();

// 或基于对象实例
$route = Attr::on(new UserController())
    ->property('repo')
    ->get(Inject::class)
    ?->getInstance();

直接传入 Reflection 对象(2.0 根因修复后支持,框架内部常持有 Reflection 实例):

$ref = new \ReflectionProperty(UserController::class, 'repo');
Attr::of($ref)->first()->getInstance(); // 读取的是属性自身的 Inject,而非 Reflection 类自身

系统要求

  • PHP >= 8.3
  • ext-json
  • ext-mbstring
  • ext-redis(仅在使用 Cache\RedisCache 时需要)

兼容性

PHP 版本 支持状态
PHP 8.1 ❌ 2.0 已不再支持(需 8.3+)
PHP 8.2 ❌ 2.0 已不再支持(需 8.3+)
PHP 8.3 ✅ 完全支持(最低要求)
PHP 8.4 ✅ 完全支持
PHP 8.5 ✅ 完全支持(优先使用新特性)

框架兼容性

框架 支持状态
Laravel ✅ 完全兼容
Symfony ✅ 完全兼容
ThinkPHP 8 ✅ 完全兼容
Webman ✅ 完全兼容
Hyperf ✅ 完全兼容
原生 PHP ✅ 完全兼容

测试

# 运行测试
composer test

# 生成覆盖率报告
composer test:coverage

# 代码风格检查
composer check

# 代码风格修复
composer fix

贡献

欢迎提交 Issue 和 Pull Request 来改进这个项目。

贡献指南

  1. Fork 本仓库
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 创建 Pull Request

许可证

本项目基于 Apache License 2.0 开源协议发布。

Copyright 2024-2026 kode (KodePHP)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

署名信息同时记录于 LICENSENOTICE 与各源码文件头部。

作者

kode (KodePHP) - 382601296@qq.com - https://github.com/kodephp

致谢

感谢所有为这个项目做出贡献的开发者!