one234ru/form-validator

HTML 表单验证工具,客户端和服务器端(PHP)

v1.0.1 2024-05-20 00:28 UTC

This package is auto-updated.

Last update: 2024-09-20 01:11:38 UTC


README

检查的结果是一个错误数据数组,用于发送到浏览器。

客户端部分描述见此处

错误数组的形式

[
  {
    "name": "имя поля (атрибут name в HTML-коде)",
    "value": "значение",
    "messages": [
      "текст ошибки",
      "текст другой ошибки"
    ]
  },
  {
    ...
  }
]

如果表单填写正确,数组为空。

检查基于特殊的配置和 HTTP 请求参数。

require 'Validator.php';
$config = [...];
$values = $_GET;
$obj = new One234ru\FormValidator($config, $values);
$obj->validate($values);
// $obj->errors будет содержать массив ошибок 

标准检查

假设表单中有三个字段——姓名、电话和电子邮件,分别对应 namephoneemail。那么检查配置的形式如下

$config = [
    'name' => ..., // здесь инструкции для проверки
    'phone' => ...,
    'email' => ...
];

数组键必须与 HTML 代码中字段的 name 属性值相匹配。

简单的填充检查

这个检查的唯一参数是错误文本,如果字段未填写或完全不存在于 HTTP 请求参数中,将显示给用户。如果字段的值是 0 而不是空字符串,则检查将不会通过。在检查之前会删除前后空格。

存在两种声明检查的方式:简短形式——字符串形式,和完整形式——键为 * 的数组形式。

假设我们要求填写姓名和电话,而电子邮件是可选的。

在下面的例子中,name 字段使用简短形式,而 phone 使用完整形式

$config = [
    'name' => 'Укажите имя.',
    'phone' => [
        '*' => 'Укажите телефон.'
    ],
    'email' => []
];

在这个例子中,没有为 email 字段指定检查指令,因此其内容不会影响检查结果。

看看如果发送空 HTTP 请求进行检查会发生什么

$obj = new One234ru\FormValidator($config);
$values = []; // имитируем пустой запрос
$obj->validate($values);
echo json_encode($obj->errors, JSON_UNESCAPED_UNICODE); 

结果(格式化以便于阅读)

[
  {
    "name": "name",
    "value": null,
    "messages": [
      "Укажите имя."
    ]
  },
  {
    "name": "phone",
    "value": null,
    "messages": [
      "Укажите телефон."
    ]
  }
]

可以看到,每个字段都有一个错误信息列表的数组。在所有情况下,value 都包含 null,因为 HTTP 请求中不存在相应的元素。

简短形式和完整形式的记录作用完全相同。通常如果没有其他检查,使用简短形式更方便。

使用正则表达式检查内容

现在假设我们不仅想要检查字段是否填写,而且要确保它们包含格式正确的值。例如,电话必须严格由十个数字组成,而电子邮件必须符合特定的格式。

这可以通过正则表达式检查来实现。为了设置这样的检查,需要在检查数组中添加一个元素,其键是正则表达式(分隔符为 /),其值是错误文本

$config = [
    // проверку name здесь и далее опустим
    'phone' => [
        '*' => 'Укажите телефон.',
        '/^\d{10}$/' => 'Телефон следует указывать в виде 10 цифр.',
    ],
    'email' => [
        '/^[\.\-\w]+@(\w+\-)*\.[A-z]{2}$/' => '"{*value*}" не является адресом электронной почты.',
    ]
];

错误文本中的 {*value*} 标记将被字段值(通过 htmlspecialchars() 编码)替换。

看看错误将如何显示

$values = [
    'phone' => '1234',
    'email' => 'somebody@',
];
[
  {
    "name": "phone",
    "value": "1234",
    "messages": [
      "Телефон следует указывать в виде 10 цифр."
    ]
  },
  {
    "name": "email",
    "value": "somebody@",
    "messages": [
      "\"somebody@\" не является адресом электронной почты."
    ]
  }
]

正则表达式检查仅在通过填充检查的情况下执行。这很容易验证

$values = [];
[
  {
    "name": "phone",
    "value": null,
    "messages": [
      "Укажите телефон."
    ]
  }
]

因此,email 字段仍然是可选的,并且只有在它包含非空值时才会进行检查。

使用任意函数的检查

这种检查分为两种类型——初级二级

二级 检查仅在成功通过该字段的全部其他检查后执行。

假设需要检查表单中输入的电话号码是否在某个数据库中。

    'phone' => [
        '*' => 'Укажите телефон.',
        '/^\d{10}$/' => 'Телефон следует указывать в виде 10 цифр.',
        function($value) {
            $found = ...; // тут ищем в базе данных
            return (!$found)
                ? "Телефон $value не значится в наших списках."
                : "";
            // Т.к. телефон уже проверялся регулярным выражением,
            // обрабатывать его htmlspecialchars()
            // перед вставкой в текст ошибки ни к чему.
        }
    ]

从例子中可以看出,函数的参数是字段值。函数应该返回错误信息字符串,如果没有错误则返回空值。

$values = [ 'phone' => '1234567890' ];
[
  {
    "name": "phone",
    "value": "1234567890",
    "messages": [
      "Телефон 1234567890 не значится в наших списках."
    ]
  }
]

初级检查总是执行,无论字段是否填写和是否有其他检查。

它们的声明仅有一点不同,即在数组中使用 '*' 代替无名称键元素。还有简化的记录形式

// вторичная проверка
'phone' => [
    function($value) {...}
]

// первичная проверка, полная форма
'phone' => [
    '*' => function($value) {...}
]

// первичная проверка, сокращенная форма
'phone' => function($value) {...}

在完整形式中,初级检查可以与其他检查结合。

假设我们决定在一个函数内部本地化所有电话检查。以下是它的样子

'phone' => function($value) {
    if (empty($value)) {
        $message = "Укажите телефон.";
    } elseif (!preg_match('/^\d{10}$/', $value)) {
        $message = "Телефон следует указывать в виде 10 цифр.";
    } elseif (!($found = ...)) {
        $message = "Телефон $value не значится в наших списках.";
    } else {
        $message = "";
    }
    return $message;
}

检查结果的数据格式保持不变。它也不依赖于记录形式——简短或完整。

汇总检查

可以根据多个字段的值构建检查。

使用将函数放在配置顶级无名称元素内部的函数来声明这些检查。这些函数接受完整的HTTP请求参数数组作为参数。

此函数的结果是发现错误列表。列表中的每个错误都以以下元素表示的数组形式出现

  • name —— 字段名称
  • value —— 字段值
  • message —— 错误信息文本

假设在我们的示例中,某个数据库中的每个电话号码都对应一个特定的电子邮件地址,并且当填写表单时需要检查这种对应关系。以下是如何进行此类检查的示例

$config = [
    ...
    [
        function ($query) {
            if (...) { // тут проверяем телефон и email
                $errors[] = [
                    'name' => 'email',
                    'value' => $query['email'],
                    'message' => "К телефону $query[phone] привязан другой email."
                ];
            } 
            return $errors ?? [];
        }
    ]
];
$values = [
    'phone' => '1234567890',
    'email' => 'someone@somewhere.ru',
];

结果

[
  {
    "name": "email",
    "value": "someone@somewhere.ru",
    "message": "К телефону 1234567890 привязан другой email."
  }
]

与单个字段的检查类似,总检查也有初级和次级:初级总是执行,次级仅在所有其他检查成功完成后执行。

它们的声明与单个字段的情况一样:初级以键 '*' 声明,次级以无名称数字键声明

$config = [
    ...,
    [
        '*' => function($query) { ... }, // первичная проверка
        function($query) { ... }, // вторичная проверка
        function($query) { ... }, // еще одна вторичная проверка
    ]
]

可以有多个包含汇总检查的元素

$config = [
    ...,
    [
        '*' => function($query) { ... },
        function($query) { ... }, 
    ],
    [
        '*' => function($query) { ... },
        function($query) { ... }, 
    ],
    ...
]

与特定字段无关的错误

在某些情况下,错误与特定字段无关(例如,在调用某些外部系统失败的情况下)。

可以使用通用检查将它们添加到列表中,不指定 namevalue 或根本返回字符串——错误文本——代替数组。

$config = [
    ...,
    function($query) {
        if (...) {
            // Полная форма:
            return [
                [
                    'message' => "Произошёл сбой связи с внешней системой."    
                ]
            ];
            // Краткая форма, вложенность на ДВА уровня ниже
            return "Произошёл сбой связи с внешней системой.";
        }
    }
];

以这种形式格式化错误将影响其在客户端的显示:它将位于表单的某个通用区域(通常在发送按钮旁边),而不是特定字段。

将通用错误添加到列表的另一种方法是调用 addError() 方法,见下文。

同类型字段的检查 —— []

同类型是指具有相同 name 属性的字段,其末尾包含 [],每个字段都遵循相同的检查逻辑。

此类字段的好例子是促销代码,用户可以在表单中随意输入。对于每个促销代码,表单中都会有一个名为 promo_codes[] 的文本字段,用户可以通过“输入更多促销代码”类型的按钮自行添加新的促销代码字段。

在HTTP请求中,这些字段将与包含字符串数组的键 promo_codes 相对应

$values = [
    'promo_codes' => [
        "ABC",
        "", // могут быть и пустые поля
        "123"
    ]
]
];

使用键 [] 指定同类型字段的检查

$config = [
    'promo_codes' => [
        '[]' => [
            '/^[A-z]+$/' => 'Промо-коды могут состоять только из латинских букв.',
            function ($value) {
                if (...) { 
                    return "Промо-код $value не распознан.";
                }
            }
        ]
    ]
]

结果

[
  {
    "name": "promo_codes[]",
    "value": "abc",
    "messages": [
      "Промо-код abc не распознан."
    ]
  },
  {
    "name": "promo_codes[]",
    "value": "134",
    "messages": [
      "Промо-коды могут состоять только из латинских букв."
    ]
  }
]

指定在键 [] 中的检查遵循与单个字段检查相同的规则

以下缩写形式的记录与完整形式

'[]' => function() {...}

相对应

'[]' => [ 
    '*' => function() {...} 
]

除了 [] 之外,还可以对请求中是否存在字段进行通用检查。它需要与 [] 位于同一级别

$config = [
    'promo_codes' => [
        '*' => 'Нужно указать хотя бы один промо-код',
        '[]' => ...
    ]
]

如果在这种情况下请求中不存在键 promo_codes 或它只包含空值(有关详细信息,请参阅以下内容),则结果将是错误消息

$values = [
    'promo_codes' => [ "", "" ]
];
[
  {
    "name": "promo_codes",
    "value": [],
    "messages": [
      "Нужно ввести хотя бы один промо-код."
    ]
  }
]

请注意,在此情况下 name 不包含末尾的 [],因为检查是在HTTP请求通用键级别进行的,而不是在单独字段级别。这将很重要,因为放置错误在客户端。

请注意,在检查之前,HTTP请求的内容会从空同类型字段中清除(预先删除边缘的空格)。数组中的空值将被排除。

将检查配置作为子配置连接 —— children

有时,一个表单可以作为子表单嵌入到一个更大的表单中,同时保留所有字段填写的规则。

例如,在网店中,客户个人信息编辑表单可能作为客户字段部分嵌入到订单表单中。此时,字段名称将进行修改,以确保在HTTP请求中,它们的值不是在顶层,而是在某个键的内部。例如,<input name="phone">可能变成<input name="client[phone]"><input name="email">变成<input name="client[email]">等等。

为了继续使用现有的字段检查配置,需要使用children键。

$clients_config = [
    'name' => 'Укажите имя.',
    'phone' => 'Укажите телефон.',
];
$full_config = [
    'client' => [
        'chlidren' => $clients_config
    ]
];
// Пример HTTP-запроса из формы с незаполненными полями
$http_query = [ 
    'client' => [
        'name' => '',
        'phone' => ''
    ] 
];
$obj = new One234ru\FormValidator($full_config);
$obj->validate($http_query);

结果

[
  {
    "name": "client[name]",
    "value": "",
    "messages": [
      "Укажите имя."
    ]
  },
  {
    "name": "client[phone]",
    "value": "",
    "messages": [
      "Укажите телефон."
    ]
  }
]

子配置没有功能限制,可以包含上述描述的任何类型的检查。

addError() - 手动添加错误列表

此方法允许绕过总体检查逻辑手动添加列表。

这可能在与外部系统交互时需要,例如,当请求基于表单数据构建时。在发送请求之前,需要确保数据的正确性,因此检查在发送请求之前进行。然而,请求结果也可能包含错误消息,在这种情况下,需要将它们包含在列表中。这正是addError()的作用。

它接受参数,顺序如下

  • 错误消息文本或错误列表数组
  • 字段名称(可选)
  • 值(可选)

示例

$obj = new One234ru\FormValidator($config);
$obj->addError('Какая-то ошибка с телефоном', 'phone');
$obj->addError('И с email тоже ошибка', 'email');

结果看起来与常规检查相同

[
  {
    "name": "phone",
    "messages": [
      "Какая-то ошибка с телефоном"
    ]
  },
  {
    "name": "email",
    "messages": [
      "И с email тоже ошибка"
    ]
  }
]

addError()的参数顺序使得添加通用错误特别方便,允许仅以单个参数指定错误文本

$obj->addError('Произошёл сбой связи с внешней системой.');
[
  {
    "messages": [
      "Произошёл сбой связи с внешней системой."
    ]
  }
]

更改检查配置,清除错误列表

可以使用setConfigTo()方法在对象中加载另一个用于检查的配置。

如果之后运行检查,新发现的错误将替换现有的错误。为了避免这种情况,需要在传递给getErrors()方法的第二个参数中指定false

// Проверяем какие-то внешние условия и добавляем в список
// выявленные ошибки в качестве общих.
$some_errors = ...; 
$obj = new One234ru\FormValidator([]);
$obj->addError($some_errors);

// Теперь генерируем конфигурацию для проверки
$config = ...;
$obj->setConfigTo($config);
$obj->validate($http_query, false);

如果检查配置是动态生成的,并且在确定某些错误时并不完全明确,这可能会很有用。

其他功能

在检查完成后,可以按字段名称确定字段是否填写正确

$obj->validate($http_query, false);
...
$obj->isFieldValid('email');