KaririCode-Framework/kariricode-property-inspector
GitHub: KaririCode-Framework/kariricode-property-inspector
KaririCode框架的属性分析组件,通过Attribute元数据和反射缓存实现高性能的对象属性验证、清洗与标准化处理。
Stars: 0 | Forks: 0
# KaririCode PropertyInspector
[](https://www.php.net/)
[](LICENSE)
[](https://phpstan.org/)
[](https://kariricode.org)
[](https://kariricode.org)
[](https://kariricode.org)
**KaririCode Framework 的基于 Attribute 的属性分析与检查 ——
多通道 Pipeline、反射缓存以及零开销属性修改,PHP 8.4+。**
[安装](#installation) · [快速入门](#quick-start) · [功能](#features) · [Pipeline](#the-inspection-pipeline) · [架构](#architecture)
## 问题所在
PHP 反射存在样板代码繁琐、容易出错的问题,并且在对象图中重复执行时速度缓慢:
```
// The old way: raw reflection on every request
$ref = new ReflectionClass($user);
foreach ($ref->getProperties() as $prop) {
$attrs = $prop->getAttributes(Validate::class);
foreach ($attrs as $attr) {
$prop->setAccessible(true); // deprecated in PHP 8.4
$value = $prop->getValue($user);
// now what? where does the result go? how do you write it back?
}
}
```
没有缓存,没有修改抽象,没有错误隔离,没有处理器契约 —— 只有你在每个项目中重复编写的原始循环。
## 解决方案
```
use KaririCode\PropertyInspector\AttributeAnalyzer;
use KaririCode\PropertyInspector\Utility\PropertyInspector;
use KaririCode\PropertyInspector\Utility\PropertyAccessor;
// 1. Configure which attribute to scan for
$analyzer = new AttributeAnalyzer(Validate::class);
$inspector = new PropertyInspector($analyzer);
// 2. Inspect — results cached after first call per class
$handler = new MyValidationHandler();
$inspector->inspect($user, $handler);
// 3. Read processed values and errors
$values = $handler->getProcessedPropertyValues();
$errors = $handler->getProcessingResultErrors();
// 4. Write back changed values via PropertyAccessor
$accessor = new PropertyAccessor($user, 'email');
$accessor->setValue(strtolower($accessor->getValue()));
```
## 环境要求
| 需求 | 版本 |
|---|---|
| PHP | 8.4 或更高版本 |
| kariricode/contract | ^2.8 |
| kariricode/exception | ^1.2 |
## 安装
```
composer require kariricode/property-inspector
```
## 快速入门
定义一个 Attribute、一个实体、一个处理器 —— 然后进行检查:
```
processed[$propertyName] = $value;
if ($attribute instanceof Validate) {
foreach ($attribute->rules as $rule) {
if ($rule === 'required' && ($value === '' || $value === null)) {
$this->errors[$propertyName]['required'] = 'Field is required';
}
if (str_starts_with($rule, 'min:')) {
$min = (int) substr($rule, 4);
if (is_string($value) && strlen($value) < $min) {
$this->errors[$propertyName]['min'] = "Min {$min} chars required";
}
if (is_int($value) && $value < $min) {
$this->errors[$propertyName]['min'] = "Must be at least {$min}";
}
}
}
}
return $value;
}
public function getProcessedPropertyValues(): array { return $this->processed; }
public function getProcessingResultMessages(): array { return []; }
public function getProcessingResultErrors(): array { return $this->errors; }
}
// 4. Run the pipeline
$user = new User(name: 'Walmir', email: 'walmir@kariricode.org', age: 30);
$analyzer = new AttributeAnalyzer(Validate::class);
$inspector = new PropertyInspector($analyzer);
$handler = new ValidationHandler();
$inspector->inspect($user, $handler);
var_dump($handler->getProcessedPropertyValues());
// ['name' => 'Walmir', 'email' => 'walmir@kariricode.org', 'age' => 30]
var_dump($handler->getProcessingResultErrors());
// [] — all good
```
## 功能
### 反射缓存
`AttributeAnalyzer` 在首次分析某个类后会缓存反射元数据。后续对同一类的调用 —— 即使是不同的对象实例 —— 将完全跳过 `ReflectionClass`:
```
$analyzer = new AttributeAnalyzer(Validate::class);
$inspector = new PropertyInspector($analyzer);
// First call: reflection + cache build
$inspector->inspect($user1, $handler1);
// Subsequent calls: metadata from cache — zero reflection overhead
$inspector->inspect($user2, $handler2);
$inspector->inspect($user3, $handler3);
// Force re-analysis when needed (e.g., after metadata change)
$analyzer->clearCache();
```
### 多通道检查
使用不同的 Attribute 类型对同一对象运行多个独立的通道:
```
// Pass 1: sanitize
$sanitizeInspector = new PropertyInspector(new AttributeAnalyzer(Sanitize::class));
$sanitizeHandler = new TrimLowercaseHandler();
$sanitizeInspector->inspect($user, $sanitizeHandler);
// Apply sanitized values back to the object
foreach ($sanitizeHandler->getProcessedPropertyValues() as $prop => $value) {
(new PropertyAccessor($user, $prop))->setValue($value);
}
// Pass 2: validate on sanitized data
$validateInspector = new PropertyInspector(new AttributeAnalyzer(Validate::class));
$validateHandler = new ValidationHandler();
$validateInspector->inspect($user, $validateHandler);
$errors = $validateHandler->getProcessingResultErrors(); // [] if clean
```
### PropertyAccessor — 安全的属性修改
读取和写入任何属性(public、protected、private),无需 `setAccessible` 样板代码:
```
use KaririCode\PropertyInspector\Utility\PropertyAccessor;
$accessor = new PropertyAccessor($user, 'email');
$current = $accessor->getValue(); // read
$accessor->setValue(strtolower($current)); // write (no setAccessible needed)
```
### Attribute 多态
`AttributeAnalyzer` 使用 `ReflectionAttribute::IS_INSTANCEOF` —— 它匹配 **Attribute 层次结构**,而不仅仅是精确的类名:
```
// Matches Validate + any subclass of Validate
$analyzer = new AttributeAnalyzer(Validate::class);
```
### 隔离的错误处理
来自反射或处理器代码的所有异常都会被包装在 `PropertyInspectionException` 中 —— 你的调用代码只需捕获一种类型:
```
use KaririCode\PropertyInspector\Exception\PropertyInspectionException;
try {
$inspector->inspect($user, $handler);
} catch (PropertyInspectionException $e) {
// ReflectionException, TypeError, Error — all caught and re-wrapped
}
```
## 检查 Pipeline
```
$inspector->inspect($object, $handler)
│
▼
AttributeAnalyzer::analyzeObject($object)
├── Check class cache
├── If miss: ReflectionClass → getProperties()
│ └── foreach property:
│ getAttributes($attributeClass, IS_INSTANCEOF)
│ newInstance() → cache [{attributes, property}]
└── extractValues($object): [{value, attributes}]
│
▼
foreach property → foreach attribute:
$handler->handleAttribute($propertyName, $attribute, $value)
│
▼
return $handler (accumulates processed values + errors)
```
## 架构
### 源码结构
```
src/
├── AttributeAnalyzer.php Core analyzer — reflection + cache + attribute extraction
├── Contract/
│ ├── AttributeAnalyzer.php Interface: analyzeObject · clearCache
│ ├── PropertyAttributeHandler.php Interface: handleAttribute · getProcessed* · getErrors
│ ├── PropertyChangeApplier.php Interface: applyChanges
│ └── PropertyInspector.php Interface: inspect
├── Exception/
│ └── PropertyInspectionException.php Named factory methods per failure mode
└── Utility/
├── PropertyAccessor.php Safe property read/write (private, protected, public)
└── PropertyInspector.php Orchestrator: delegates analysis → handler
```
### 关键设计决策
| 决策 | 理由 | ADR |
|---|---|---|
| 每个类的反射缓存 | 每种类型只调用一次 `ReflectionClass`,而不是每个实例 | — |
| 移除 `setAccessible` | 在 PHP 8.1 中已弃用,在 PHP 9 中已移除;`PropertyAccessor` 负责处理此问题 | [ADR-001](docs/ADR-001-remove-setaccessible-dead-code.md) |
| 接口上的 `clearCache()` | 支持测试隔离和动态类重载 | [ADR-002](docs/ADR-002-add-clearcache-to-interface.md) |
| 包装的异常层次结构 | 调用者捕获 `PropertyInspectionException`,而不是反射内部异常 | [ADR-003](docs/ADR-003-correct-throws-annotation.md) |
| 处理器返回值 | 由处理器决定处理后的值 —— 支持链式调用和转换 | — |
### 规范
| 规范 | 涵盖内容 |
|---|---|
| [SPEC-001](docs/SPEC-001-property-inspection-pipeline.md) | 完整 Pipeline:分析 → 处理器 → 修改 |
## 与 KaririCode 生态系统集成
PropertyInspector 是其他 KaririCode 组件内部使用的 **反射引擎**:
| 组件 | 角色 |
|---|---|
| `kariricode/validator` | 使用 `PropertyInspector` 发现 `#[Rule]` Attribute 并分发到规则处理器 |
| `kariricode/sanitizer` | 使用 `PropertyInspector` 发现 `#[Sanitize]` Attribute 并应用转换器 |
| `kariricode/normalizer` | 使用 `PropertyInspector` 进行基于 Attribute 的标准化通道 |
任何需要 **声明式、基于 Attribute 的属性处理** 的组件都可以构建在此 Pipeline 之上。
## 项目统计
| 指标 | 数值 |
|---|---|
| PHP 源文件 | 7 |
| 外部运行时依赖 | 2 (contract · exception) |
| 测试套件 | 40 个测试 · 96 个断言 |
| PHPStan 级别 | 9 |
| PHP 版本 | 8.4+ |
| ARFA 合规性 | 1.3 |
| 测试套件类型 | Unit + Integration |
| 反射缓存 | 每个类,每个 `AttributeAnalyzer` 实例 |
## 许可证
[MIT License](LICENSE) © [Walmir Silva](mailto:community@kariricode.org)
**[KaririCode Framework](https://kariricode.org)** 生态系统的一部分。
[kariricode.org](https://kariricode.org) · [GitHub](https://github.com/KaririCode-Framework/kariricode-property-inspector) · [Packagist](https://packagist.org/packages/kariricode/property-inspector) · [Issues](https://github.com/KaririCode-Framework/kariricode-property-inspector/issues)
标签:ffuf, KaririCode, OpenVAS, PHP, PHP 8.4, PHPStan, Symfony/Laravel 集成, 云安全监控, 元编程, 单元测试, 反射, 后端开发, 契约设计, 威胁情报, 对象图, 属性分析, 库, 应急响应, 开发者工具, 数据校验, 注解, 管道模式, 组件, 缓存, 规范化, 静态分析