Validation使用示例.md 10 KB

Validation 使用示例

重要说明:动态属性规范

从 PHP 8.2+ 开始,动态属性被弃用。为了代码的健壮性和可维护性,我们要求:

❌ 不允许的做法

// 错误:直接给未定义的属性赋值(会产生弃用警告)
$this->validation->upgrade_config = $config;
$this->validation->land = $landObject;

✅ 推荐的做法

// 正确:在 Validation 类中预先定义属性并声明类型
class LandUpgradeValidation extends ValidationCore
{
    // 预定义属性,声明类型
    public ?FarmLandUpgradeConfig $upgrade_config = null;
    public ?FarmLand $land = null;

    // 验证规则...
}

规范要求

  1. 预定义属性:所有需要在验证器间传递的数据都必须预先在 Validation 类中定义
  2. 类型声明:所有属性都必须声明类型,使用 nullable 类型(?Type)允许初始值为 null
  3. 初始值:为属性设置合理的初始值(通常为 null
  4. 文档注释:为属性添加清晰的注释说明其用途

示例对比

旧的写法(不推荐):

// 在验证器中动态赋值
$this->validation->land = $land;
$this->validation->upgrade_config = $config;

新的写法(推荐):

// 1. 在 Validation 类中预定义
class LandUpgradeValidation extends ValidationCore
{
    /** @var FarmLand|null 土地对象,由 LandOwnershipValidator 设置 */
    public ?FarmLand $land = null;

    /** @var FarmLandUpgradeConfig|null 升级配置,由 LandUpgradePathValidator 设置 */
    public ?FarmLandUpgradeConfig $upgrade_config = null;
}

// 2. 在验证器中安全赋值
$this->validation->land = $land;
$this->validation->upgrade_config = $config;

基础使用示例

1. 创建 Validation 类

<?php

namespace App\Module\Farm\Validations;

use App\Module\Farm\Models\FarmLand;
use App\Module\Farm\Models\FarmLandUpgradeConfig;
use App\Module\Farm\Validators\LandOwnershipValidator;
use App\Module\Farm\Validators\LandUpgradeStatusValidator;
use App\Module\Farm\Validators\LandUpgradePathValidator;
use UCore\ValidationCore;

/**
 * 土地升级验证类
 */
class LandUpgradeValidation extends ValidationCore
{
    /** @var FarmLand|null 土地对象,由 LandOwnershipValidator 设置 */
    public ?FarmLand $land = null;

    /** @var FarmLandUpgradeConfig|null 升级配置,由 LandUpgradePathValidator 设置 */
    public ?FarmLandUpgradeConfig $upgrade_config = null;

    /**
     * 验证规则
     */
    public function rules($rules = []): array
    {
        return [
            // 基础验证
            ['user_id,land_id', 'required'],
            ['user_id,land_id', 'integer', 'min' => 1],

            // 业务验证(按顺序执行)
            [
                'land_id', new LandOwnershipValidator($this, ['user_id', 'land']),
                'msg' => '土地不存在或不属于当前用户'
            ],
            [
                'land_id', new LandUpgradeStatusValidator($this, ['land']),
                'msg' => '土地状态不允许升级'
            ],
            [
                'land_id', new LandUpgradePathValidator($this, ['user_id', 'upgrade_config']),
                'msg' => '升级路径验证失败'
            ],
        ];
    }

    /**
     * 默认值
     */
    public function default(): array
    {
        return [];
    }
}

2. 创建 Validator 类

<?php

namespace App\Module\Farm\Validators;

use App\Module\Farm\Enums\LAND_STATUS;
use App\Module\Farm\Models\FarmLand;
use UCore\Validator;

/**
 * 土地归属验证器
 */
class LandOwnershipValidator extends Validator
{
    /**
     * 验证土地归属
     *
     * @param mixed $value 土地ID
     * @param array $data 包含用户ID的数组
     * @return bool 验证是否通过
     */
    public function validate(mixed $value, array $data): bool
    {
        // 从 args 获取参数
        $userIdKey = $this->args[0] ?? 'user_id';
        $landKey = $this->args[1] ?? 'land';

        $userId = $data[$userIdKey] ?? null;

        if (!$userId) {
            $this->addError('用户ID不能为空');
            return false;
        }

        try {
            // 查询土地
            $land = FarmLand::where('id', $value)
                ->where('user_id', $userId)
                ->first();

            if (!$land) {
                $this->addError('土地不存在或不属于当前用户');
                return false;
            }

            // 将土地对象保存到验证对象中,供后续验证器使用
            $this->validation->$landKey = $land;

            return true;
        } catch (\Exception $e) {
            $this->addError('验证土地归属时发生错误: ' . $e->getMessage());
            return false;
        }
    }
}

3. 在 Handler 中使用

<?php

namespace App\Module\Farm\Handlers;

use App\Module\Farm\Validations\LandUpgradeValidation;
use App\Module\Farm\Logics\LandLogic;
use UCore\Handler;

/**
 * 土地升级处理器
 */
class LandUpgradeHandler extends Handler
{
    /**
     * 处理土地升级
     */
    public function handle(array $data): array
    {
        // 1. 验证数据
        $validation = new LandUpgradeValidation($data);
        $validation->validated();

        // 2. 获取验证后的数据和对象
        $userId = $validation->getSafe('user_id');
        $landId = $validation->getSafe('land_id');
        $land = $validation->land;  // 预定义的属性,类型安全
        $upgradeConfig = $validation->upgrade_config;  // 预定义的属性,类型安全

        // 3. 执行业务逻辑
        DB::transaction(function () use ($userId, $landId, $upgradeConfig) {
            $landLogic = new LandLogic();
            $landLogic->upgradeLand($userId, $landId, $upgradeConfig->to_type_id);
        });

        return ['success' => true, 'message' => '土地升级成功'];
    }
}

最佳实践

1. 属性定义规范

class ExampleValidation extends ValidationCore
{
    // ✅ 正确:预定义属性,声明类型,添加注释
    /** @var User|null 用户对象,由 UserValidator 设置 */
    public ?User $user = null;

    /** @var array<Item>|null 物品列表,由 ItemValidator 设置 */
    public ?array $items = null;

    /** @var Config|null 配置对象,由 ConfigValidator 设置 */
    public ?Config $config = null;

    // ❌ 错误:没有类型声明
    // public $user;

    // ❌ 错误:没有注释说明
    // public ?User $user = null;
}

2. Validator 中的属性赋值

class ExampleValidator extends Validator
{
    public function validate(mixed $value, array $data): bool
    {
        // 获取数据
        $user = User::find($value);

        if (!$user) {
            $this->addError('用户不存在');
            return false;
        }

        // ✅ 正确:赋值给预定义的属性
        $this->validation->user = $user;

        // ❌ 错误:动态属性赋值(会产生弃用警告)
        // $this->validation->dynamic_property = $user;

        return true;
    }
}

3. 验证器间数据传递

class FirstValidator extends Validator
{
    public function validate(mixed $value, array $data): bool
    {
        $user = User::find($value);

        // 第一个验证器设置用户对象
        $this->validation->user = $user;

        return true;
    }
}

class SecondValidator extends Validator
{
    public function validate(mixed $value, array $data): bool
    {
        // 第二个验证器使用第一个验证器设置的用户对象
        $user = $this->validation->user;

        if (!$user) {
            $this->addError('用户信息不存在,请先验证用户');
            return false;
        }

        // 使用用户对象进行验证...
        return $user->isActive();
    }
}

4. 类型安全的好处

class LandUpgradeValidation extends ValidationCore
{
    /** @var FarmLand|null */
    public ?FarmLand $land = null;

    /** @var FarmLandUpgradeConfig|null */
    public ?FarmLandUpgradeConfig $upgrade_config = null;
}

// 在 Handler 中使用
$validation = new LandUpgradeValidation($data);
$validation->validated();

// ✅ IDE 可以提供类型提示和自动完成
$landType = $validation->land->land_type;  // IDE 知道这是 FarmLand 对象
$targetType = $validation->upgrade_config->to_type_id;  // IDE 知道这是 FarmLandUpgradeConfig 对象

// ✅ 静态分析工具可以检查类型错误
// $validation->land->nonExistentProperty;  // IDE 会警告属性不存在

5. 迁移现有代码

如果你有使用动态属性的现有代码,按以下步骤迁移:

步骤 1:识别动态属性

# 搜索可能的动态属性赋值
grep -r "\$this->validation->" app/Module/*/Validators/

步骤 2:在 Validation 类中定义属性

// 在相应的 Validation 类中添加属性定义
class YourValidation extends ValidationCore
{
    /** @var SomeModel|null 描述这个属性的用途 */
    public ?SomeModel $property_name = null;
}

步骤 3:更新验证器代码

// 验证器中的赋值保持不变,但现在是类型安全的
$this->validation->property_name = $value;

步骤 4:测试验证

# 运行测试确保没有破坏现有功能
php artisan test

常见问题

Q: 为什么要禁用动态属性?

A:

  1. 类型安全:预定义属性可以声明类型,IDE 和静态分析工具可以提供更好的支持
  2. 代码可读性:明确的属性定义让代码更容易理解和维护
  3. 错误预防:避免因拼写错误导致的 bug
  4. PHP 兼容性:为 PHP 9.0 做准备(将完全禁止动态属性)

Q: 如何处理可选的验证器数据?

A: 使用 nullable 类型和默认值:

/** @var OptionalData|null 可选数据,可能为 null */
public ?OptionalData $optional_data = null;

Q: 如何处理数组类型的数据?

A: 使用泛型注释:

/** @var array<Item>|null 物品数组 */
public ?array $items = null;

/** @var array<string, mixed>|null 配置数组 */
public ?array $config = null;

总结

通过遵循这些规范和最佳实践,我们可以:

  1. 提高代码质量:类型安全的属性定义让代码更健壮
  2. 改善开发体验:IDE 可以提供更好的类型提示和错误检查
  3. 增强可维护性:明确的属性定义让代码更容易理解和维护
  4. 保证兼容性:为 PHP 9.0 的完全禁止动态属性做好准备

记住:预定义属性 + 类型声明 + 清晰注释 = 更好的代码