PHP filter_validate_deep() 函数

注意:filter_validate_deep() 不是PHP内置函数,而是本教程提供的自定义深度验证函数,用于验证复杂的数据结构。

定义和用法

filter_validate_deep() 函数是一个深度数据验证工具,可以对多维数组、嵌套对象等复杂数据结构进行递归验证。它扩展了PHP的过滤器功能,支持对嵌套数据的全面验证。

主要特点:

  • 支持多维数组的递归验证
  • 可以验证嵌套对象
  • 支持自定义验证规则
  • 返回详细的验证结果和错误信息
  • 可以处理复杂的业务逻辑验证

函数实现

<?php
/**
 * 深度数据验证函数
 *
 * @param mixed $data 要验证的数据(数组、对象或标量值)
 * @param mixed $rules 验证规则(数组或回调函数)
 * @param bool $strict 是否严格模式(严格模式下,未定义规则的字段将被过滤掉)
 * @return array 包含验证结果和信息的数组
 */
function filter_validate_deep($data, $rules, $strict = false) {
    $result = [
        'valid' => true,
        'data' => [],
        'errors' => [],
        'raw_errors' => []
    ];

    // 如果是标量值,直接应用验证规则
    if (!is_array($data) && !is_object($data)) {
        return validate_single_value($data, $rules);
    }

    // 将对象转换为数组以便处理
    if (is_object($data)) {
        $data = (array)$data;
    }

    // 处理验证规则
    foreach ($rules as $field => $fieldRules) {
        $fieldExists = array_key_exists($field, $data);
        $fieldValue = $fieldExists ? $data[$field] : null;

        // 如果字段不存在且规则不包含required,则跳过
        $hasRequiredRule = is_string($fieldRules) ?
            strpos($fieldRules, 'required') !== false :
            (is_array($fieldRules) && in_array('required', $fieldRules));

        if (!$fieldExists && !$hasRequiredRule) {
            if (!$strict) {
                // 非严格模式下,保留原始值
                $result['data'][$field] = null;
            }
            continue;
        }

        // 验证字段
        $validationResult = validate_field($fieldValue, $fieldRules, $field);

        if (!$validationResult['valid']) {
            $result['valid'] = false;
            $result['raw_errors'][$field] = $validationResult['errors'];
            $result['errors'][$field] = implode('; ', $validationResult['errors']);
        }

        $result['data'][$field] = $validationResult['value'];
    }

    // 非严格模式下,保留未在规则中定义的字段
    if (!$strict) {
        foreach ($data as $field => $value) {
            if (!array_key_exists($field, $rules)) {
                $result['data'][$field] = $value;
            }
        }
    }

    return $result;
}

/**
 * 验证单个字段
 */
function validate_field($value, $rules, $fieldName = '') {
    $result = [
        'valid' => true,
        'value' => $value,
        'errors' => []
    ];

    // 将规则字符串转换为数组
    if (is_string($rules)) {
        $rules = explode('|', $rules);
    }

    // 如果规则是数组且有嵌套规则,进行递归验证
    if (is_array($rules) && isset($rules['*'])) {
        if (is_array($value)) {
            $validatedArray = [];
            $arrayErrors = [];
            $allValid = true;

            foreach ($value as $key => $item) {
                $itemResult = validate_field($item, $rules['*'], "{$fieldName}[{$key}]");

                if (!$itemResult['valid']) {
                    $allValid = false;
                    $arrayErrors[$key] = $itemResult['errors'];
                }

                $validatedArray[$key] = $itemResult['value'];
            }

            $result['valid'] = $allValid;
            $result['value'] = $validatedArray;
            $result['errors'] = $arrayErrors;
            return $result;
        }
    }

    // 如果规则是数组且有深度规则(例如字段映射)
    if (is_array($rules) && !isset($rules['*'])) {
        // 检查是否有验证器配置
        $hasValidatorConfig = false;
        foreach ($rules as $key => $rule) {
            if (is_string($rule) || is_array($rule)) {
                $hasValidatorConfig = true;
                break;
            }
        }

        if ($hasValidatorConfig) {
            // 这是验证规则数组
            foreach ($rules as $rule) {
                if (is_string($rule)) {
                    $ruleResult = apply_validation_rule($value, $rule);
                    if (!$ruleResult['valid']) {
                        $result['valid'] = false;
                        $result['errors'][] = $ruleResult['error'];
                    } else {
                        $result['value'] = $ruleResult['value'];
                    }
                } elseif (is_array($rule) && isset($rule['rule'])) {
                    // 带参数的规则
                    $ruleResult = apply_validation_rule($value, $rule['rule'], $rule['params'] ?? []);
                    if (!$ruleResult['valid']) {
                        $result['valid'] = false;
                        $result['errors'][] = $ruleResult['error'];
                    } else {
                        $result['value'] = $ruleResult['value'];
                    }
                }
            }
        } else {
            // 这是嵌套规则,递归验证
            if (is_array($value)) {
                $nestedResult = filter_validate_deep($value, $rules, false);
                $result['valid'] = $nestedResult['valid'];
                $result['value'] = $nestedResult['data'];
                $result['errors'] = $nestedResult['raw_errors'];
            } else {
                $result['valid'] = false;
                $result['errors'][] = "{$fieldName} 应该是数组";
            }
        }

        return $result;
    }

    // 应用简单的规则数组
    if (is_array($rules) && isset($rules[0])) {
        foreach ($rules as $rule) {
            $ruleResult = apply_validation_rule($value, $rule);
            if (!$ruleResult['valid']) {
                $result['valid'] = false;
                $result['errors'][] = $ruleResult['error'];
            } else {
                $result['value'] = $ruleResult['value'];
            }
        }
        return $result;
    }

    // 如果规则是回调函数
    if (is_callable($rules)) {
        $validationResult = call_user_func($rules, $value);
        if ($validationResult === false) {
            $result['valid'] = false;
            $result['errors'][] = "自定义验证失败";
        } elseif (is_array($validationResult)) {
            $result['valid'] = $validationResult['valid'] ?? false;
            if (isset($validationResult['value'])) {
                $result['value'] = $validationResult['value'];
            }
            if (isset($validationResult['error'])) {
                $result['errors'][] = $validationResult['error'];
            }
        }
        return $result;
    }

    return $result;
}

/**
 * 应用单个验证规则
 */
function apply_validation_rule($value, $rule, $params = []) {
    $result = [
        'valid' => true,
        'value' => $value,
        'error' => ''
    ];

    $ruleParts = explode(':', $rule, 2);
    $ruleName = $ruleParts[0];
    $ruleParam = isset($ruleParts[1]) ? $ruleParts[1] : null;

    switch ($ruleName) {
        case 'required':
            if (empty($value) && $value !== '0' && $value !== 0) {
                $result['valid'] = false;
                $result['error'] = '字段是必填的';
            }
            break;

        case 'email':
            if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                $result['valid'] = false;
                $result['error'] = '邮箱格式无效';
            }
            break;

        case 'url':
            if (!filter_var($value, FILTER_VALIDATE_URL)) {
                $result['valid'] = false;
                $result['error'] = 'URL格式无效';
            }
            break;

        case 'int':
            $options = [];
            if ($ruleParam) {
                $range = explode(',', $ruleParam);
                if (count($range) >= 2) {
                    $options['options'] = [
                        'min_range' => (int)$range[0],
                        'max_range' => (int)$range[1]
                    ];
                }
            }
            $filtered = filter_var($value, FILTER_VALIDATE_INT, $options);
            if ($filtered === false) {
                $result['valid'] = false;
                $result['error'] = $ruleParam ? "整数必须在{$range[0]}-{$range[1]}之间" : '必须是有效的整数';
            } else {
                $result['value'] = $filtered;
            }
            break;

        case 'float':
            $filtered = filter_var($value, FILTER_VALIDATE_FLOAT);
            if ($filtered === false) {
                $result['valid'] = false;
                $result['error'] = '必须是有效的浮点数';
            } else {
                $result['value'] = $filtered;
            }
            break;

        case 'boolean':
            $filtered = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
            if ($filtered === null) {
                $result['valid'] = false;
                $result['error'] = '必须是有效的布尔值';
            } else {
                $result['value'] = $filtered;
            }
            break;

        case 'string':
            if (!is_string($value)) {
                $result['valid'] = false;
                $result['error'] = '必须是字符串';
            }
            break;

        case 'array':
            if (!is_array($value)) {
                $result['valid'] = false;
                $result['error'] = '必须是数组';
            }
            break;

        case 'min':
            if (is_numeric($value)) {
                if ($value < (float)$ruleParam) {
                    $result['valid'] = false;
                    $result['error'] = "不能小于{$ruleParam}";
                }
            } elseif (is_string($value)) {
                if (strlen($value) < (int)$ruleParam) {
                    $result['valid'] = false;
                    $result['error'] = "长度不能少于{$ruleParam}个字符";
                }
            }
            break;

        case 'max':
            if (is_numeric($value)) {
                if ($value > (float)$ruleParam) {
                    $result['valid'] = false;
                    $result['error'] = "不能大于{$ruleParam}";
                }
            } elseif (is_string($value)) {
                if (strlen($value) > (int)$ruleParam) {
                    $result['valid'] = false;
                    $result['error'] = "长度不能超过{$ruleParam}个字符";
                }
            }
            break;

        case 'regex':
            if (!preg_match($ruleParam, $value)) {
                $result['valid'] = false;
                $result['error'] = '格式不符合要求';
            }
            break;

        case 'in':
            $allowed = explode(',', $ruleParam);
            if (!in_array($value, $allowed)) {
                $result['valid'] = false;
                $result['error'] = "值必须是: " . implode(', ', $allowed);
            }
            break;

        case 'date':
            if (!strtotime($value)) {
                $result['valid'] = false;
                $result['error'] = '必须是有效的日期';
            }
            break;

        case 'ip':
            if (!filter_var($value, FILTER_VALIDATE_IP)) {
                $result['valid'] = false;
                $result['error'] = '必须是有效的IP地址';
            }
            break;

        case 'callback':
            if (is_callable($ruleParam) && !call_user_func($ruleParam, $value)) {
                $result['valid'] = false;
                $result['error'] = '自定义验证失败';
            }
            break;

        default:
            // 尝试使用PHP内置过滤器
            $filterId = filter_id($ruleName);
            if ($filterId !== false) {
                $filtered = filter_var($value, $filterId);
                if ($filtered === false) {
                    $result['valid'] = false;
                    $result['error'] = "{$ruleName}验证失败";
                } else {
                    $result['value'] = $filtered;
                }
            }
            break;
    }

    return $result;
}

/**
 * 验证单个值
 */
function validate_single_value($value, $rules) {
    if (is_string($rules) || is_array($rules)) {
        $result = validate_field($value, $rules);
        return [
            'valid' => $result['valid'],
            'data' => $result['value'],
            'errors' => $result['valid'] ? [] : [implode('; ', $result['errors'])],
            'raw_errors' => $result['valid'] ? [] : [$result['errors']]
        ];
    }

    if (is_callable($rules)) {
        $validationResult = call_user_func($rules, $value);
        if ($validationResult === true) {
            return [
                'valid' => true,
                'data' => $value,
                'errors' => [],
                'raw_errors' => []
            ];
        } elseif (is_array($validationResult)) {
            return [
                'valid' => $validationResult['valid'] ?? false,
                'data' => $validationResult['value'] ?? $value,
                'errors' => $validationResult['valid'] ? [] : [$validationResult['error'] ?? '验证失败'],
                'raw_errors' => $validationResult['valid'] ? [] : [[$validationResult['error'] ?? '验证失败']]
            ];
        }
    }

    return [
        'valid' => false,
        'data' => $value,
        'errors' => ['无效的验证规则'],
        'raw_errors' => [['无效的验证规则']]
    ];
}
?>

函数参数

参数 描述
$data 要验证的数据。可以是数组、对象或标量值。
$rules

验证规则。支持多种格式:

  • 简单规则'required|email|max:100'
  • 规则数组['required', 'email', 'max:100']
  • 嵌套规则:关联数组,用于验证嵌套结构
  • 数组元素规则'*' => 'required|int' 验证数组所有元素
  • 回调函数:自定义验证函数
$strict 严格模式(可选,默认false)。
  • true:只保留规则中定义的字段
  • false:保留所有原始字段,包括未定义规则的字段

返回值

函数返回一个包含以下键的关联数组:

[
    'valid'      => true,      // 布尔值:整体验证是否通过
    'data'       => array,     // 验证后的数据
    'errors'     => array,     // 简化的错误信息数组(字段名 => 错误信息)
    'raw_errors' => array      // 原始错误信息数组(字段名 => 错误数组)
]

示例

示例 1:基本多维数组验证

<?php
// 包含上面的自定义函数定义
require_once 'filter_validate_deep.php';

// 测试数据:用户信息数组
$userData = [
    'username' => 'john_doe',
    'email' => 'john@example.com',
    'age' => 25,
    'profile' => [
        'first_name' => 'John',
        'last_name' => 'Doe',
        'bio' => 'Software developer with 5 years experience.',
        'address' => [
            'street' => '123 Main St',
            'city' => 'New York',
            'zip' => '10001'
        ]
    ],
    'hobbies' => ['coding', 'reading', 'hiking'],
    'scores' => [85, 92, 78, 95]
];

// 验证规则
$validationRules = [
    'username' => 'required|min:3|max:20',
    'email' => 'required|email',
    'age' => 'required|int:18,100',
    'profile' => [
        'first_name' => 'required|min:2|max:50',
        'last_name' => 'required|min:2|max:50',
        'bio' => 'max:500',
        'address' => [
            'street' => 'required',
            'city' => 'required',
            'zip' => 'required|regex:/^\d{5}$/'
        ]
    ],
    'hobbies' => [
        '*' => 'string|min:2|max:50'  // 验证数组中的所有元素
    ],
    'scores' => [
        '*' => 'int:0,100'  // 验证所有分数在0-100之间
    ]
];

// 执行深度验证
$result = filter_validate_deep($userData, $validationRules);

// 显示验证结果
echo "<h4>验证结果:</h4>";
echo "整体验证状态: " . ($result['valid'] ? '<span class="text-success">通过</span>' : '<span class="text-danger">失败</span>') . "<br><br>";

echo "<h5>验证后的数据:</h5>";
echo "<pre>" . print_r($result['data'], true) . "</pre>";

if (!empty($result['errors'])) {
    echo "<h5>错误信息:</h5>";
    echo "<ul>";
    foreach ($result['errors'] as $field => $error) {
        echo "<li><strong>$field</strong>: $error</li>";
    }
    echo "</ul>";
}
?>

示例 2:表单数据深度验证

<?php
// 复杂的表单数据:订单表单
$orderData = [
    'customer' => [
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'phone' => '+1234567890',
        'address' => [
            'street' => '456 Oak Ave',
            'city' => 'Los Angeles',
            'state' => 'CA',
            'zip' => '90001'
        ]
    ],
    'items' => [
        [
            'product_id' => 101,
            'name' => 'Laptop',
            'quantity' => 1,
            'price' => 999.99
        ],
        [
            'product_id' => 205,
            'name' => 'Mouse',
            'quantity' => 2,
            'price' => 25.50
        ]
    ],
    'payment' => [
        'method' => 'credit_card',
        'card_number' => '4111111111111111',
        'expiry' => '12/25',
        'cvv' => '123'
    ],
    'discount_code' => 'SAVE10',
    'notes' => 'Please deliver after 5 PM.'
];

// 验证规则
$orderRules = [
    'customer' => [
        'name' => 'required|min:2|max:100',
        'email' => 'required|email',
        'phone' => 'required|regex:/^\+?[0-9]{10,15}$/',
        'address' => [
            'street' => 'required',
            'city' => 'required',
            'state' => 'required|in:AL,AK,AZ,AR,CA,CO,CT,DE,FL,GA,HI,ID,IL,IN,IA,KS,KY,LA,ME,MD,MA,MI,MN,MS,MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,OH,OK,OR,PA,RI,SC,SD,TN,TX,UT,VT,VA,WA,WV,WI,WY',
            'zip' => 'required|regex:/^\d{5}$/'
        ]
    ],
    'items' => [
        '*' => [
            'product_id' => 'required|int',
            'name' => 'required|string|min:1|max:200',
            'quantity' => 'required|int:1,100',
            'price' => 'required|float|min:0'
        ]
    ],
    'payment' => [
        'method' => 'required|in:credit_card,paypal,bank_transfer',
        'card_number' => 'required_if:method,credit_card|regex:/^\d{16}$/',
        'expiry' => 'required_if:method,credit_card|regex:/^(0[1-9]|1[0-2])\/\d{2}$/',
        'cvv' => 'required_if:method,credit_card|regex:/^\d{3,4}$/'
    ],
    'discount_code' => 'max:20',
    'notes' => 'max:500'
];

// 自定义验证规则:检查信用卡有效期
function validate_card_expiry($expiry) {
    list($month, $year) = explode('/', $expiry);
    $currentYear = date('y');
    $currentMonth = date('m');

    if ($year < $currentYear) {
        return ['valid' => false, 'error' => '信用卡已过期'];
    }

    if ($year == $currentYear && $month < $currentMonth) {
        return ['valid' => false, 'error' => '信用卡已过期'];
    }

    return ['valid' => true, 'value' => $expiry];
}

// 修改规则,使用自定义验证
$orderRules['payment']['expiry'] = function($value) {
    // 先验证格式
    if (!preg_match('/^(0[1-9]|1[0-2])\/\d{2}$/', $value)) {
        return ['valid' => false, 'error' => '有效期格式无效,应为MM/YY'];
    }
    // 然后验证是否过期
    return validate_card_expiry($value);
};

// 执行深度验证
$result = filter_validate_deep($orderData, $orderRules);

echo "<h4>订单表单验证结果:</h4>";
echo "整体验证状态: " . ($result['valid'] ? '<span class="text-success">通过</span>' : '<span class="text-danger">失败</span>') . "<br><br>";

if (!$result['valid']) {
    echo "<h5>验证错误:</h5>";
    echo "<div class='alert alert-danger'>";

    function display_errors($errors, $prefix = '') {
        foreach ($errors as $field => $error) {
            if (is_array($error)) {
                display_errors($error, $prefix . $field . '.');
            } else {
                echo "<p><strong>" . $prefix . $field . "</strong>: $error</p>";
            }
        }
    }

    display_errors($result['raw_errors']);
    echo "</div>";
} else {
    echo "<div class='alert alert-success'>";
    echo "<h5>所有数据验证通过!</h5>";
    echo "<p>订单数据已准备就绪,可以继续处理。</p>";
    echo "</div>";
}
?>

示例 3:API响应数据验证

<?php
// 模拟API返回的复杂JSON数据
$apiResponse = [
    'status' => 'success',
    'data' => [
        'users' => [
            [
                'id' => 1,
                'username' => 'john_doe',
                'email' => 'john@example.com',
                'profile' => [
                    'avatar' => 'https://example.com/avatar1.jpg',
                    'joined_at' => '2020-01-15'
                ],
                'permissions' => ['read', 'write', 'delete']
            ],
            [
                'id' => 2,
                'username' => 'jane_smith',
                'email' => 'jane@example.com',
                'profile' => [
                    'avatar' => 'https://example.com/avatar2.jpg',
                    'joined_at' => '2020-03-22'
                ],
                'permissions' => ['read']
            ]
        ],
        'pagination' => [
            'total' => 2,
            'page' => 1,
            'per_page' => 10
        ]
    ],
    'meta' => [
        'timestamp' => '2023-10-01T12:00:00Z',
        'version' => '1.0'
    ]
];

// API响应验证规则
$apiRules = [
    'status' => 'required|in:success,error',
    'data' => [
        'users' => [
            '*' => [
                'id' => 'required|int',
                'username' => 'required|min:3|max:50',
                'email' => 'required|email',
                'profile' => [
                    'avatar' => 'required|url',
                    'joined_at' => 'required|date'
                ],
                'permissions' => [
                    '*' => 'in:read,write,delete,admin'
                ]
            ]
        ],
        'pagination' => [
            'total' => 'required|int:0',
            'page' => 'required|int:1',
            'per_page' => 'required|int:1,100'
        ]
    ],
    'meta' => [
        'timestamp' => 'required|date',
        'version' => 'required|regex:/^\d+\.\d+$/'
    ]
];

// 执行验证
$result = filter_validate_deep($apiResponse, $apiRules);

echo "<h4>API响应验证:</h4>";
echo "验证状态: " . ($result['valid'] ? '<span class="badge bg-success">有效</span>' : '<span class="badge bg-danger">无效</span>') . "<br><br>";

if ($result['valid']) {
    echo "<div class='alert alert-success'>";
    echo "<h5>API响应格式正确</h5>";
    echo "<p>数据已验证,可以安全使用。</p>";
    echo "</div>";

    // 显示验证后的数据摘要
    echo "<h5>数据摘要:</h5>";
    echo "状态: {$result['data']['status']}<br>";
    echo "用户数量: " . count($result['data']['data']['users']) . "<br>";
    echo "分页信息: 第{$result['data']['data']['pagination']['page']}页,";
    echo "每页{$result['data']['data']['pagination']['per_page']}条,";
    echo "共{$result['data']['data']['pagination']['total']}条<br>";
    echo "API版本: {$result['data']['meta']['version']}<br>";
} else {
    echo "<div class='alert alert-danger'>";
    echo "<h5>API响应格式错误</h5>";

    // 显示详细的错误信息
    function flatten_errors($errors, $path = '') {
        $flat = [];
        foreach ($errors as $key => $error) {
            $currentPath = $path ? $path . '.' . $key : $key;
            if (is_array($error)) {
                $flat = array_merge($flat, flatten_errors($error, $currentPath));
            } else {
                $flat[$currentPath] = $error;
            }
        }
        return $flat;
    }

    $flatErrors = flatten_errors($result['raw_errors']);
    echo "<ul>";
    foreach ($flatErrors as $path => $error) {
        if (is_array($error)) {
            foreach ($error as $err) {
                echo "<li><strong>$path</strong>: $err</li>";
            }
        } else {
            echo "<li><strong>$path</strong>: $error</li>";
        }
    }
    echo "</ul>";
    echo "</div>";
}
?>

示例 4:严格模式与非严格模式

<?php
// 测试数据
$data = [
    'name' => 'John',
    'email' => 'john@example.com',
    'age' => 25,
    'extra_field1' => '应该被过滤掉',
    'extra_field2' => '在非严格模式下保留'
];

// 验证规则
$rules = [
    'name' => 'required|min:2',
    'email' => 'required|email',
    'age' => 'int:18,100'
];

echo "<h4>严格模式 vs 非严格模式</h4>";

// 非严格模式(默认)
echo "<h5>非严格模式(strict=false):</h5>";
$result1 = filter_validate_deep($data, $rules, false);
echo "验证状态: " . ($result1['valid'] ? '通过' : '失败') . "<br>";
echo "验证后字段数: " . count($result1['data']) . "<br>";
echo "字段列表: " . implode(', ', array_keys($result1['data'])) . "<br><br>";

// 严格模式
echo "<h5>严格模式(strict=true):</h5>";
$result2 = filter_validate_deep($data, $rules, true);
echo "验证状态: " . ($result2['valid'] ? '通过' : '失败') . "<br>";
echo "验证后字段数: " . count($result2['data']) . "<br>";
echo "字段列表: " . implode(', ', array_keys($result2['data'])) . "<br><br>";

// 比较结果
echo "<h5>比较结果:</h5>";
echo "<table class='table table-bordered'>";
echo "<thead><tr><th>字段</th><th>原始值</th><th>非严格模式</th><th>严格模式</th></tr></thead>";
echo "<tbody>";

$fields = array_unique(array_merge(array_keys($data), array_keys($result1['data']), array_keys($result2['data'])));
foreach ($fields as $field) {
    $original = isset($data[$field]) ? htmlspecialchars($data[$field]) : '-';
    $nonStrict = isset($result1['data'][$field]) ? htmlspecialchars($result1['data'][$field]) : '-';
    $strict = isset($result2['data'][$field]) ? htmlspecialchars($result2['data'][$field]) : '-';

    echo "<tr>";
    echo "<td>$field</td>";
    echo "<td>$original</td>";
    echo "<td>$nonStrict</td>";
    echo "<td>$strict</td>";
    echo "</tr>";
}

echo "</tbody></table>";

echo "<div class='alert alert-info'>";
echo "<strong>说明:</strong><br>";
echo "1. 非严格模式保留所有原始字段,包括未定义验证规则的字段<br>";
echo "2. 严格模式只保留验证规则中定义的字段<br>";
echo "3. 严格模式更适合处理外部数据,确保只接收预期的字段";
echo "</div>";
?>

示例 5:自定义规则和错误消息

<?php
// 自定义验证函数
function validate_strong_password($value) {
    if (strlen($value) < 8) {
        return ['valid' => false, 'error' => '密码至少需要8个字符'];
    }
    if (!preg_match('/[A-Z]/', $value)) {
        return ['valid' => false, 'error' => '密码必须包含至少一个大写字母'];
    }
    if (!preg_match('/[a-z]/', $value)) {
        return ['valid' => false, 'error' => '密码必须包含至少一个小写字母'];
    }
    if (!preg_match('/[0-9]/', $value)) {
        return ['valid' => false, 'error' => '密码必须包含至少一个数字'];
    }
    if (!preg_match('/[^A-Za-z0-9]/', $value)) {
        return ['valid' => false, 'error' => '密码必须包含至少一个特殊字符'];
    }
    return ['valid' => true, 'value' => $value];
}

function validate_username_unique($value) {
    // 模拟检查用户名是否唯一
    $existingUsernames = ['admin', 'user', 'test', 'john_doe'];
    if (in_array($value, $existingUsernames)) {
        return ['valid' => false, 'error' => '用户名已存在'];
    }
    return ['valid' => true, 'value' => $value];
}

// 测试数据
$userInput = [
    'username' => 'new_user',
    'password' => 'WeakPass123',
    'profile' => [
        'display_name' => 'New User',
        'birth_date' => '2000-01-01',
        'custom_fields' => [
            'favorite_color' => 'blue',
            'hobby' => 'coding'
        ]
    ]
];

// 使用自定义规则的验证
$customRules = [
    'username' => [
        'required',
        'min:3',
        'max:20',
        'regex:/^[a-zA-Z0-9_]+$/',
        'callback:validate_username_unique'
    ],
    'password' => function($value) {
        return validate_strong_password($value);
    },
    'profile' => [
        'display_name' => 'required|min:2|max:50',
        'birth_date' => [
            'required',
            'date',
            function($value) {
                // 验证年龄至少18岁
                $birthDate = new DateTime($value);
                $today = new DateTime();
                $age = $today->diff($birthDate)->y;
                if ($age < 18) {
                    return ['valid' => false, 'error' => '必须年满18岁'];
                }
                return ['valid' => true, 'value' => $value];
            }
        ],
        'custom_fields' => [
            'favorite_color' => 'in:red,blue,green,yellow',
            'hobby' => 'max:100'
        ]
    ]
];

// 执行验证
$result = filter_validate_deep($userInput, $customRules);

echo "<h4>自定义规则验证:</h4>";
echo "验证状态: " . ($result['valid'] ? '<span class="text-success">通过</span>' : '<span class="text-danger">失败</span>') . "<br><br>";

if (!$result['valid']) {
    echo "<div class='alert alert-danger'>";
    echo "<h5>验证错误:</h5>";

    // 递归显示错误
    function display_nested_errors($errors, $indent = '') {
        foreach ($errors as $field => $error) {
            if (is_array($error)) {
                echo "$indent<strong>$field</strong>:<br>";
                display_nested_errors($error, $indent . '    ');
            } elseif (is_array($error) && isset($error[0])) {
                echo "$indent<strong>$field</strong>: " . implode('; ', $error) . "<br>";
            } else {
                echo "$indent<strong>$field</strong>: $error<br>";
            }
        }
    }

    display_nested_errors($result['raw_errors']);
    echo "</div>";
} else {
    echo "<div class='alert alert-success'>";
    echo "<h5>所有验证通过!</h5>";
    echo "用户数据已验证,可以安全存储。";
    echo "</div>";

    echo "<h5>验证后的数据:</h5>";
    echo "<pre>" . print_r($result['data'], true) . "</pre>";
}
?>

注意事项

  • 非内置函数:这不是PHP内置函数,需要自行实现或包含
  • 性能考虑:深度验证复杂数据结构可能影响性能,特别是大型数组
  • 递归深度:PHP有递归深度限制,超深嵌套可能导致错误
  • 错误处理:复杂的验证规则需要仔细设计错误信息
  • 规则设计:验证规则设计要清晰,避免循环引用
  • 安全性:自定义回调函数要注意安全性,避免代码注入
  • 测试:深度验证函数需要全面测试,特别是边界情况
  • 内存使用:验证大型数据集时注意内存消耗

最佳实践

  1. 分层次验证:先验证数据结构,再验证具体数据
  2. 使用缓存:对于重复的验证规则,考虑缓存验证结果
  3. 渐进式验证:对于复杂表单,可以分步骤验证
  4. 清晰的错误信息:为用户提供明确的错误指导
  5. 记录验证日志:重要数据的验证失败应记录日志
  6. 性能监控:监控验证函数的执行时间和内存使用
  7. 单元测试:为验证规则编写全面的单元测试
  8. 文档化规则:维护验证规则的文档,便于团队理解

相关函数