KaririCode-Framework/kariricode-property-inspector

GitHub: KaririCode-Framework/kariricode-property-inspector

KaririCode框架的属性分析组件,通过Attribute元数据和反射缓存实现高性能的对象属性验证、清洗与标准化处理。

Stars: 0 | Forks: 0

# KaririCode PropertyInspector
[![PHP 8.4+](https://img.shields.io/badge/PHP-8.4%2B-777BB4?logo=php&logoColor=white)](https://www.php.net/) [![License: MIT](https://img.shields.io/badge/License-MIT-22c55e.svg)](LICENSE) [![PHPStan Level 9](https://img.shields.io/badge/PHPStan-Level%209-4F46E5)](https://phpstan.org/) [![Tests](https://img.shields.io/badge/Tests-40%20passing-22c55e)](https://kariricode.org) [![ARFA](https://img.shields.io/badge/ARFA-1.3-orange)](https://kariricode.org) [![KaririCode Framework](https://img.shields.io/badge/KaririCode-Framework-orange)](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 集成, 云安全监控, 元编程, 单元测试, 反射, 后端开发, 契约设计, 威胁情报, 对象图, 属性分析, 库, 应急响应, 开发者工具, 数据校验, 注解, 管道模式, 组件, 缓存, 规范化, 静态分析