PHP项目Laravel数组验证与嵌套数据

wen PHP项目 2

本文目录导读:

PHP项目Laravel数组验证与嵌套数据

  1. 基础数组验证
  2. 嵌套数组验证
  3. 数组嵌套验证的高级技巧
  4. 数据准备与验证前处理
  5. 验证嵌套数组的常用场景
  6. FormRequest 中的复杂验证示例
  7. 验证后的数据提取
  8. 使用自定义规则类
  9. 性能优化建议
  10. 常见陷阱和解决方案

在 Laravel 中处理数组验证和嵌套数据是非常常见的需求,让我详细介绍各种验证技巧和最佳实践。

基础数组验证

简单数组验证

$request->validate([
    'tags' => 'required|array',
    'tags.*' => 'required|string|max:50',
]);

数组长度验证

$request->validate([
    'items' => 'required|array|min:1|max:10',
]);

嵌套数组验证

多维数组验证

$request->validate([
    'users' => 'required|array',
    'users.*.name' => 'required|string|max:255',
    'users.*.email' => 'required|email|unique:users,email',
    'users.*.age' => 'required|integer|min:18',
    'users.*.addresses' => 'sometimes|array',
    'users.*.addresses.*.city' => 'required_with:users.*.addresses|string',
    'users.*.addresses.*.zip' => 'required_with:users.*.addresses|digits:5',
]);

使用 FormRequest 类

namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'customer' => 'required|array',
            'customer.name' => 'required|string|max:255',
            'customer.email' => 'required|email',
            'items' => 'required|array|min:1',
            'items.*.product_id' => 'required|exists:products,id',
            'items.*.quantity' => 'required|integer|min:1|max:99',
            'items.*.size' => 'nullable|in:S,M,L,XL',
            'items.*.extras' => 'array',
            'items.*.extras.*' => 'string|max:50',
        ];
    }
    public function messages(): array
    {
        return [
            'items.*.quantity.min' => '每个商品至少需要订购1件',
            'items.*.product_id.exists' => '商品不存在',
        ];
    }
}

数组嵌套验证的高级技巧

条件验证

$request->validate([
    'products' => 'array',
    'products.*.type' => 'required|in:physical,digital',
    // 物理商品需要重量
    'products.*.weight' => 'required_if:products.*.type,physical|numeric|min:0.1',
    // 数字商品需要下载链接
    'products.*.download_url' => 'required_if:products.*.type,digital|url',
]);

自定义验证规则

Validator::make($request->all(), [
    'selections' => [
        'required',
        'array',
        function ($attribute, $value, $fail) {
            // 检查数组中对值
            if (count($value) !== count(array_unique($value))) {
                $fail('选择项不能重复。');
            }
        },
    ],
]);

数组项之间验证

public function rules(): array
{
    return [
        'items' => 'required|array|min:2',
        'items.*.quantity' => 'required|integer|min:1',
    ];
}
public function withValidator($validator): void
{
    $validator->after(function ($validator) {
        $items = $this->input('items');
        // 检查总数量不超过库存
        $totalQuantity = array_sum(array_column($items, 'quantity'));
        if ($totalQuantity > 100) {
            $validator->errors()->add('items', '总数量不能超过100件。');
        }
    });
}

数据准备与验证前处理

使用 prepareForValidation

class UpdateProfileRequest extends FormRequest
{
    protected function prepareForValidation(): void
    {
        $this->merge([
            'interests' => is_string($this->interests) 
                ? explode(',', $this->interests) 
                : $this->interests,
            'settings' => [
                'notifications' => $this->boolean('notifications'),
                'theme' => $this->input('theme', 'light'),
            ],
        ]);
    }
}

验证嵌套数组的常用场景

JSON 数据验证

$request->validate([
    'data' => 'required|json',
]);
// 验证 JSON 解码后的数据
$data = json_decode($request->input('data'), true);
$validator = Validator::make($data, [
    'items' => 'required|array',
    'items.*.name' => 'required|string',
    'items.*.price' => 'required|numeric|min:0',
]);

动态字段验证

public function rules(): array
{
    $rules = [
        'type' => 'required|in:appointment,order,custom',
        'fields' => 'required|array',
    ];
    // 根据类型动态添加规则
    if ($this->input('type') === 'appointment') {
        $rules['fields.date'] = 'required|date|after:today';
        $rules['fields.time'] = 'required|date_format:H:i';
        $rules['fields.doctor'] = 'required|exists:doctors,id';
    }
    return $rules;
}

FormRequest 中的复杂验证示例

namespace App\Http\Requests;
class ProcessSurveyRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'survey_id' => 'required|exists:surveys,id',
            'respondent' => 'required|array',
            'respondent.age_group' => 'required|in:18-25,26-35,36-50,50+',
            'respondent.gender' => 'nullable|in:male,female,other',
            'answers' => 'required|array|min:1',
            'answers.*.question_id' => 'required|distinct|exists:questions,id',
            'answers.*.answer' => 'required_without:answers.*.answer_text',
            'answers.*.answer_text' => 'required_without:answers.*.answer',
            'answers.*.rating' => 'nullable|integer|between:1,5',
            'optional_questions' => 'sometimes|array',
            'optional_questions.*' => 'nullable|string|max:500',
        ];
    }
    public function withValidator($validator): void
    {
        $validator->after(function ($validator) {
            // 检查是否回答了所有必答题
            $survey = \App\Models\Survey::find($this->survey_id);
            $requiredQuestions = $survey->questions()->where('required', true)->pluck('id')->toArray();
            $answeredQuestions = array_column($this->answers, 'question_id');
            $missingQuestions = array_diff($requiredQuestions, $answeredQuestions);
            if (!empty($missingQuestions)) {
                $validator->errors()->add(
                    'answers',
                    '请回答所有必答题: ' . implode(', ', $missingQuestions)
                );
            }
        });
    }
    public function messages(): array
    {
        return [
            'answers.*.distinct' => '每个问题只能回答一次',
            'answers.*.rating.between' => '评分必须在1-5之间',
        ];
    }
}

验证后的数据提取

// 在控制器中
public function store(Request $request)
{
    $validated = $request->validate([...]);
    // 提取部分数据
    $userData = $request->safe()->only(['name', 'email']);
    $arrayData = $request->safe()->only(['items', 'tags']);
    // 排除某些字段
    $data = $request->safe()->except(['items', 'tags']);
}

使用自定义规则类

namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class ValidItemCombination implements Rule
{
    public function passes($attribute, $value)
    {
        // $value 是数组
        $categories = array_column($value, 'category');
        $names = array_column($value, 'name');
        // 检查组合是否唯一
        $combinations = array_map(function($cat, $name) {
            return $cat . '-' . $name;
        }, $categories, $names);
        return count($combinations) === count(array_unique($combinations));
    }
    public function message()
    {
        return '每个商品组合必须唯一。';
    }
}
// 使用
$request->validate([
    'items' => ['required', 'array', new ValidItemCombination],
]);

性能优化建议

// 避免过多嵌套层级
'items.*.details.specifications.technical.description' => 'required'; // 太深了
// 应该扁平化或拆分
'items.*.description' => 'required';

常见陷阱和解决方案

处理可选数组

// 错误方式(可能导致 null)
$request->validate([
    'tags.*' => 'string',
]);
// 正确方式
$request->validate([
    'tags' => 'sometimes|array',
    'tags.*' => 'string',
]);

数组索引连续性

$validator = Validator::make($request->all(), [
    'items' => 'required|array',
    'items.*' => 'required',
]);
// 检查索引是否连续
$validator->after(function ($validator) {
    $items = $this->input('items');
    $keys = array_keys($items);
    if ($keys !== range(0, count($items) - 1)) {
        // 可以重新索引
        $reindexed = array_values($items);
        $this->merge(['items' => $reindexed]);
    }
});

这些技巧可以帮助你处理几乎所有 Laravel 数组验证场景,关键是要根据项目实际需求选择合适的验证策略,并充分利用 Laravel 提供的验证功能。

抱歉,评论功能暂时关闭!