.gitignore 0000644 00000000230 15166406615 0006541 0 ustar 00 /.idea
/.vscode
/vendor
/build
*.log
.env
/tests/tmp
/tests/.phpunit.result.cache
.phpunit.result.cache
syntax: glob
!cfg/*.schema.php
/.trae
.DS_Store
README.md 0000644 00000024160 15166406615 0006040 0 ustar 00 # Awesome Validation
一个基于 PHP 8.1 的轻量级参数验证库,提供友好的错误消息和流畅的 API 设计。
## 特性
- 🚀 基于 PHP 8.1+ 语法,使用现代 PHP 特性
- 📝 遵循 PSR-4 自动加载规范
- 🔗 流畅的链式调用 API
- 💬 友好的中文错误消息
- 🎯 支持多种验证规则
- 🛡️ 支持自定义验证规则
- 📦 支持数组数据验证
- 🌳 支持嵌套字段验证
- ⚡ 支持异常抛出模式
## 安装
```bash
composer require awesome/validation
```
## 快速开始
### 基础验证
```php
use Awesome\Validation\Validator;
// 字符串验证
$result = Validator::string()
->minLength(5)
->maxLength(20)
->validate('hello world');
if ($result->isValid()) {
echo "验证通过";
} else {
print_r($result->errors()->all());
}
// 邮箱验证
$result = Validator::email()->validate('test@example.com');
// 整数验证
$result = Validator::integer()->validate('25');
// 必填验证
$result = Validator::required()->validate('value');
```
### 数组数据验证
```php
$data = [
'username' => 'john_doe',
'email' => 'john@example.com',
'age' => '25'
];
$rules = [
'username' => 'required|min:5|max:20',
'email' => 'required|email',
'age' => 'required|integer'
];
$result = Validator::validate($data, $rules);
if ($result->isValid()) {
echo "验证通过";
} else {
foreach ($result->errors()->all() as $field => $errors) {
echo "{$field}: " . implode(', ', $errors) . "\n";
}
}
```
### 自定义错误消息
```php
$rules = [
'username' => 'required|min:5,message:用户名至少需要5个字符',
'email' => 'required|email,message:请输入有效的邮箱地址'
];
$result = Validator::validate($data, $rules);
```
### 异常抛出模式
```php
use Awesome\Validation\ValidationException;
try {
Validator::validateOrFail($data, $rules);
echo "验证通过";
} catch (ValidationException $e) {
echo "验证失败: " . $e->getMessage();
print_r($e->toArray());
}
```
### 使用 key() 方法进行灵活验证
使用 `key()` 方法可以更灵活地进行数组验证,避免手写规则字符串的拼写错误,并支持 IDE 自动完成:
```php
$data = [
'username' => 'john_doe',
'email' => 'john@example.com',
'age' => '25'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('username')->required()->string()->minLength(5),
$builder->key('email')->required()->email(),
$builder->key('age')->required()->integer()
];
});
if ($result->isValid()) {
echo "验证通过";
} else {
foreach ($result->errors()->all() as $field => $errors) {
echo "{$field}: " . implode(', ', $errors) . "\n";
}
}
```
#### 嵌套字段验证
```php
$data = [
'user' => [
'profile' => [
'name' => 'John Doe',
'email' => 'john@example.com'
]
]
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('user.profile.name')->required()->string()->minLength(2),
$builder->key('user.profile.email')->required()->email()
];
});
```
#### 自定义错误消息
```php
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('username')->required()->string()->minLength(5, '用户名至少需要5个字符'),
$builder->key('email')->required()->email('请输入有效的邮箱地址')
];
});
```
#### 使用 Builder 的 key() 方法
```php
$builder = Validator::make($data);
$result = $builder->key('username')
->required()
->string()
->minLength(3)
->validate();
```
#### 使用 each() 方法验证数组元素
当某个字段是数组时,可以使用 `each()` 方法验证数组中的每个元素:
```php
$data = [
'emails' => [
'john@example.com',
'jane@example.com',
'invalid-email'
]
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('emails')->each()->email()
];
});
if ($result->isValid()) {
echo "所有邮箱验证通过";
} else {
foreach ($result->errors()->all() as $field => $errors) {
echo "{$field}: " . implode(', ', $errors) . "\n";
}
}
```
#### 混合使用 key() 和 each()
```php
$data = [
'username' => 'john_doe',
'emails' => [
'john@example.com',
'jane@example.com'
],
'ages' => ['25', '30', '35']
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('username')->required()->string()->minLength(5),
$builder->key('emails')->each()->email(),
$builder->key('ages')->each()->integer()
];
});
```
## 验证规则
### 基础规则
| 规则 | 描述 | 示例 |
|------|------|------|
| `required` | 必填字段 | `required` |
| `string` | 字符串类型 | `string` |
| `integer` | 整数类型 | `integer` |
| `numeric` | 数字类型(包括字符串形式的数字) | `numeric` |
| `email` | 邮箱格式 | `email` |
### 长度规则
| 规则 | 描述 | 示例 |
|------|------|------|
| `min:n` | 最小长度 | `min:5` |
| `max:n` | 最大长度 | `max:20` |
### 值规则
| 规则 | 描述 | 示例 |
|------|------|------|
| `in:a,b,c` | 值在指定列表中 | `in:active,inactive,pending` |
| `regex:pattern` | 正则表达式匹配 | `regex:/^[A-Z]/` |
### 自定义规则
```php
$result = Validator::string()
->custom(function($value) {
return preg_match('/[A-Z]/', $value) &&
preg_match('/[0-9]/', $value);
}, '密码必须包含大写字母和数字')
->validate('Password123');
```
## 高级用法
### 嵌套字段验证
```php
$data = [
'user' => [
'profile' => [
'name' => 'John Doe',
'email' => 'john@example.com'
]
]
];
$rules = [
'user.profile.name' => 'required|min:2',
'user.profile.email' => 'required|email'
];
$result = Validator::validate($data, $rules);
```
### 数组项验证
```php
$data = [
['name' => 'John', 'age' => '25'],
['name' => 'Jane', 'age' => '30']
];
$rules = [
'name' => 'required|min:2',
'age' => 'required|integer'
];
$result = Validator::validateNested($data, $rules);
```
### 链式验证
```php
$result = Validator::string()
->minLength(8)
->custom(function($value) {
return preg_match('/[A-Z]/', $value);
}, '必须包含大写字母')
->validate('Password123');
```
## API 参考
### Validator 门面类
#### 静态方法
- `string(?string $message = null): Builder` - 创建字符串验证器
- `integer(?string $message = null): Builder` - 创建整数验证器
- `required(?string $message = null): Builder` - 创建必填验证器
- `email(?string $message = null): Builder` - 创建邮箱验证器
- `array(?string $message = null): Builder` - 创建数组验证器
- `make(array $data): Builder` - 创建验证器构建器
- `validate(array $data, array $rules): ValidationResult` - 验证数组数据
- `validateOrFail(array $data, array $rules): ValidationResult` - 验证并抛出异常
- `validateNested(array $data, array $rules): ValidationResult` - 验证嵌套数组
- `validateKeys(array $data, callable $callback): ValidationResult` - 使用 key() 方法验证数组数据
### ValidationResult 类
#### 方法
- `isValid(): bool` - 检查验证是否通过
- `isInvalid(): bool` - 检查验证是否失败
- `errors(): MessageBag` - 获取错误消息包
- `toArray(): array` - 转换为数组
- `toJson(): string` - 转换为 JSON
### ValidationException 类
#### 方法
- `errors(): MessageBag` - 获取错误消息包
- `toArray(): array` - 转换为数组
- `toJson(): string` - 转换为 JSON
- `fromResult(ValidationResult $result, string $message = ''): self` - 从验证结果创建异常
### EachValidator 类
EachValidator 用于验证数组中的每个元素,提供与 KeyValidator 相同的验证方法。
#### 方法
- `required(?string $message = null): self` - 添加必填验证
- `string(?string $message = null): self` - 添加字符串验证
- `integer(?string $message = null): self` - 添加整数验证
- `email(?string $message = null): self` - 添加邮箱验证
- `minLength(int $min, ?string $message = null): self` - 添加最小长度验证
- `maxLength(int $max, ?string $message = null): self` - 添加最大长度验证
- `in(array $values, ?string $message = null): self` - 添加枚举值验证
- `regex(string $pattern, ?string $message = null): self` - 添加正则验证
- `custom(callable $callback, ?string $message = null): self` - 添加自定义验证
- `validate(): ValidationResult` - 执行验证
## 运行示例
```bash
# 基础验证示例
php examples/basic_validation.php
# 数组验证示例
php examples/array_validation.php
# Key 验证示例
php examples/key_validation.php
# Each 验证示例
php examples/each_validation.php
# Numeric 验证示例
php examples/numeric_validation.php
```
## 运行测试
```bash
# 运行所有测试
./vendor/bin/phpunit
# 运行单元测试
./vendor/bin/phpunit tests/Unit
# 运行功能测试
./vendor/bin/phpunit tests/Feature
```
## 项目结构
```
awesome-validation/
├── src/
│ └── Awesome/
│ └── Validation/
│ ├── Validator.php
│ ├── Builder.php
│ ├── ValidationResult.php
│ ├── ValidationException.php
│ ├── Rules/
│ │ ├── AbstractRule.php
│ │ ├── StringRule.php
│ │ ├── IntegerRule.php
│ │ ├── RequiredRule.php
│ │ ├── EmailRule.php
│ │ ├── MinLengthRule.php
│ │ ├── MaxLengthRule.php
│ │ ├── InRule.php
│ │ ├── RegexRule.php
│ │ └── CustomRule.php
│ └── Messages/
│ └── MessageBag.php
├── tests/
│ ├── Unit/
│ └── Feature/
├── examples/
│ ├── basic_validation.php
│ └── array_validation.php
└── composer.json
```
## 许可证
MIT License
## 贡献
欢迎提交 Issue 和 Pull Request!
RULES.md 0000644 00000007676 15166406615 0006012 0 ustar 00 # Awesome Validation - 验证规则清单
本文档列出了 Awesome Validation 库中所有可用的验证规则。
## 规则分类
### 1. 类型验证规则
| 规则类 | 规则名 | 说明 | 使用示例 |
|--------|--------|------|----------|
| StringRule | string | 验证值是否为字符串 | `$v->string()` |
| IntegerRule | integer | 验证值是否为整数(支持 0x, 0b 等进制) | `$v->integer()` |
| StrictIntegerRule | strictInteger | 严格校验是否为 int 类型 | `$v->strictInteger()` |
| ArrayRule | array | 验证值是否为数组 | `$v->array()` |
| NumericRule | numeric | 验证值是否为数值(包括字符串数字) | `$v->numeric()` |
### 2. 长度/范围验证规则
| 规则类 | 规则名 | 说明 | 使用示例 |
|--------|--------|------|----------|
| MinLengthRule | minLength | 验证字符串/数组长度不小于指定值 | `$v->minLength(5)` |
| MaxLengthRule | maxLength | 验证字符串/数组长度不大于指定值 | `$v->maxLength(20)` |
| ChineseLengthRule | chineseLength | 验证中文字符长度(一个汉字算1个长度) | `$v->chineseLength(2, 10)` |
| MinRule | min | 验证数值最小值 | `$v->min(10, true)` |
| MaxRule | max | 验证数值最大值 | `$v->max(100, true)` |
| BetweenRule | between | 验证数值区间 | `$v->between(1, 10, true, false)` |
### 3. 数学属性验证规则
| 规则类 | 规则名 | 说明 | 使用示例 |
|--------|--------|------|----------|
| EvenRule | even | 验证是否为偶数 | `$v->even()` |
| OddRule | odd | 验证是否为奇数 | `$v->odd()` |
| PositiveRule | positive | 验证是否为正数 | `$v->positive()` |
| NegativeRule | negative | 验证是否为负数 | `$v->negative()` |
| PrimeRule | prime | 验证是否为质数 | `$v->prime()` |
| SafeIntegerRule | safeInteger | 验证是否在安全整数范围内 | `$v->safeInteger()` |
### 4. 格式验证规则
| 规则类 | 规则名 | 说明 | 使用示例 |
|--------|--------|------|----------|
| EmailRule | email | 验证邮箱格式 | `$v->email()` |
| PhoneRule | phone | 验证手机号格式 | `$v->phone()` |
| IpRule | ip | 验证 IP 地址格式 | `$v->ip()` |
| DomainRule | domain | 验证域名格式 | `$v->domain()` |
| RegexRule | regex | 使用正则表达式验证 | `$v->regex('/^[a-z]+$/')` |
### 5. 特殊验证规则
| 规则类 | 规则名 | 说明 | 使用示例 |
|--------|--------|------|----------|
| InRule | in | 验证值在指定范围内 | `$v->in(['active', 'inactive'])` |
| EachRule | each | 验证数组中的每个元素 | `$v->each()->string()` |
| CustomRule | custom | 使用自定义回调验证 | `$v->custom(fn($v) => $v > 0)` |
### 6. 必填验证规则
| 规则类 | 规则名 | 说明 | 使用示例 |
|--------|--------|------|----------|
| RequiredRule | required | 验证字段存在且不为空 | `$v->key('name')->required()` |
| NotEmptyRule | notEmpty | 验证值不为空 | `$v->notEmpty()` |
## 数据预处理 (Transformation)
您可以在验证之前对数据进行转换。转换后的值仅在当前验证流中有效。
```php
$v->key('email')
->transform('trim')
->transform('strtolower')
->email();
```
## 规则组合使用示例
### 基础类型验证
```php
$result = Validator::validateKeys($data, function($v) {
return [
$v->key('username')->string()->minLength(3)->maxLength(20),
$v->key('age')->integer()->min(0)->max(150, true),
$v->key('email')->transform('trim')->strtolower()->email(),
];
});
```
### 数值区间与属性校验
```php
$result = Validator::validateKeys($data, function($v) {
return [
// 分数必须在 [0, 100] 之间
$v->key('score')->numeric()->between(0, 100),
// 必须是正奇数
$v->key('count')->numeric()->positive()->odd(),
// 质数校验
$v->key('id')->numeric()->prime(),
];
});
```
---
**总计**: 26 个验证规则类,覆盖类型、范围、数学属性、格式、特殊和必填六大类别。
composer.json 0000644 00000001356 15166406615 0007305 0 ustar 00 {
"name": "awesome/validation",
"description": "一个友好的 PHP 参数验证库",
"type": "library",
"license": "MIT",
"repositories": [
{
"packagist": false
},
{
"type": "composer",
"url": "https://packagist.i-ktv.com/"
}
],
"require": {
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.0"
},
"autoload": {
"psr-4": {
"Awesome\\Validation\\": "src/Awesome/Validation/"
}
},
"autoload-dev": {
"psr-4": {
"Awesome\\Validation\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"analyze": "phpstan analyse src"
}
} composer.lock 0000644 00000157105 15166406615 0007270 0 ustar 00 {
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "71c15bb0aa4a339d3ffa937c70794a60",
"packages": [],
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.12.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/myclabs/deep-copy/myclabs-deep-copy-123267b2c49fbf30d78a7b2d333f6be754b94845-zip-d3d371.tar",
"reference": "123267b2c49fbf30d78a7b2d333f6be754b94845",
"shasum": "ca6d35760f2c915885e4aad2bc6f77fbfdd0515f"
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
"phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
"autoload": {
"files": [
"src/DeepCopy/deep_copy.php"
],
"psr-4": {
"DeepCopy\\": "src/DeepCopy/"
}
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"MIT"
],
"description": "Create deep copies (clones) of your objects",
"keywords": [
"clone",
"copy",
"duplicate",
"object",
"object graph"
],
"funding": [
{
"url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
"type": "tidelift"
}
],
"time": "2024-11-08T17:47:46+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.4.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/nikic/php-parser/nikic-php-parser-447a020a1f875a434d62f2a401f53b82a396e494-zip-35835f.tar",
"reference": "447a020a1f875a434d62f2a401f53b82a396e494",
"shasum": "b528f9880dc133cb5ddf725e07f1f9cc2d18bdc5"
},
"require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-tokenizer": "*",
"php": ">=7.4"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0-dev"
}
},
"autoload": {
"psr-4": {
"PhpParser\\": "lib/PhpParser"
}
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov"
}
],
"description": "A PHP parser written in PHP",
"keywords": [
"parser",
"php"
],
"time": "2024-12-30T11:07:19+00:00"
},
{
"name": "phar-io/manifest",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
"reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phar-io/manifest/phar-io-manifest-54750ef60c58e43759730615a392c31c80e23176-zip-828055.tar",
"reference": "54750ef60c58e43759730615a392c31c80e23176",
"shasum": "ff4b490eac87839069252112b75ccf8e8ae2f60f"
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-phar": "*",
"ext-xmlwriter": "*",
"phar-io/version": "^3.0.1",
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"funding": [
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2024-03-03T12:33:53+00:00"
},
{
"name": "phar-io/version",
"version": "3.2.1",
"source": {
"type": "git",
"url": "https://github.com/phar-io/version.git",
"reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phar-io/version/phar-io-version-4f7fd7836c6f332bb2933569e566a0d6c4cbed74-zip-44d1a0.tar",
"reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
"shasum": "0704589b2e31b1fd3ac07305c84b97ba66d181ec"
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Library for handling version information and constraints",
"time": "2022-02-21T01:04:05+00:00"
},
{
"name": "phpstan/phpstan",
"version": "1.12.16",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "e0bb5cb78545aae631220735aa706eac633a6be9"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phpstan/phpstan/phpstan-phpstan-e0bb5cb78545aae631220735aa706eac633a6be9-zip-c6bc53.tar",
"reference": "e0bb5cb78545aae631220735aa706eac633a6be9",
"shasum": "83f9bcf6d04ab6e33888926029b5bdec3d1feba4"
},
"require": {
"php": "^7.2|^8.0"
},
"conflict": {
"phpstan/phpstan-shim": "*"
},
"bin": [
"phpstan",
"phpstan.phar"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"MIT"
],
"description": "PHPStan - PHP Static Analysis Tool",
"keywords": [
"dev",
"static analysis"
],
"funding": [
{
"url": "https://github.com/ondrejmirtes",
"type": "github"
},
{
"url": "https://github.com/phpstan",
"type": "github"
}
],
"time": "2025-01-21T14:50:05+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "10.1.16",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "7e308268858ed6baedc8704a304727d20bc07c77"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phpunit/php-code-coverage/phpunit-php-code-coverage-7e308268858ed6baedc8704a304727d20bc07c77-zip-385d17.tar",
"reference": "7e308268858ed6baedc8704a304727d20bc07c77",
"shasum": "082f909d83bb1436d143827bc343b7f7bd7f5069"
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.19.1 || ^5.1.0",
"php": ">=8.1",
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-text-template": "^3.0.1",
"sebastian/code-unit-reverse-lookup": "^3.0.0",
"sebastian/complexity": "^3.2.0",
"sebastian/environment": "^6.1.0",
"sebastian/lines-of-code": "^2.0.2",
"sebastian/version": "^4.0.1",
"theseer/tokenizer": "^1.2.3"
},
"require-dev": {
"phpunit/phpunit": "^10.1"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "10.1.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2024-08-22T04:31:57+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "4.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phpunit/php-file-iterator/phpunit-php-file-iterator-a95037b6d9e608ba092da1b23931e537cadc3c3c-zip-bc4229.tar",
"reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
"shasum": "86767402534f67c71da6a94f44b8345dadcd9137"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "4.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-08-31T06:24:48+00:00"
},
{
"name": "phpunit/php-invoker",
"version": "4.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-invoker.git",
"reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phpunit/php-invoker/phpunit-php-invoker-f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7-zip-4167d0.tar",
"reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
"shasum": "bd0e4e5ac7ab471bd0837feca473eeb35f55705d"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"ext-pcntl": "*",
"phpunit/phpunit": "^10.0"
},
"suggest": {
"ext-pcntl": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "4.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Invoke callables with a timeout",
"homepage": "https://github.com/sebastianbergmann/php-invoker/",
"keywords": [
"process"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T06:56:09+00:00"
},
{
"name": "phpunit/php-text-template",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phpunit/php-text-template/phpunit-php-text-template-0c7b06ff49e3d5072f057eb1fa59258bf287a748-zip-cc03ba.tar",
"reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
"shasum": "fae71c5616d45433f50e0b8ea19959f6d80545f7"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-08-31T14:07:24+00:00"
},
{
"name": "phpunit/php-timer",
"version": "6.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phpunit/php-timer/phpunit-php-timer-e2a2d67966e740530f4a3343fe2e030ffdc1161d-zip-eb6b83.tar",
"reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
"shasum": "35587490a5453dcc78af419e3c72fd7735db046f"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T06:57:52+00:00"
},
{
"name": "phpunit/phpunit",
"version": "10.5.44",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/phpunit/phpunit/phpunit-phpunit-1381c62769be4bb88fa4c5aec1366c7c66ca4f36-zip-550947.tar",
"reference": "1381c62769be4bb88fa4c5aec1366c7c66ca4f36",
"shasum": "e8654d8a9671284872dd061b4934a6d17a4f0b30"
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.12.1",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.1",
"phpunit/php-code-coverage": "^10.1.16",
"phpunit/php-file-iterator": "^4.1.0",
"phpunit/php-invoker": "^4.0.0",
"phpunit/php-text-template": "^3.0.1",
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.3",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.2",
"sebastian/global-state": "^6.0.2",
"sebastian/object-enumerator": "^5.0.0",
"sebastian/recursion-context": "^5.0.0",
"sebastian/type": "^4.0.0",
"sebastian/version": "^4.0.1"
},
"suggest": {
"ext-soap": "To be able to generate mocks based on WSDL files"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "10.5-dev"
}
},
"autoload": {
"files": [
"src/Framework/Assert/Functions.php"
],
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"funding": [
{
"url": "https://phpunit.de/sponsors.html",
"type": "custom"
},
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift"
}
],
"time": "2025-01-31T07:00:38+00:00"
},
{
"name": "sebastian/cli-parser",
"version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
"reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/cli-parser/sebastian-cli-parser-c34583b87e7b7a8055bf6c450c2c77ce32a24084-zip-580728.tar",
"reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
"shasum": "cf8e8cd3ca813a74689e5f81423f1e91fd4d10bc"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library for parsing CLI options",
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2024-03-02T07:12:49+00:00"
},
{
"name": "sebastian/code-unit",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit.git",
"reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/code-unit/sebastian-code-unit-a81fee9eef0b7a76af11d121767abc44c104e503-zip-86e01f.tar",
"reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
"shasum": "b45db454110fc044e60fa62398453a2ddbeba598"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Collection of value objects that represent the PHP code units",
"homepage": "https://github.com/sebastianbergmann/code-unit",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T06:58:43+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
"reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/code-unit-reverse-lookup/sebastian-code-unit-reverse-lookup-5e3a687f7d8ae33fb362c5c0743794bbb2420a1d-zip-6c698c.tar",
"reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
"shasum": "0bad1d5f9453fd2629bd23111f9ea89537fdc8a8"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Looks up which function or method a line of code belongs to",
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T06:59:15+00:00"
},
{
"name": "sebastian/comparator",
"version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/comparator/sebastian-comparator-a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e-zip-a1c96f.tar",
"reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e",
"shasum": "decb876c06efa7f3bd8bfa78e7ee4cf081d190d5"
},
"require": {
"ext-dom": "*",
"ext-mbstring": "*",
"php": ">=8.1",
"sebastian/diff": "^5.0",
"sebastian/exporter": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2024-10-18T14:56:07+00:00"
},
{
"name": "sebastian/complexity",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "68ff824baeae169ec9f2137158ee529584553799"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/complexity/sebastian-complexity-68ff824baeae169ec9f2137158ee529584553799-zip-99104e.tar",
"reference": "68ff824baeae169ec9f2137158ee529584553799",
"shasum": "3503ca493accd6d7ca1a0c497e24e8ea01728978"
},
"require": {
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.2-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library for calculating the complexity of PHP code units",
"homepage": "https://github.com/sebastianbergmann/complexity",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-12-21T08:37:17+00:00"
},
{
"name": "sebastian/diff",
"version": "5.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/diff/sebastian-diff-c41e007b4b62af48218231d6c2275e4c9b975b2e-zip-f415cf.tar",
"reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
"shasum": "80cbc53d49e13197d34210932c2cf055e857adc9"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"symfony/process": "^6.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff",
"udiff",
"unidiff",
"unified diff"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2024-03-02T07:15:17+00:00"
},
{
"name": "sebastian/environment",
"version": "6.1.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/environment/sebastian-environment-8074dbcd93529b357029f5cc5058fd3e43666984-zip-5b3490.tar",
"reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
"shasum": "301eab31fd519db24aa67e28247eb6d3f7db66ed"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"suggest": {
"ext-posix": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "https://github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2024-03-23T08:47:14+00:00"
},
{
"name": "sebastian/exporter",
"version": "5.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "955288482d97c19a372d3f31006ab3f37da47adf"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/exporter/sebastian-exporter-955288482d97c19a372d3f31006ab3f37da47adf-zip-f40d11.tar",
"reference": "955288482d97c19a372d3f31006ab3f37da47adf",
"shasum": "547ec94a495e36fe0c8082418bf362c54365df27"
},
"require": {
"ext-mbstring": "*",
"php": ">=8.1",
"sebastian/recursion-context": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.1-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "https://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2024-03-02T07:17:12+00:00"
},
{
"name": "sebastian/global-state",
"version": "6.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/global-state/sebastian-global-state-987bafff24ecc4c9ac418cab1145b96dd6e9cbd9-zip-a34a10.tar",
"reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
"shasum": "2001156a62e2843908056e2f097b97fdaa1c26df"
},
"require": {
"php": ">=8.1",
"sebastian/object-reflector": "^3.0",
"sebastian/recursion-context": "^5.0"
},
"require-dev": {
"ext-dom": "*",
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "https://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2024-03-02T07:19:19+00:00"
},
{
"name": "sebastian/lines-of-code",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/lines-of-code/sebastian-lines-of-code-856e7f6a75a84e339195d48c556f23be2ebf75d0-zip-f0fa48.tar",
"reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
"shasum": "02dd5bf8a44f0d49940cd45c958d1db685b81ccd"
},
"require": {
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library for counting the lines of code in PHP source code",
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-12-21T08:38:20+00:00"
},
{
"name": "sebastian/object-enumerator",
"version": "5.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
"reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/object-enumerator/sebastian-object-enumerator-202d0e344a580d7f7d04b3fafce6933e59dae906-zip-1726e9.tar",
"reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
"shasum": "2ba9f0673f17360388452106af69884db90c0500"
},
"require": {
"php": ">=8.1",
"sebastian/object-reflector": "^3.0",
"sebastian/recursion-context": "^5.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Traverses array structures and object graphs to enumerate all referenced objects",
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T07:08:32+00:00"
},
{
"name": "sebastian/object-reflector",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-reflector.git",
"reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/object-reflector/sebastian-object-reflector-24ed13d98130f0e7122df55d06c5c4942a577957-zip-38f027.tar",
"reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
"shasum": "6ac60650e1e10126838e8c0ee4e3d6073ed17022"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Allows reflection of object attributes, including inherited and non-public ones",
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T07:06:18+00:00"
},
{
"name": "sebastian/recursion-context",
"version": "5.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "05909fb5bc7df4c52992396d0116aed689f93712"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/recursion-context/sebastian-recursion-context-05909fb5bc7df4c52992396d0116aed689f93712-zip-0d1366.tar",
"reference": "05909fb5bc7df4c52992396d0116aed689f93712",
"shasum": "7b52d9210ca5793a41ff9440aa39c24c4d130901"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T07:05:40+00:00"
},
{
"name": "sebastian/type",
"version": "4.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/type/sebastian-type-462699a16464c3944eefc02ebdd77882bd3925bf-zip-c08c28.tar",
"reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
"shasum": "676c0745dd26083d1afdea9e20b9b185e5106e81"
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "4.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Collection of value objects that represent the types of the PHP type system",
"homepage": "https://github.com/sebastianbergmann/type",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-03T07:10:45+00:00"
},
{
"name": "sebastian/version",
"version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
"reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/sebastian/version/sebastian-version-c51fa83a5d8f43f1402e3f32a005e6262244ef17-zip-d9a2c6.tar",
"reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
"shasum": "17cee86f8d711d798439dc165735a4d0ab17cb3e"
},
"require": {
"php": ">=8.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "4.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
}
],
"time": "2023-02-07T11:34:05+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
},
"dist": {
"type": "tar",
"url": "https://packagist.i-ktv.com//dist/theseer/tokenizer/theseer-tokenizer-737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2-zip-752359.tar",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
"shasum": "1d8d5570aaf949ffb7d8b801abc092c73bef915d"
},
"require": {
"ext-dom": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": "^7.2 || ^8.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.int.fun360.cn/composer/download",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
}
],
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"funding": [
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2024-03-03T12:36:25+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^8.1"
},
"platform-dev": [],
"plugin-api-version": "1.1.0"
}
docs/plans/2026-04-10-awesome-validation-design.md 0000644 00000023041 15166406615 0015232 0 ustar 00 # Awesome Validation 设计文档
## 项目概述
Awesome Validation 是一个友好的 PHP 参数验证库,基于 PHP 8.1+ 语法开发,遵循 PSR-4 规范。该库解决了现有验证库(如 Respect Validation)错误信息提示不友好的问题,提供了灵活的消息定制和多种验证方式。
## 核心需求
1. **友好的错误消息**:每个验证规则有默认中文消息模板,支持自定义覆盖
2. **灵活的消息设置**:支持单独设置、批量设置、使用默认消息三种方式
3. **多种验证方式**:支持链式调用、规则数组、Attribute 三种验证方式
4. **多格式输出**:支持字符串、数组、JSON 三种错误输出格式
5. **嵌套验证**:支持点号路径访问(如 `user.profile.name`)
6. **丰富的上下文信息**:错误消息包含字段名、期望值等上下文信息
## 架构设计
### 目录结构
```
awesome-validation/
├── src/
│ └── Awesome/
│ └── Validation/
│ ├── Validator.php # 门面类
│ ├── Builder.php # 验证器构建器
│ ├── ValidationResult.php # 验证结果
│ ├── Rules/ # 验证规则
│ │ ├── AbstractRule.php # 规则基类
│ │ ├── StringRule.php
│ │ ├── IntegerRule.php
│ │ ├── EmailRule.php
│ │ ├── RequiredRule.php
│ │ ├── MinLengthRule.php
│ │ ├── MaxLengthRule.php
│ │ ├── MinRule.php
│ │ ├── MaxRule.php
│ │ ├── InRule.php
│ │ ├── RegexRule.php
│ │ └── CustomRule.php
│ ├── Exceptions/ # 异常类
│ │ └── ValidationException.php
│ ├── Messages/ # 消息处理
│ │ ├── MessageBag.php
│ │ └── MessageTemplate.php
│ └── Support/ # 辅助类
│ ├── AttributeValidator.php
│ └── RuleParser.php
├── tests/
│ ├── Unit/
│ │ ├── Rules/
│ │ └── Messages/
│ └── Feature/
│ └── ValidatorTest.php
├── composer.json
├── phpunit.xml
└── README.md
```
### 核心组件
#### 1. Validator 门面类
提供静态方法作为验证库的入口点。
```php
namespace Awesome\Validation;
class Validator
{
public static function string(?string $message = null): Builder
public static function integer(?string $message = null): Builder
public static function email(?string $message = null): Builder
public static function required(?string $message = null): Builder
public static function array(?string $message = null): Builder
public static function validate(array $data, array $rules): ValidationResult
public static function make(array $data): Builder
}
```
#### 2. Builder 验证器构建器
实现链式调用,管理验证规则和消息。
```php
namespace Awesome\Validation;
class Builder
{
private array $rules = [];
private array $messages = [];
private ?string $field = null;
public function __construct(private mixed $value = null) {}
public function string(?string $message = null): self
public function integer(?string $message = null): self
public function minLength(int $min, ?string $message = null): self
public function maxLength(int $max, ?string $message = null): self
public function email(?string $message = null): self
public function required(?string $message = null): self
public function in(array $values, ?string $message = null): self
public function regex(string $pattern, ?string $message = null): self
public function custom(callable $callback, ?string $message = null): self
public function message(string $message): self
public function messages(array $messages): self
public function validate(mixed $value = null): ValidationResult
public function check(mixed $value = null): bool
}
```
#### 3. ValidationResult 验证结果
封装验证结果,提供多种输出格式。
```php
namespace Awesome\Validation;
class ValidationResult
{
private bool $valid;
private MessageBag $errors;
public function isValid(): bool
public function isInvalid(): bool
public function errors(): MessageBag
public function firstError(): ?string
public function toArray(): array
public function toJson(): string
public function __toString(): string
}
```
#### 4. MessageBag 消息收集器
收集和管理验证错误消息。
```php
namespace Awesome\Validation;
class MessageBag implements \JsonSerializable
{
private array $messages = [];
public function add(string $field, string $message): void
public function get(string $field): array
public function all(): array
public function first(string $field): ?string
public function has(string $field): bool
public function isEmpty(): bool
public function toArray(): array
public function toJson(): string
public function __toString(): string
}
```
#### 5. AbstractRule 规则基类
所有验证规则的基类,定义规则接口。
```php
namespace Awesome\Validation\Rules;
abstract class AbstractRule
{
protected string $name;
protected ?string $message = null;
protected array $parameters = [];
public function __construct(?string $message = null)
{
$this->message = $message;
}
abstract public function validate(mixed $value): bool;
public function getName(): string
public function setMessage(string $message): self
public function getMessage(): string
public function getParameters(): array
public function getDefaultMessage(): string
public function getFormattedMessage(array $context = []): string
}
```
#### 6. ValidationException 异常类
验证失败时抛出的异常。
```php
namespace Awesome\Validation\Exceptions;
class ValidationException extends \InvalidArgumentException
{
private MessageBag $errors;
private string $field;
public function __construct(string $message, MessageBag $errors, string $field = '')
{
parent::__construct($message);
$this->errors = $errors;
$this->field = $field;
}
public function getErrors(): MessageBag
public function getField(): string
public function toArray(): array
public function toJson(): string
}
```
## 使用示例
### 链式调用
```php
use Awesome\Validation\Validator;
try {
Validator::string()->message('必须是字符串')
->minLength(5)->message('至少5个字符')
->maxLength(20)->message('最多20个字符')
->validate($username);
} catch (ValidationException $e) {
echo $e->getMessage();
}
$result = Validator::email()->validate($email);
if ($result->isInvalid()) {
echo $result->firstError();
}
```
### 规则数组验证
```php
use Awesome\Validation\Validator;
$result = Validator::validate($data, [
'username' => 'required|string|min:5|max:20',
'email' => 'required|email',
'age' => 'integer|min:18|max:120',
'profile.bio' => 'string|max:500'
]);
if ($result->isInvalid()) {
print_r($result->errors()->toArray());
}
```
### Attribute 验证
```php
use Awesome\Validation\Support\Validate;
class UserRequest
{
#[Validate('required|string|min:5|max:20', message: '用户名必填')]
public string $username;
#[Validate('required|email', message: '邮箱格式错误')]
public string $email;
#[Validate('integer|min:18', message: '年龄必须大于18岁')]
public int $age;
}
$validator = new AttributeValidator();
$validator->validate($userRequest);
```
## 数据流设计
### 验证流程
```
用户输入
↓
Validator::validate()
↓
Builder (构建验证链)
↓
Rule 验证器 (逐个验证)
↓
MessageBag (收集错误)
↓
ValidationResult (返回结果)
↓
用户处理结果
```
### 错误处理流程
```
验证失败
↓
Rule 抛出异常
↓
Builder 捕获并收集
↓
MessageBag 存储错误
↓
ValidationException (如果需要)
↓
用户获取错误信息
```
## 技术规范
### PHP 版本
- 最低要求:PHP 8.1+
- 推荐版本:PHP 8.2+
### PSR 规范
- PSR-4:自动加载规范
- PSR-12:编码风格规范
### 依赖
- 无外部依赖(纯 PHP 实现)
- 开发依赖:PHPUnit、PHPStan
### 性能考虑
- 规则对象可缓存复用
- 适合常驻内存环境(如 Webman)
- 避免不必要的对象创建
## 扩展性设计
### 添加新规则
1. 继承 `AbstractRule` 类
2. 实现 `validate()` 方法
3. 定义 `getDefaultMessage()` 方法
4. 在 `Builder` 中添加对应方法
### 自定义消息模板
支持使用占位符动态生成消息:
- `{field}`:字段名
- `{value}`:当前值
- `{min}`、`{max}`:规则参数
### 国际化支持
预留接口,未来可扩展多语言支持。
## 测试策略
### 单元测试
- 每个规则类的独立测试
- MessageBag 功能测试
- MessageTemplate 测试
### 集成测试
- 链式调用测试
- 规则数组验证测试
- Attribute 验证测试
### 边界测试
- 空值处理
- 类型转换
- 嵌套验证
## 后续优化
1. 支持更多验证规则(日期、URL、文件等)
2. 添加数据库验证规则(唯一性、存在性)
3. 性能优化和基准测试
4. 完善文档和示例
5. 添加更多语言支持
docs/plans/2026-04-10-awesome-validation-implementation.md 0000644 00000131447 15166406615 0017020 0 ustar 00 # Awesome Validation Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 构建一个友好的 PHP 参数验证库,支持多种验证方式、灵活的错误消息定制和多格式输出
**Architecture:** 基于规则引擎的验证器架构,每个验证规则是独立的类,通过 Builder 实现链式调用,MessageBag 管理错误消息,支持链式调用、规则数组和 Attribute 三种验证方式
**Tech Stack:** PHP 8.1+, PHPUnit 10.0, PSR-4, PSR-12
---
## Task 1: 初始化项目结构
**Files:**
- Create: `composer.json`
- Create: `phpunit.xml`
- Create: `README.md`
- Create: `src/Awesome/Validation/`
- Create: `tests/`
**Step 1: 创建 composer.json**
```json
{
"name": "awesome/validation",
"description": "一个友好的 PHP 参数验证库",
"type": "library",
"license": "MIT",
"require": {
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.0"
},
"autoload": {
"psr-4": {
"Awesome\\Validation\\": "src/Awesome/Validation/"
}
},
"autoload-dev": {
"psr-4": {
"Awesome\\Validation\\Tests\\": "tests/"
}
},
"scripts": {
"test": "phpunit",
"analyze": "phpstan analyse src"
}
}
```
**Step 2: 创建 phpunit.xml**
```xml
tests
src
```
**Step 3: 创建 README.md**
```markdown
# Awesome Validation
一个友好的 PHP 参数验证库
## 安装
```bash
composer require awesome/validation
```
## 使用示例
```php
use Awesome\Validation\Validator;
Validator::string()->minLength(5)->validate($username);
```
## 运行测试
```bash
composer test
```
```
**Step 4: 创建目录结构**
Run: `mkdir -p src/Awesome/Validation/{Rules,Exceptions,Messages,Support} tests/{Unit,Feature}`
**Step 5: 初始化 Composer**
Run: `composer install`
**Step 6: Commit**
```bash
git add composer.json phpunit.xml README.md
git commit -m "feat: initialize project structure"
```
---
## Task 2: 实现 MessageBag 消息收集器
**Files:**
- Create: `src/Awesome/Validation/Messages/MessageBag.php`
- Create: `tests/Unit/Messages/MessageBagTest.php`
**Step 1: 编写 MessageBag 测试**
```php
add('username', '用户名必填');
$this->assertEquals(['用户名必填'], $bag->get('username'));
}
public function testGetAllMessages()
{
$bag = new MessageBag();
$bag->add('username', '用户名必填');
$bag->add('email', '邮箱格式错误');
$expected = [
'username' => ['用户名必填'],
'email' => ['邮箱格式错误']
];
$this->assertEquals($expected, $bag->all());
}
public function testFirstMessage()
{
$bag = new MessageBag();
$bag->add('username', '用户名必填');
$bag->add('username', '用户名太短');
$this->assertEquals('用户名必填', $bag->first('username'));
}
public function testHasField()
{
$bag = new MessageBag();
$bag->add('username', '用户名必填');
$this->assertTrue($bag->has('username'));
$this->assertFalse($bag->has('email'));
}
public function testIsEmpty()
{
$bag = new MessageBag();
$this->assertTrue($bag->isEmpty());
$bag->add('username', '用户名必填');
$this->assertFalse($bag->isEmpty());
}
public function testToArray()
{
$bag = new MessageBag();
$bag->add('username', '用户名必填');
$this->assertEquals(['username' => ['用户名必填']], $bag->toArray());
}
public function testToJson()
{
$bag = new MessageBag();
$bag->add('username', '用户名必填');
$expected = json_encode(['username' => ['用户名必填']]);
$this->assertEquals($expected, $bag->toJson());
}
public function testToString()
{
$bag = new MessageBag();
$bag->add('username', '用户名必填');
$this->assertEquals('用户名必填', (string)$bag);
}
}
```
**Step 2: 运行测试验证失败**
Run: `composer test`
Expected: FAIL with "Class Awesome\Validation\Messages\MessageBag not found"
**Step 3: 实现 MessageBag 类**
```php
messages[$field])) {
$this->messages[$field] = [];
}
$this->messages[$field][] = $message;
}
public function get(string $field): array
{
return $this->messages[$field] ?? [];
}
public function all(): array
{
return $this->messages;
}
public function first(string $field): ?string
{
$messages = $this->get($field);
return $messages[0] ?? null;
}
public function has(string $field): bool
{
return isset($this->messages[$field]) && !empty($this->messages[$field]);
}
public function isEmpty(): bool
{
return empty($this->messages);
}
public function toArray(): array
{
return $this->messages;
}
public function toJson(): string
{
return json_encode($this->messages, JSON_UNESCAPED_UNICODE);
}
public function jsonSerialize(): array
{
return $this->messages;
}
public function __toString(): string
{
$messages = [];
foreach ($this->messages as $field => $fieldMessages) {
foreach ($fieldMessages as $message) {
$messages[] = $message;
}
}
return implode("\n", $messages);
}
}
```
**Step 4: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/Awesome/Validation/Messages/MessageBag.php tests/Unit/Messages/MessageBagTest.php
git commit -m "feat: implement MessageBag message collector"
```
---
## Task 3: 实现 AbstractRule 规则基类
**Files:**
- Create: `src/Awesome/Validation/Rules/AbstractRule.php`
- Create: `tests/Unit/Rules/AbstractRuleTest.php`
**Step 1: 编写 AbstractRule 测试**
```php
assertEquals('test', $rule->getName());
}
public function testSetAndGetMessage()
{
$rule = new TestRule('自定义消息');
$this->assertEquals('自定义消息', $rule->getMessage());
$rule->setMessage('新消息');
$this->assertEquals('新消息', $rule->getMessage());
}
public function testGetParameters()
{
$rule = new TestRuleWithParams(5, 10);
$this->assertEquals(['min' => 5, 'max' => 10], $rule->getParameters());
}
public function testGetDefaultMessage()
{
$rule = new TestRule();
$this->assertEquals('默认消息', $rule->getDefaultMessage());
}
public function testGetFormattedMessage()
{
$rule = new TestRuleWithParams(5, 10);
$message = $rule->getFormattedMessage(['field' => 'username']);
$this->assertStringContainsString('username', $message);
$this->assertStringContainsString('5', $message);
$this->assertStringContainsString('10', $message);
}
}
class TestRule extends AbstractRule
{
protected string $name = 'test';
public function validate(mixed $value): bool
{
return true;
}
public function getDefaultMessage(): string
{
return '默认消息';
}
}
class TestRuleWithParams extends AbstractRule
{
protected string $name = 'test';
public function __construct(int $min, int $max, ?string $message = null)
{
parent::__construct($message);
$this->parameters['min'] = $min;
$this->parameters['max'] = $max;
}
public function validate(mixed $value): bool
{
return true;
}
public function getDefaultMessage(): string
{
return '{field} 必须在 {min} 到 {max} 之间';
}
}
```
**Step 2: 运行测试验证失败**
Run: `composer test`
Expected: FAIL with "Class Awesome\Validation\Rules\AbstractRule not found"
**Step 3: 实现 AbstractRule 类**
```php
message = $message;
}
abstract public function validate(mixed $value): bool;
public function getName(): string
{
return $this->name;
}
public function setMessage(string $message): self
{
$this->message = $message;
return $this;
}
public function getMessage(): string
{
return $this->message ?? $this->getDefaultMessage();
}
public function getParameters(): array
{
return $this->parameters;
}
abstract public function getDefaultMessage(): string;
public function getFormattedMessage(array $context = []): string
{
$message = $this->getMessage();
$replacements = array_merge($this->parameters, $context);
foreach ($replacements as $key => $value) {
$message = str_replace('{' . $key . '}', (string)$value, $message);
}
return $message;
}
}
```
**Step 4: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/Awesome/Validation/Rules/AbstractRule.php tests/Unit/Rules/AbstractRuleTest.php
git commit -m "feat: implement AbstractRule base class"
```
---
## Task 4: 实现基础验证规则
**Files:**
- Create: `src/Awesome/Validation/Rules/StringRule.php`
- Create: `src/Awesome/Validation/Rules/IntegerRule.php`
- Create: `src/Awesome/Validation/Rules/RequiredRule.php`
- Create: `src/Awesome/Validation/Rules/EmailRule.php`
- Create: `tests/Unit/Rules/StringRuleTest.php`
- Create: `tests/Unit/Rules/IntegerRuleTest.php`
- Create: `tests/Unit/Rules/RequiredRuleTest.php`
- Create: `tests/Unit/Rules/EmailRuleTest.php`
**Step 1: 实现 StringRule**
```php
assertTrue($rule->validate('hello'));
}
public function testInvalidString()
{
$rule = new StringRule();
$this->assertFalse($rule->validate(123));
$this->assertFalse($rule->validate(null));
$this->assertFalse($rule->validate([]));
}
public function testCustomMessage()
{
$rule = new StringRule('必须是字符串');
$this->assertEquals('必须是字符串', $rule->getMessage());
}
public function testDefaultMessage()
{
$rule = new StringRule();
$this->assertEquals('{field} 必须是字符串', $rule->getDefaultMessage());
}
}
```
**Step 6: 编写 IntegerRule 测试**
```php
assertTrue($rule->validate(123));
$this->assertTrue($rule->validate(0));
$this->assertTrue($rule->validate(-456));
}
public function testInvalidInteger()
{
$rule = new IntegerRule();
$this->assertFalse($rule->validate('123'));
$this->assertFalse($rule->validate(12.34));
$this->assertFalse($rule->validate(null));
}
}
```
**Step 7: 编写 RequiredRule 测试**
```php
assertTrue($rule->validate('hello'));
$this->assertTrue($rule->validate(123));
$this->assertTrue($rule->validate([1, 2, 3]));
}
public function testInvalidRequired()
{
$rule = new RequiredRule();
$this->assertFalse($rule->validate(null));
$this->assertFalse($rule->validate(''));
$this->assertFalse($rule->validate(' '));
$this->assertFalse($rule->validate([]));
}
}
```
**Step 8: 编写 EmailRule 测试**
```php
assertTrue($rule->validate('test@example.com'));
$this->assertTrue($rule->validate('user.name+tag@domain.co.uk'));
}
public function testInvalidEmail()
{
$rule = new EmailRule();
$this->assertFalse($rule->validate('invalid'));
$this->assertFalse($rule->validate('invalid@'));
$this->assertFalse($rule->validate('@example.com'));
$this->assertFalse($rule->validate(123));
}
}
```
**Step 9: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 10: Commit**
```bash
git add src/Awesome/Validation/Rules/*.php tests/Unit/Rules/*.php
git commit -m "feat: implement basic validation rules"
```
---
## Task 5: 实现 ValidationResult 验证结果类
**Files:**
- Create: `src/Awesome/Validation/ValidationResult.php`
- Create: `tests/Unit/ValidationResultTest.php`
**Step 1: 编写 ValidationResult 测试**
```php
assertTrue($result->isValid());
$this->assertFalse($result->isInvalid());
}
public function testInvalidResult()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$result = new ValidationResult(false, $errors);
$this->assertFalse($result->isValid());
$this->assertTrue($result->isInvalid());
}
public function testErrors()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$result = new ValidationResult(false, $errors);
$this->assertEquals($errors, $result->errors());
}
public function testFirstError()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$errors->add('email', '邮箱格式错误');
$result = new ValidationResult(false, $errors);
$this->assertEquals('用户名必填', $result->firstError());
}
public function testToArray()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$result = new ValidationResult(false, $errors);
$this->assertEquals(['username' => ['用户名必填']], $result->toArray());
}
public function testToJson()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$result = new ValidationResult(false, $errors);
$expected = json_encode(['username' => ['用户名必填']]);
$this->assertEquals($expected, $result->toJson());
}
public function testToString()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$result = new ValidationResult(false, $errors);
$this->assertEquals('用户名必填', (string)$result);
}
}
```
**Step 2: 运行测试验证失败**
Run: `composer test`
Expected: FAIL with "Class Awesome\Validation\ValidationResult not found"
**Step 3: 实现 ValidationResult 类**
```php
valid = $valid;
$this->errors = $errors;
}
public function isValid(): bool
{
return $this->valid;
}
public function isInvalid(): bool
{
return !$this->valid;
}
public function errors(): MessageBag
{
return $this->errors;
}
public function firstError(): ?string
{
$all = $this->errors->all();
if (empty($all)) {
return null;
}
$firstField = array_key_first($all);
return $this->errors->first($firstField);
}
public function toArray(): array
{
return $this->errors->toArray();
}
public function toJson(): string
{
return $this->errors->toJson();
}
public function __toString(): string
{
return (string)$this->errors;
}
}
```
**Step 4: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/Awesome/Validation/ValidationResult.php tests/Unit/ValidationResultTest.php
git commit -m "feat: implement ValidationResult class"
```
---
## Task 6: 实现 Builder 验证器构建器
**Files:**
- Create: `src/Awesome/Validation/Builder.php`
- Create: `tests/Unit/BuilderTest.php`
**Step 1: 编写 Builder 测试**
```php
string();
$result = $builder->validate('hello');
$this->assertTrue($result->isValid());
}
public function testStringRuleWithInvalidValue()
{
$builder = new Builder();
$builder->string();
$result = $builder->validate(123);
$this->assertTrue($result->isInvalid());
}
public function testChainedRules()
{
$builder = new Builder();
$builder->string()->minLength(5)->maxLength(10);
$result = $builder->validate('hello');
$this->assertTrue($result->isValid());
}
public function testChainedRulesWithInvalidValue()
{
$builder = new Builder();
$builder->string()->minLength(5);
$result = $builder->validate('hi');
$this->assertTrue($result->isInvalid());
}
public function testCustomMessage()
{
$builder = new Builder();
$builder->string('必须是字符串');
$result = $builder->validate(123);
$this->assertEquals('必须是字符串', $result->firstError());
}
public function testMessagesArray()
{
$builder = new Builder();
$builder->string()->minLength(5)->messages([
'string' => '必须是字符串',
'minLength' => '至少5个字符'
]);
$result = $builder->validate(123);
$this->assertEquals('必须是字符串', $result->firstError());
}
public function testCheckMethod()
{
$builder = new Builder();
$builder->string();
$this->assertTrue($builder->check('hello'));
$this->assertFalse($builder->check(123));
}
}
```
**Step 2: 运行测试验证失败**
Run: `composer test`
Expected: FAIL with "Class Awesome\Validation\Builder not found"
**Step 3: 实现 Builder 类**
```php
addRule($rule);
return $this;
}
public function integer(?string $message = null): self
{
$rule = new IntegerRule($message);
$this->addRule($rule);
return $this;
}
public function required(?string $message = null): self
{
$rule = new RequiredRule($message);
$this->addRule($rule);
return $this;
}
public function email(?string $message = null): self
{
$rule = new EmailRule($message);
$this->addRule($rule);
return $this;
}
public function minLength(int $min, ?string $message = null): self
{
$rule = new MinLengthRule($min, $message);
$this->addRule($rule);
return $this;
}
public function maxLength(int $max, ?string $message = null): self
{
$rule = new MaxLengthRule($max, $message);
$this->addRule($rule);
return $this;
}
public function in(array $values, ?string $message = null): self
{
$rule = new InRule($values, $message);
$this->addRule($rule);
return $this;
}
public function regex(string $pattern, ?string $message = null): self
{
$rule = new RegexRule($pattern, $message);
$this->addRule($rule);
return $this;
}
public function custom(callable $callback, ?string $message = null): self
{
$rule = new CustomRule($callback, $message);
$this->addRule($rule);
return $this;
}
public function message(string $message): self
{
if (!empty($this->rules)) {
$lastRule = end($this->rules);
$lastRule->setMessage($message);
}
return $this;
}
public function messages(array $messages): self
{
$this->messages = array_merge($this->messages, $messages);
return $this;
}
public function validate(mixed $value = null): ValidationResult
{
$value = $value ?? $this->value;
$errors = new MessageBag();
foreach ($this->rules as $rule) {
if (!$rule->validate($value)) {
$message = $this->getRuleMessage($rule);
$field = $this->field ?? 'value';
$errors->add($field, $message);
}
}
return new ValidationResult($errors->isEmpty(), $errors);
}
public function check(mixed $value = null): bool
{
return $this->validate($value)->isValid();
}
public function setField(string $field): self
{
$this->field = $field;
return $this;
}
private function addRule(AbstractRule $rule): void
{
$this->rules[] = $rule;
}
private function getRuleMessage(AbstractRule $rule): string
{
$ruleName = $rule->getName();
if (isset($this->messages[$ruleName])) {
return $rule->getFormattedMessage(['field' => $this->field ?? 'value']);
}
return $rule->getFormattedMessage(['field' => $this->field ?? 'value']);
}
}
```
**Step 4: 运行测试验证失败(缺少规则类)**
Run: `composer test`
Expected: FAIL with missing rule classes
**Step 5: 实现缺失的规则类**
```php
parameters['min'] = $min;
}
public function validate(mixed $value): bool
{
if (!is_string($value) && !is_array($value)) {
return false;
}
return strlen($value) >= $this->parameters['min'];
}
public function getDefaultMessage(): string
{
return '{field} 长度不能小于 {min}';
}
}
```
```php
parameters['max'] = $max;
}
public function validate(mixed $value): bool
{
if (!is_string($value) && !is_array($value)) {
return false;
}
return strlen($value) <= $this->parameters['max'];
}
public function getDefaultMessage(): string
{
return '{field} 长度不能大于 {max}';
}
}
```
```php
parameters['values'] = $values;
}
public function validate(mixed $value): bool
{
return in_array($value, $this->parameters['values'], true);
}
public function getDefaultMessage(): string
{
return '{field} 必须是以下值之一';
}
}
```
```php
parameters['pattern'] = $pattern;
}
public function validate(mixed $value): bool
{
if (!is_string($value)) {
return false;
}
return preg_match($this->parameters['pattern'], $value) === 1;
}
public function getDefaultMessage(): string
{
return '{field} 格式不正确';
}
}
```
```php
callback = $callback;
}
public function validate(mixed $value): bool
{
return ($this->callback)($value);
}
public function getDefaultMessage(): string
{
return '{field} 验证失败';
}
}
```
**Step 6: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 7: Commit**
```bash
git add src/Awesome/Validation/Builder.php src/Awesome/Validation/Rules/*.php tests/Unit/BuilderTest.php
git commit -m "feat: implement Builder validator"
```
---
## Task 7: 实现 Validator 门面类
**Files:**
- Create: `src/Awesome/Validation/Validator.php`
- Create: `tests/Feature/ValidatorTest.php`
**Step 1: 编写 Validator 测试**
```php
validate('hello');
$this->assertTrue($result->isValid());
}
public function testIntegerValidation()
{
$result = Validator::integer()->validate(123);
$this->assertTrue($result->isValid());
}
public function testEmailValidation()
{
$result = Validator::email()->validate('test@example.com');
$this->assertTrue($result->isValid());
}
public function testChainedValidation()
{
$result = Validator::string()
->minLength(5)
->maxLength(10)
->validate('hello');
$this->assertTrue($result->isValid());
}
public function testCustomMessage()
{
$result = Validator::string('必须是字符串')->validate(123);
$this->assertEquals('必须是字符串', $result->firstError());
}
}
```
**Step 2: 运行测试验证失败**
Run: `composer test`
Expected: FAIL with "Class Awesome\Validation\Validator not found"
**Step 3: 实现 Validator 类**
```php
string($message);
}
public static function integer(?string $message = null): Builder
{
return (new Builder())->integer($message);
}
public static function required(?string $message = null): Builder
{
return (new Builder())->required($message);
}
public static function email(?string $message = null): Builder
{
return (new Builder())->email($message);
}
public static function array(?string $message = null): Builder
{
return (new Builder())->custom(fn($v) => is_array($v), $message);
}
public static function make(array $data): Builder
{
return new Builder($data);
}
public static function validate(array $data, array $rules): ValidationResult
{
$errors = new Messages\MessageBag();
foreach ($rules as $field => $ruleString) {
$value = self::getNestedValue($data, $field);
$builder = new Builder($value);
$builder->setField($field);
$ruleList = explode('|', $ruleString);
foreach ($ruleList as $rule) {
self::applyRule($builder, $rule);
}
$result = $builder->validate();
if ($result->isInvalid()) {
foreach ($result->errors()->all() as $fieldErrors) {
foreach ($fieldErrors as $error) {
$errors->add($field, $error);
}
}
}
}
return new ValidationResult($errors->isEmpty(), $errors);
}
private static function getNestedValue(array $data, string $field): mixed
{
$keys = explode('.', $field);
$value = $data;
foreach ($keys as $key) {
if (!is_array($value) || !array_key_exists($key, $value)) {
return null;
}
$value = $value[$key];
}
return $value;
}
private static function applyRule(Builder $builder, string $rule): void
{
if (str_contains($rule, ':')) {
[$ruleName, $parameters] = explode(':', $rule, 2);
$paramArray = explode(',', $parameters);
match ($ruleName) {
'min' => $builder->minLength((int)$paramArray[0]),
'max' => $builder->maxLength((int)$paramArray[0]),
'in' => $builder->in($paramArray),
'regex' => $builder->regex($paramArray[0]),
default => null
};
} else {
match ($rule) {
'required' => $builder->required(),
'string' => $builder->string(),
'integer' => $builder->integer(),
'email' => $builder->email(),
default => null
};
}
}
}
```
**Step 4: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/Awesome/Validation/Validator.php tests/Feature/ValidatorTest.php
git commit -m "feat: implement Validator facade"
```
---
## Task 8: 实现 ValidationException 异常类
**Files:**
- Create: `src/Awesome/Validation/Exceptions/ValidationException.php`
- Create: `tests/Unit/Exceptions/ValidationExceptionTest.php`
**Step 1: 编写 ValidationException 测试**
```php
add('username', '用户名必填');
$exception = new ValidationException('验证失败', $errors, 'username');
$this->assertEquals('验证失败', $exception->getMessage());
$this->assertEquals($errors, $exception->getErrors());
$this->assertEquals('username', $exception->getField());
}
public function testToArray()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$exception = new ValidationException('验证失败', $errors);
$this->assertEquals(['username' => ['用户名必填']], $exception->toArray());
}
public function testToJson()
{
$errors = new MessageBag();
$errors->add('username', '用户名必填');
$exception = new ValidationException('验证失败', $errors);
$expected = json_encode(['username' => ['用户名必填']]);
$this->assertEquals($expected, $exception->toJson());
}
}
```
**Step 2: 运行测试验证失败**
Run: `composer test`
Expected: FAIL with "Class Awesome\Validation\Exceptions\ValidationException not found"
**Step 3: 实现 ValidationException 类**
```php
errors = $errors;
$this->field = $field;
}
public function getErrors(): MessageBag
{
return $this->errors;
}
public function getField(): string
{
return $this->field;
}
public function toArray(): array
{
return $this->errors->toArray();
}
public function toJson(): string
{
return $this->errors->toJson();
}
}
```
**Step 4: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/Awesome/Validation/Exceptions/ValidationException.php tests/Unit/Exceptions/ValidationExceptionTest.php
git commit -m "feat: implement ValidationException"
```
---
## Task 9: 实现规则数组验证功能
**Files:**
- Modify: `src/Awesome/Validation/Validator.php`
- Create: `tests/Feature/ArrayValidationTest.php`
**Step 1: 编写规则数组验证测试**
```php
'john',
'email' => 'john@example.com',
'age' => 25
];
$result = Validator::validate($data, [
'username' => 'required|string|min:3',
'email' => 'required|email',
'age' => 'integer|min:18'
]);
$this->assertTrue($result->isValid());
}
public function testArrayValidationWithErrors()
{
$data = [
'username' => 'jo',
'email' => 'invalid-email',
'age' => 15
];
$result = Validator::validate($data, [
'username' => 'required|string|min:3',
'email' => 'required|email',
'age' => 'integer|min:18'
]);
$this->assertTrue($result->isInvalid());
$this->assertNotEmpty($result->toArray());
}
public function testNestedValidation()
{
$data = [
'user' => [
'profile' => [
'name' => 'John Doe',
'age' => 30
]
]
];
$result = Validator::validate($data, [
'user.profile.name' => 'required|string',
'user.profile.age' => 'required|integer|min:18'
]);
$this->assertTrue($result->isValid());
}
}
```
**Step 2: 运行测试验证通过**
Run: `composer test`
Expected: PASS
**Step 3: Commit**
```bash
git add tests/Feature/ArrayValidationTest.php
git commit -m "feat: add array validation tests"
```
---
## Task 10: 实现完整的使用示例和文档
**Files:**
- Modify: `README.md`
- Create: `examples/basic.php`
- Create: `examples/array-validation.php`
- Create: `examples/chained-validation.php`
**Step 1: 更新 README.md**
```markdown
# Awesome Validation
一个友好的 PHP 参数验证库,基于 PHP 8.1+ 语法开发,遵循 PSR-4 规范。
## 特性
- 多种验证方式:链式调用、规则数组、Attribute
- 友好的错误消息:默认中文消息,支持自定义
- 灵活的消息设置:单独设置、批量设置、使用默认消息
- 多格式输出:字符串、数组、JSON
- 嵌套验证:支持点号路径访问
- 丰富的上下文信息:字段名、期望值等
## 安装
```bash
composer require awesome/validation
```
## 使用示例
### 链式调用
```php
use Awesome\Validation\Validator;
try {
Validator::string()->message('必须是字符串')
->minLength(5)->message('至少5个字符')
->maxLength(20)->message('最多20个字符')
->validate($username);
} catch (ValidationException $e) {
echo $e->getMessage();
}
$result = Validator::email()->validate($email);
if ($result->isInvalid()) {
echo $result->firstError();
}
```
### 规则数组验证
```php
use Awesome\Validation\Validator;
$result = Validator::validate($data, [
'username' => 'required|string|min:5|max:20',
'email' => 'required|email',
'age' => 'integer|min:18|max:120',
'profile.bio' => 'string|max:500'
]);
if ($result->isInvalid()) {
print_r($result->errors()->toArray());
}
```
### 批量设置消息
```php
Validator::string()->minLength(5)->maxLength(20)
->messages([
'string' => '必须是字符串',
'minLength' => '至少5个字符',
'maxLength' => '最多20个字符'
])
->validate($username);
```
## 运行测试
```bash
composer test
```
## 代码分析
```bash
composer analyze
```
```
**Step 2: 创建基础示例**
```php
validate('hello');
echo "字符串验证: " . ($stringValidation->isValid() ? '通过' : '失败') . "\n";
$emailValidation = Validator::email()->validate('test@example.com');
echo "邮箱验证: " . ($emailValidation->isValid() ? '通过' : '失败') . "\n";
$integerValidation = Validator::integer()->validate(123);
echo "整数验证: " . ($integerValidation->isValid() ? '通过' : '失败') . "\n";
```
**Step 3: 创建链式验证示例**
```php
minLength(5)
->maxLength(20)
->validate('hello world');
echo "验证结果: " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息: " . $result->firstError() . "\n";
}
$result2 = Validator::string()
->minLength(5)->message('至少5个字符')
->maxLength(20)->message('最多20个字符')
->validate('hi');
echo "\n验证结果: " . ($result2->isValid() ? '通过' : '失败') . "\n";
if ($result2->isInvalid()) {
echo "错误信息: " . $result2->firstError() . "\n";
}
```
**Step 4: 创建数组验证示例**
```php
'john',
'email' => 'john@example.com',
'age' => 25,
'profile' => [
'bio' => 'A developer'
]
];
$result = Validator::validate($data, [
'username' => 'required|string|min:3',
'email' => 'required|email',
'age' => 'integer|min:18',
'profile.bio' => 'string|max:100'
]);
echo "验证结果: " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息:\n";
print_r($result->toArray());
}
```
**Step 5: 运行示例验证**
Run: `php examples/basic.php`
Expected: 显示基础验证结果
Run: `php examples/chained-validation.php`
Expected: 显示链式验证结果
Run: `php examples/array-validation.php`
Expected: 显示数组验证结果
**Step 6: Commit**
```bash
git add README.md examples/
git commit -m "docs: add usage examples and documentation"
```
---
## Task 11: 运行完整测试套件
**Files:**
- None (verification task)
**Step 1: 运行所有测试**
Run: `composer test`
Expected: All tests PASS
**Step 2: 运行代码分析**
Run: `composer analyze`
Expected: No errors
**Step 3: 验证自动加载**
Run: `composer dump-autoload`
Expected: No errors
**Step 4: 最终验证**
Run: `php -v && composer --version`
Expected: Show PHP and Composer versions
**Step 5: Commit**
```bash
git add .
git commit -m "test: verify complete implementation"
```
---
## 总结
实现计划已完成,包含以下主要组件:
1. ✅ 项目结构初始化
2. ✅ MessageBag 消息收集器
3. ✅ AbstractRule 规则基类
4. ✅ 基础验证规则(String, Integer, Required, Email等)
5. ✅ ValidationResult 验证结果类
6. ✅ Builder 验证器构建器
7. ✅ Validator 门面类
8. ✅ ValidationException 异常类
9. ✅ 规则数组验证功能
10. ✅ 使用示例和文档
11. ✅ 完整测试套件
该实现满足所有需求:
- 支持多种验证方式(链式调用、规则数组)
- 友好的错误消息(默认中文,支持自定义)
- 灵活的消息设置(单独、批量、默认)
- 多格式输出(字符串、数组、JSON)
- 嵌套验证(点号路径)
- 遵循 PSR-4 规范
- 基于 PHP 8.1+ 语法
docs/plans/2026-04-11-numeric-and-preprocess-design.md 0000644 00000004705 15166406615 0016036 0 ustar 00 # Awesome Validation 增强功能设计方案 (2026-04-11)
## 1. 目标
扩展 Awesome Validation 库,增加更强大的数值校验(最小值、区间)、严格类型校验、大数处理、预处理管道以及数学特征校验。
## 2. 详细设计
### 2.1 数值验证增强 (Min/Between)
- **MinRule**: 扩展现有规则,支持 `inclusive` 参数。
- API: `$v->min(float $min)->inclusive(bool $inclusive = true)`
- **BetweenRule**: 支持数值范围检查。
- API: `$v->between(float $min, float $max)->inclusive(bool $minInclusive = true, bool $maxInclusive = true)`
- **实现细节**:
- 内部统一将输入转换为 `float` 进行比较。
- 提供清晰的错误消息,如 "value 必须在 [1, 10) 之间"。
### 2.2 字符串预处理框架 (Preprocessing)
- **机制**: 在 `Builder` 中引入预处理管道。
- **Builder 变更**:
- 增加 `private mixed $currentValue` 属性,初始值为原始 `$value`。
- 增加 `public function transform(callable $callback): self`。
- 所有后续规则的 `validate()` 方法将作用于 `$currentValue`。
- **缓存**: 在单次验证链中,`transform` 的结果将被缓存。
- **示例**: `$v->transform('trim')->transform('strtolower')->email()`。
### 2.3 严格数字类型与大数支持
- **StrictIntegerRule**: 严格检查类型是否为 `int`。
- **SafeIntegerRule**: 检查是否在 `PHP_INT_MIN` 到 `PHP_INT_MAX` 之间。
- **进制支持**:
- `IntegerRule` 增加进制自动检测逻辑。
- 使用 `filter_var($value, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_OCTAL | FILTER_FLAG_ALLOW_HEX)`。
### 2.4 数字属性验证库
- **新增规则**:
- `EvenRule`: 偶数校验 (`$value % 2 === 0`)。
- `OddRule`: 奇数校验 (`$value % 2 !== 0`)。
- `PositiveRule`: 正数校验 (`$value > 0`)。
- `NegativeRule`: 负数校验 (`$value < 0`)。
- `PrimeRule`: 质数校验。
- **PrimeRule 性能优化**:
- 使用试除法 (Trial Division) 结合 $\sqrt{n}$ 优化。
- 增加大数安全检查。
## 3. 架构影响
- `Builder` 将不再是无状态的(在单次验证链中),它需要跟踪 `$currentValue`。
- `AbstractRule` 可能需要微调以更好地支持 `field` 名称的动态获取(尤其是在预处理后)。
## 4. 测试策略
- 为每个新规则编写 Unit Tests。
- 编写 Feature Tests 验证 `transform` 管道的流转正确性。
- 增加大数、浮点数精度等边缘情况的测试用例。
docs/plans/2026-04-11-numeric-preprocess-implementation.md 0000644 00000012410 15166406615 0017042 0 ustar 00 # Numeric and Preprocess Enhancements Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Extend validation rules for numeric range, strict types, mathematical properties, and implement a transformation pipeline.
**Architecture:** Add a `transform` pipeline to `Builder` to allow pre-validation data modification. Implement specialized numeric rules (Min, Between, StrictInteger, Prime, etc.) using PHP's native filters and optimized algorithms.
**Tech Stack:** PHP 8.1+, filter_var, BCMath (optional for precision).
---
### Task 1: Implement Enhanced MinRule and BetweenRule
**Files:**
- Create: `src/Awesome/Validation/Rules/MinRule.php`
- Create: `src/Awesome/Validation/Rules/BetweenRule.php`
- Modify: `src/Awesome/Validation/Builder.php`
- Test: `tests/Unit/Rules/MinRuleTest.php`, `tests/Unit/Rules/BetweenRuleTest.php`
**Step 1: Create MinRule with inclusive support**
```php
namespace Awesome\Validation\Rules;
class MinRule extends AbstractRule {
protected string $name = 'min';
public function __construct(protected float $min, protected bool $inclusive = true, ?string $message = null) {
parent::__construct($message);
}
public function validate(mixed $value): bool {
if (!is_numeric($value)) return false;
return $this->inclusive ? $value >= $this->min : $value > $this->min;
}
public function getDefaultMessage(): string {
return $this->inclusive ? "{field} 必须大于或等于 {$this->min}" : "{field} 必须大于 {$this->min}";
}
}
```
**Step 2: Create BetweenRule with flexible boundaries**
```php
namespace Awesome\Validation\Rules;
class BetweenRule extends AbstractRule {
protected string $name = 'between';
public function __construct(protected float $min, protected float $max, protected bool $minInclusive = true, protected bool $maxInclusive = true, ?string $message = null) {
parent::__construct($message);
}
public function validate(mixed $value): bool {
if (!is_numeric($value)) return false;
$low = $this->minInclusive ? $value >= $this->min : $value > $this->min;
$high = $this->maxInclusive ? $value <= $this->max : $value < $this->max;
return $low && $high;
}
}
```
**Step 3: Add methods to Builder**
```php
public function min(float $min, bool $inclusive = true, ?string $message = null): self {
return $this->addRule(new Rules\MinRule($min, $inclusive, $message));
}
public function between(float $min, float $max, bool $minInc = true, bool $maxInc = true, ?string $message = null): self {
return $this->addRule(new Rules\BetweenRule($min, $max, $minInc, $maxInc, $message));
}
```
---
### Task 2: Build Transformation (Preprocessing) Framework
**Files:**
- Modify: `src/Awesome/Validation/Builder.php`
- Test: `tests/Feature/TransformTest.php`
**Step 1: Add transformation state to Builder**
```php
private mixed $currentValue;
private array $transformations = [];
// Update constructor
public function __construct(private mixed $value = null) {
$this->currentValue = $value;
}
public function transform(callable $callback): self {
$this->transformations[] = $callback;
$this->currentValue = $callback($this->currentValue);
return $this;
}
```
**Step 2: Update validate methods to use currentValue**
```php
public function validate(mixed $value = null): ValidationResult {
$val = $value !== null ? $this->applyTransformations($value) : $this->currentValue;
// ... loop rules using $val
}
private function applyTransformations(mixed $value): mixed {
foreach ($this->transformations as $callback) {
$value = $callback($value);
}
return $value;
}
```
---
### Task 3: Implement Strict Integer and Base Detection
**Files:**
- Create: `src/Awesome/Validation/Rules/StrictIntegerRule.php`
- Modify: `src/Awesome/Validation/Rules/IntegerRule.php`
- Test: `tests/Unit/Rules/IntegerTypeTest.php`
**Step 1: Strict Integer (type check)**
```php
public function validate(mixed $value): bool {
return is_int($value);
}
```
**Step 2: Update IntegerRule for base detection**
```php
public function validate(mixed $value): bool {
return filter_var($value, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_OCTAL | FILTER_FLAG_ALLOW_HEX) !== false;
}
```
---
### Task 4: Implement Mathematical Property Rules (Even, Odd, Prime)
**Files:**
- Create: `src/Awesome/Validation/Rules/EvenRule.php`
- Create: `src/Awesome/Validation/Rules/OddRule.php`
- Create: `src/Awesome/Validation/Rules/PrimeRule.php`
**Step 1: Even/Odd Rules**
```php
// Even: $value % 2 === 0
// Odd: $value % 2 !== 0
```
**Step 2: Optimized PrimeRule**
```php
public function validate(mixed $value): bool {
if (!is_numeric($value) || $value < 2) return false;
$n = (int)$value;
if ($n === 2 || $n === 3) return true;
if ($n % 2 === 0 || $n % 3 === 0) return false;
for ($i = 5; $i * $i <= $n; $i += 6) {
if ($n % $i === 0 || $n % ($i + 2) === 0) return false;
}
return true;
}
```
---
### Task 5: Final Integration and Documentation
**Files:**
- Modify: `RULES.md`
- Create: `examples/advanced_numeric.php`
**Step 1: Update documentation with new rules and transform example.**
**Step 2: Create a complex example showing transform -> numeric -> between -> prime pipeline.**
examples/advanced_numeric.php 0000644 00000004154 15166406615 0012400 0 ustar 00 ' 101 ', // 需要 trim 且是质数
'score' => '85.5', // 需要在 [0, 100] 之间
'age' => 26, // 修正为偶数
'hex_val' => '0x1F', // 进制检测支持
'email' => ' Admin@Example.Com ', // 预处理转换
];
$result = Validator::validateKeys($data, function($v) {
return [
// 1. 预处理 + 质数校验
$v->key('user_id')->transform('trim')->numeric()->prime(),
// 2. 数值区间校验 (闭区间)
$v->key('score')->numeric()->between(0, 100),
// 3. 正数 + 偶数校验
$v->key('age')->numeric()->positive()->even(),
// 4. 自动进制检测
$v->key('hex_val')->integer()->min(0),
// 5. 链式预处理 + 格式校验
$v->key('email')->transform('trim')->transform('strtolower')->email(),
];
});
if ($result->isValid()) {
echo "✅ 所有高级校验均通过!\n";
echo "验证日志:\n";
foreach ($result->getLogs() as $log) {
echo " - [{$log['status']}] {$log['field']}: {$log['message']}\n";
}
} else {
echo "❌ 校验失败:\n";
print_r($result->errors()->all());
}
echo "\n=== 边界情况演示 ===\n";
$failData = [
'odd_num' => 4, // 期望奇数
'range' => 10, // 期望 (0, 10) 开区间
'large' => '9223372036854775808', // 超出 PHP 64位整数范围
];
$result2 = Validator::validateKeys($failData, function($v) {
return [
$v->key('odd_num')->numeric()->odd(),
$v->key('range')->numeric()->between(0, 10, false, false),
$v->key('large')->safeInteger(),
];
});
if ($result2->isInvalid()) {
echo "捕获到预期错误:\n";
foreach ($result2->errors()->all() as $field => $errors) {
$msg = is_array($errors) ? implode(', ', $errors) : $errors;
echo " - {$field}: {$msg}\n";
}
}
echo "\n=== 示例结束 ===\n";
examples/array_validation.php 0000644 00000011174 15166406615 0012441 0 ustar 00 'john_doe',
'email' => 'john@example.com',
'age' => '25',
'status' => 'active'
];
$rules = [
'username' => 'required|min:5|max:20',
'email' => 'required|email',
'age' => 'required|integer',
'status' => 'required|in:active,inactive,pending'
];
$result = Validator::validate($data, $rules);
if ($result->isValid()) {
echo "✓ 所有字段验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example2 = function() {
echo "示例2: 带自定义错误消息的验证\n";
$data = [
'username' => 'ab',
'email' => 'invalid-email',
'password' => '123'
];
$rules = [
'username' => 'required|min:5,message:用户名至少需要5个字符',
'email' => 'required|email,message:请输入有效的邮箱地址',
'password' => 'required|min:8,message:密码至少需要8个字符'
];
$result = Validator::validate($data, $rules);
if ($result->isValid()) {
echo "✓ 验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example3 = function() {
echo "示例3: 嵌套字段验证\n";
$data = [
'user' => [
'profile' => [
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => '30'
]
]
];
$rules = [
'user.profile.name' => 'required|min:2',
'user.profile.email' => 'required|email',
'user.profile.age' => 'required|integer'
];
$result = Validator::validate($data, $rules);
if ($result->isValid()) {
echo "✓ 嵌套字段验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example4 = function() {
echo "示例4: 数组项验证\n";
$data = [
['name' => 'John', 'age' => '25'],
['name' => 'Jane', 'age' => '30'],
['name' => 'Bob', 'age' => 'not-a-number']
];
$rules = [
'name' => 'required|min:2',
'age' => 'required|integer'
];
$result = Validator::validateNested($data, $rules);
if ($result->isValid()) {
echo "✓ 所有数组项验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example5 = function() {
echo "示例5: 使用 validateOrFail 抛出异常\n";
$data = [
'username' => 'ab',
'email' => 'invalid'
];
$rules = [
'username' => 'required|min:5',
'email' => 'required|email'
];
try {
Validator::validateOrFail($data, $rules);
echo "✓ 验证通过\n";
} catch (\Awesome\Validation\ValidationException $e) {
echo "✗ 验证失败,抛出异常:\n";
echo "异常消息: " . $e->getMessage() . "\n";
echo "错误详情:\n";
foreach ($e->toArray() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example6 = function() {
echo "示例6: 正则表达式验证\n";
$data = [
'phone' => '13800138000',
'id_card' => '110101199001011234'
];
$rules = [
'phone' => 'required|regex:/^1[3-9]\d{9}$/',
'id_card' => 'required|regex:/^\d{17}[\dXx]$/'
];
$result = Validator::validate($data, $rules);
if ($result->isValid()) {
echo "✓ 正则验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example1();
$example2();
$example3();
$example4();
$example5();
$example6();
echo "=== 数组验证示例完成 ===\n";
examples/basic_validation.php 0000644 00000004354 15166406615 0012406 0 ustar 00 minLength(5)
->maxLength(20)
->validate('hello world');
if ($result->isValid()) {
echo "✓ 验证通过\n";
} else {
echo "✗ 验证失败\n";
print_r($result->errors()->all());
}
echo "\n";
};
$example2 = function() {
echo "示例2: 邮箱验证\n验证邮箱: test@example.com\n";
$result = Validator::email()->validate('test@example.com');
if ($result->isValid()) {
echo "✓ 邮箱格式正确\n";
} else {
echo "✗ 邮箱格式错误\n";
print_r($result->errors()->all());
}
echo "\n";
};
$example3 = function() {
echo "示例3: 整数验证\n验证年龄: 25\n";
$result = Validator::integer()
->minLength(1)
->maxLength(3)
->validate('25');
if ($result->isValid()) {
echo "✓ 年龄验证通过\n";
} else {
echo "✗ 年龄验证失败\n";
print_r($result->errors()->all());
}
echo "\n";
};
$example4 = function() {
echo "示例4: 必填字段验证\n验证空值\n";
$result = Validator::required()->validate('');
if ($result->isValid()) {
echo "✓ 验证通过\n";
} else {
echo "✗ 验证失败\n";
print_r($result->errors()->all());
}
echo "\n";
};
$example5 = function() {
echo "示例5: 自定义验证规则\n验证密码强度\n";
$result = Validator::string()
->minLength(8)
->custom(function($value) {
return preg_match('/[A-Z]/', $value) &&
preg_match('/[0-9]/', $value);
}, '密码必须包含大写字母和数字')
->validate('Password123');
if ($result->isValid()) {
echo "✓ 密码强度验证通过\n";
} else {
echo "✗ 密码强度验证失败\n";
print_r($result->errors()->all());
}
echo "\n";
};
$example1();
$example2();
$example3();
$example4();
$example5();
echo "=== 基础验证示例完成 ===\n";
examples/common_validation.php 0000644 00000010265 15166406615 0012613 0 ustar 00 validate('你好世界');
echo "验证 '你好世界' (2-10个字符): " . ($result->isValid() ? '通过' : '失败') . "\n";
$result = Validator::chineseLength(2, 10)->validate('你');
echo "验证 '你' (2-10个字符): " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息: " . $result->errors()->first('value') . "\n";
}
echo "\n2. 手机号验证\n";
$result = Validator::phone()->validate('13800138000');
echo "验证 '13800138000': " . ($result->isValid() ? '通过' : '失败') . "\n";
$result = Validator::phone()->validate('12800138000');
echo "验证 '12800138000': " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息: " . $result->errors()->first('value') . "\n";
}
echo "\n3. IP地址验证 (IPv4)\n";
$result = Validator::ip('v4')->validate('192.168.1.1');
echo "验证 '192.168.1.1': " . ($result->isValid() ? '通过' : '失败') . "\n";
$result = Validator::ip('v4')->validate('256.168.1.1');
echo "验证 '256.168.1.1': " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息: " . $result->errors()->first('value') . "\n";
}
echo "\n4. IP地址验证 (IPv6)\n";
$result = Validator::ip('v6')->validate('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
echo "验证 '2001:0db8:85a3:0000:0000:8a2e:0370:7334': " . ($result->isValid() ? '通过' : '失败') . "\n";
$result = Validator::ip('v6')->validate('192.168.1.1');
echo "验证 '192.168.1.1': " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息: " . $result->errors()->first('value') . "\n";
}
echo "\n5. 域名验证\n";
$result = Validator::domain()->validate('example.com');
echo "验证 'example.com': " . ($result->isValid() ? '通过' : '失败') . "\n";
$result = Validator::domain()->validate('example');
echo "验证 'example': " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息: " . $result->errors()->first('value') . "\n";
}
echo "\n6. 组合验证 (用户注册表单)\n";
$data = [
'username' => '张三',
'phone' => '13800138000',
'ip' => '192.168.1.1',
'domain' => 'example.com'
];
$result = Validator::validateKeys($data, function($validator) use ($data) {
return [
$validator->key('username')->required()->chineseLength(2, 10),
$validator->key('phone')->required()->phone(),
$validator->key('ip')->required()->ip('v4'),
$validator->key('domain')->required()->domain()
];
});
if ($result->isValid()) {
echo "所有字段验证通过!\n";
} else {
echo "验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
foreach ($errors as $error) {
echo " {$field}: {$error}\n";
}
}
}
echo "\n7. 数组元素验证\n";
$phones = ['13800138000', '13912345678', '12800138000'];
$result = Validator::validateKeys($phones, function($validator) {
return [
$validator->key('0')->phone(),
$validator->key('1')->phone(),
$validator->key('2')->phone()
];
});
if ($result->isValid()) {
echo "所有手机号验证通过!\n";
} else {
echo "验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
foreach ($errors as $error) {
echo " {$field}: {$error}\n";
}
}
}
echo "\n8. 使用 each() 验证数组中的每个元素\n";
$domains = ['example.com', 'test.org', 'invalid'];
$result = Validator::validateKeys($domains, function($validator) {
return [
$validator->key('0')->domain(),
$validator->key('1')->domain(),
$validator->key('2')->domain()
];
});
if ($result->isValid()) {
echo "所有域名验证通过!\n";
} else {
echo "验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
foreach ($errors as $error) {
echo " {$field}: {$error}\n";
}
}
}
echo "\n=== 示例完成 ===\n";
examples/each_validation.php 0000644 00000013112 15166406615 0012215 0 ustar 00 ['1', '2', '3', '4', '5']
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('numbers')->each()->integer()
];
});
if ($result->isValid()) {
echo "✓ 所有数字验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example2 = function() {
echo "示例2: 验证邮箱数组\n";
$data = [
'emails' => [
'john@example.com',
'jane@example.com',
'invalid-email'
]
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('emails')->each()->email()
];
});
if ($result->isValid()) {
echo "✓ 所有邮箱验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example3 = function() {
echo "示例3: 验证字符串数组(最小长度)\n";
$data = [
'names' => ['John', 'Jane', 'A', 'Bob']
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('names')->each()->string()->minLength(2)
];
});
if ($result->isValid()) {
echo "✓ 所有名称验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example4 = function() {
echo "示例4: 验证状态数组(in 规则)\n";
$data = [
'statuses' => ['active', 'inactive', 'invalid']
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('statuses')->each()->in(['active', 'inactive', 'pending'])
];
});
if ($result->isValid()) {
echo "✓ 所有状态验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example5 = function() {
echo "示例5: 验证年龄数组(自定义错误消息)\n";
$data = [
'ages' => ['25', '30', 'not-a-number', '35']
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('ages')->each()->integer('年龄必须是整数')
];
});
if ($result->isValid()) {
echo "✓ 所有年龄验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example6 = function() {
echo "示例6: 验证空数组\n";
$data = [
'items' => []
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('items')->each()->string()
];
});
if ($result->isValid()) {
echo "✓ 空数组验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example7 = function() {
echo "示例7: 混合验证(普通字段 + 数组字段)\n";
$data = [
'username' => 'john_doe',
'emails' => [
'john@example.com',
'jane@example.com',
'invalid-email'
],
'ages' => ['25', '30', '35']
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('username')->required()->string()->minLength(5),
$builder->key('emails')->each()->email(),
$builder->key('ages')->each()->integer()
];
});
if ($result->isValid()) {
echo "✓ 混合验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example8 = function() {
echo "示例8: 嵌套数组验证\n";
$data = [
'data' => [
'users' => [
['name' => 'John', 'age' => '25'],
['name' => 'Jane', 'age' => '30']
]
]
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('data.users')->each()->string()->integer()
];
});
if ($result->isValid()) {
echo "✓ 嵌套数组验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example1();
$example2();
$example3();
$example4();
$example5();
$example6();
$example7();
$example8();
echo "=== Each 验证示例完成 ===\n";
examples/key_validation.php 0000644 00000014446 15166406615 0012120 0 ustar 00 'john_doe',
'email' => 'john@example.com',
'age' => '25'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('username')->required()->string()->minLength(5),
$builder->key('email')->required()->email(),
$builder->key('age')->required()->integer()
];
});
if ($result->isValid()) {
echo "✓ 所有字段验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example2 = function() {
echo "示例2: 带自定义错误消息的 key 验证\n";
$data = [
'username' => 'ab',
'email' => 'invalid-email',
'password' => '123'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('username')->required()->string()->minLength(5, '用户名至少需要5个字符'),
$builder->key('email')->required()->email('请输入有效的邮箱地址'),
$builder->key('password')->required()->string()->minLength(8, '密码至少需要8个字符')
];
});
if ($result->isValid()) {
echo "✓ 验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example3 = function() {
echo "示例3: 嵌套字段 key 验证\n";
$data = [
'user' => [
'profile' => [
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => '30'
]
]
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('user.profile.name')->required()->string()->minLength(2),
$builder->key('user.profile.email')->required()->email(),
$builder->key('user.profile.age')->required()->integer()
];
});
if ($result->isValid()) {
echo "✓ 嵌套字段验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example4 = function() {
echo "示例4: 使用 in 和 regex 规则\n";
$data = [
'status' => 'active',
'priority' => 'high',
'phone' => '13800138000'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('status')->required()->in(['active', 'inactive', 'pending']),
$builder->key('priority')->required()->in(['low', 'medium', 'high']),
$builder->key('phone')->required()->regex('/^1[3-9]\d{9}$/')
];
});
if ($result->isValid()) {
echo "✓ 验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example5 = function() {
echo "示例5: 自定义验证规则\n";
$data = [
'password' => 'Password123'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('password')
->required()
->string()
->minLength(8)
->custom(function($value) {
return preg_match('/[A-Z]/', $value) &&
preg_match('/[0-9]/', $value);
}, '密码必须包含大写字母和数字')
];
});
if ($result->isValid()) {
echo "✓ 密码强度验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example6 = function() {
echo "示例6: 使用 Builder 的 key() 方法\n";
$data = [
'username' => 'john',
'email' => 'john@example.com'
];
$builder = Validator::make($data);
$result = $builder->key('username')
->required()
->string()
->minLength(3)
->validate();
if ($result->isValid()) {
echo "✓ 用户名验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example7 = function() {
echo "示例7: 复杂数据结构验证\n";
$data = [
'username' => 'shenzhao',
'balance' => 67.89,
'brand_id' => 37,
'user' => [
'profile' => [
'name' => 'John',
'email' => 'john@example.com'
]
]
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('username')->required()->string()->minLength(3),
$builder->key('balance')->required()->custom(function($value) {
return is_numeric($value) && $value >= 0;
}, '余额必须是非负数'),
$builder->key('brand_id')->required()->integer(),
$builder->key('user.profile.name')->required()->string()->minLength(2),
$builder->key('user.profile.email')->required()->email()
];
});
if ($result->isValid()) {
echo "✓ 复杂数据结构验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example1();
$example2();
$example3();
$example4();
$example5();
$example6();
$example7();
echo "=== Key 验证示例完成 ===\n";
examples/max_validation.php 0000644 00000005705 15166406615 0012113 0 ustar 00 numeric()->max(100, false)->validate(50);
echo "值 50 验证结果: " . ($result->isValid() ? '通过' : '失败') . "\n";
$result = $builder->numeric()->max(100, false)->validate(100);
echo "值 100 验证结果: " . ($result->isValid() ? '通过' : '失败') . " (exclusive: 等于100应该失败)\n";
$result = $builder->numeric()->max(100, false)->validate(150);
echo "值 150 验证结果: " . ($result->isValid() ? '通过' : '失败') . "\n\n";
// 示例 2: 使用 Builder 进行 inclusive 最大值验证(<=)
echo "示例 2: Inclusive Max (<= 100)\n";
$builder2 = new Builder();
$result = $builder2->numeric()->max(100, true)->validate(100);
echo "值 100 验证结果: " . ($result->isValid() ? '通过' : '失败') . " (inclusive: 等于100应该通过)\n";
$result = $builder2->numeric()->max(100, true)->validate(99);
echo "值 99 验证结果: " . ($result->isValid() ? '通过' : '失败') . "\n\n";
// 示例 3: 数组验证场景
echo "示例 3: 在数组验证中使用 max()\n";
$data = [
'age' => 25,
'score' => 95,
'temperature' => 36.5,
];
$result = Validator::validateKeys($data, function($v) {
return [
$v->key('age')->numeric()->max(150, true), // 年龄 <= 150
$v->key('score')->numeric()->max(100, true), // 分数 <= 100
$v->key('temperature')->numeric()->max(42, false), // 体温 < 42
];
});
echo "验证结果: " . ($result->isValid() ? '全部通过' : '有失败项') . "\n\n";
// 示例 4: 验证失败的情况
echo "示例 4: 验证失败的场景\n";
$failData = [
'age' => 200, // 超过了150
'percentage' => 100, // exclusive 模式下等于100会失败
];
$result = Validator::validateKeys($failData, function($v) {
return [
$v->key('age')->numeric()->max(150, true),
$v->key('percentage')->numeric()->max(100, false), // exclusive 模式
];
});
echo "验证结果: " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
echo "错误信息:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . (is_array($errors) ? implode(', ', $errors) : $errors) . "\n";
}
}
echo "\n";
// 示例 5: 自定义错误消息
echo "示例 5: 使用自定义错误消息\n";
$builder5 = new Builder();
$result = $builder5->numeric()->max(100, true, '分数不能超过100分')->validate(150);
echo "验证结果: " . ($result->isValid() ? '通过' : '失败') . "\n";
if ($result->isInvalid()) {
// 使用 firstError() 获取第一个错误
$errorMessage = $result->firstError();
echo "错误信息: {$errorMessage}\n";
}
echo "\n=== 示例结束 ===\n";
examples/numeric_validation.php 0000644 00000011346 15166406615 0012766 0 ustar 00 '99.99',
'quantity' => '10',
'discount' => '0.5'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('price')->numeric(),
$builder->key('quantity')->numeric(),
$builder->key('discount')->numeric()
];
});
if ($result->isValid()) {
echo "✓ 所有数字验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example2 = function() {
echo "示例2: 验证无效数字\n";
$data = [
'price' => 'not-a-number',
'quantity' => 'abc',
'discount' => null
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('price')->numeric(),
$builder->key('quantity')->numeric(),
$builder->key('discount')->numeric()
];
});
if ($result->isValid()) {
echo "✓ 验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example3 = function() {
echo "示例3: 使用 Builder 的 numeric() 方法\n";
$result = Validator::numeric()->validate('123.45');
if ($result->isValid()) {
echo "✓ 数字验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example4 = function() {
echo "示例4: 验证数字数组\n";
$data = [
'prices' => ['99.99', '10.50', 'abc', '25.00']
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('prices')->each()->numeric()
];
});
if ($result->isValid()) {
echo "✓ 所有数字验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example5 = function() {
echo "示例5: 自定义错误消息\n";
$data = [
'amount' => 'invalid'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('amount')->numeric('金额必须是数字')
];
});
if ($result->isValid()) {
echo "✓ 验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example6 = function() {
echo "示例6: 混合验证(numeric + integer)\n";
$data = [
'price' => '99.99',
'quantity' => '10',
'total' => '999.99'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('price')->numeric(),
$builder->key('quantity')->integer(),
$builder->key('total')->numeric()
];
});
if ($result->isValid()) {
echo "✓ 混合验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example7 = function() {
echo "示例7: 负数和零值验证\n";
$data = [
'balance' => '-100.50',
'discount' => '0',
'profit' => '250.75'
];
$result = Validator::validateKeys($data, function($builder) {
return [
$builder->key('balance')->numeric(),
$builder->key('discount')->numeric(),
$builder->key('profit')->numeric()
];
});
if ($result->isValid()) {
echo "✓ 负数和零值验证通过\n";
} else {
echo "✗ 验证失败:\n";
foreach ($result->errors()->all() as $field => $errors) {
echo " - {$field}: " . implode(', ', $errors) . "\n";
}
}
echo "\n";
};
$example1();
$example2();
$example3();
$example4();
$example5();
$example6();
$example7();
echo "=== Numeric 验证示例完成 ===\n";
examples/validate_or_fail.php 0000644 00000014406 15166406615 0012376 0 ustar 00 minLength(5)->validateOrFail('hello world');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n2. 基本验证 - 失败案例\n";
try {
$result = Validator::string()->minLength(5)->validateOrFail('hi');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n3. 手机号验证 - 成功案例\n";
try {
$result = Validator::phone()->validateOrFail('13800138000');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n4. 手机号验证 - 失败案例\n";
try {
$result = Validator::phone()->validateOrFail('invalid');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n5. 多规则验证 - 第一个规则失败\n";
try {
$result = Validator::string()
->minLength(5)
->phone()
->validateOrFail('hi');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
echo "注意:只返回第一个失败的规则错误\n";
}
echo "\n6. 多规则验证 - 第二个规则失败\n";
try {
$result = Validator::string()
->minLength(5)
->phone()
->validateOrFail('invalid phone');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
echo "注意:只返回第一个失败的规则错误\n";
}
echo "\n7. 数组字段验证 - 成功案例\n";
try {
$data = [
'username' => '张三',
'phone' => '13800138000',
'email' => 'test@example.com'
];
$result = Validator::validateKeysOrFail($data, function($validator) {
return [
$validator->key('username')->required()->chineseLength(2, 10),
$validator->key('phone')->required()->phone(),
$validator->key('email')->required()->email()
];
});
echo "✓ 所有字段验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n8. 数组字段验证 - 第一个字段失败\n";
try {
$data = [
'username' => '张',
'phone' => 'invalid',
'email' => 'invalid'
];
$result = Validator::validateKeysOrFail($data, function($validator) {
return [
$validator->key('username')->required()->chineseLength(2, 10),
$validator->key('phone')->required()->phone(),
$validator->key('email')->required()->email()
];
});
echo "✓ 所有字段验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
echo "注意:只返回第一个失败的字段错误\n";
}
echo "\n9. KeyValidator 验证 - 成功案例\n";
try {
$data = ['phone' => '13800138000'];
$result = Validator::make($data)
->key('phone')
->phone()
->validateOrFail();
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n10. KeyValidator 验证 - 失败案例\n";
try {
$data = ['phone' => 'invalid'];
$result = Validator::make($data)
->key('phone')
->phone()
->validateOrFail();
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n11. EachValidator 验证 - 成功案例\n";
try {
$data = ['phones' => ['13800138000', '13912345678', '18612345678']];
$result = Validator::make($data)
->key('phones')
->each()
->phone()
->validateOrFail();
echo "✓ 所有元素验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n12. EachValidator 验证 - 第一个元素失败\n";
try {
$data = ['phones' => ['invalid', '13912345678', '18612345678']];
$result = Validator::make($data)
->key('phones')
->each()
->phone()
->validateOrFail();
echo "✓ 所有元素验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
echo "注意:只返回第一个失败的元素错误\n";
}
echo "\n13. EachValidator 验证 - 第二个元素失败\n";
try {
$data = ['phones' => ['13800138000', 'invalid', '18612345678']];
$result = Validator::make($data)
->key('phones')
->each()
->phone()
->validateOrFail();
echo "✓ 所有元素验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
echo "注意:只返回第一个失败的元素错误\n";
}
echo "\n14. IP地址验证 - 成功案例\n";
try {
$result = Validator::ip('v4')->validateOrFail('192.168.1.1');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n15. IP地址验证 - 失败案例\n";
try {
$result = Validator::ip('v4')->validateOrFail('256.168.1.1');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n16. 域名验证 - 成功案例\n";
try {
$result = Validator::domain()->validateOrFail('example.com');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n17. 域名验证 - 失败案例\n";
try {
$result = Validator::domain()->validateOrFail('invalid');
echo "✓ 验证通过\n";
} catch (ValidationException $e) {
echo "✗ 验证失败: {$e->getMessage()}\n";
}
echo "\n=== 示例完成 ===\n";
echo "\n总结:\n";
echo "- validateOrFail() 方法在遇到第一个验证失败时立即抛出异常\n";
echo "- 不会继续验证后续规则或字段\n";
echo "- 适用于需要快速失败的场景\n";
echo "- 与 validate() 方法相比,validate() 会收集所有错误信息\n";
phpunit.xml 0000644 00000001024 15166406615 0006764 0 ustar 00
tests
src