将验证绑定到表单

当表单首次呈现时,将一个表单类(如前面示例中描述的Application\Form\Factory)与一个可以执行过滤或验证的类(如前面配方中描述的Application\Filter\*)绑定是没有什么价值的。然而,一旦表单数据被提交,兴趣就会增长。如果表单数据未能通过验证,则可以对值进行过滤,然后重新显示。验证错误信息可以与表单元素绑定,并呈现在表单字段旁边。

如何做…

1.首先,一定要实现在 《实现表单工厂》、《链式$_POST过滤器》和 《链式$_POST验证器》示例中定义的类。

  1. 现在我们将注意力转向Application/Form/Factory类,并添加属性和设置器,允许我们附加Application/Filter/FilterApplication/Filter/Validator的实例。我们还需要定义$data,它将用于保留过滤和验证的数据。
  1. const DATA_NOT_FOUND = 'Data not found. Run setData()';
  2. const FILTER_NOT_FOUND = 'Filter not found. Run setFilter()';
  3. const VALIDATOR_NOT_FOUND = 'Validator not found. Run setValidator()';
  4. protected $filter;
  5. protected $validator;
  6. protected $data;
  7. public function setFilter(Filter $filter)
  8. {
  9. $this->filter = $filter;
  10. }
  11. public function setValidator(Validator $validator)
  12. {
  13. $this->validator = $validator;
  14. }
  15. public function setData($data)
  16. {
  17. $this->data = $data;
  18. }
  1. 接下来,我们定义了一个validate()方法,该方法调用嵌入式Application/Filter/Validator实例的process()方法。我们检查$data$validator是否存在。如果不存在,就会抛出相应的异常,并指示需要先运行哪个方法。
  1. public function validate()
  2. {
  3. if (!$this->data)
  4. throw new RuntimeException(self::DATA_NOT_FOUND);
  5. if (!$this->validator)
  6. throw new RuntimeException(self::VALIDATOR_NOT_FOUND);
  1. 调用process()方法后,我们将验证结果消息与表单元素消息关联起来。需要注意的是,process()方法会返回一个布尔值,代表数据集的整体验证状态。当表单在验证失败后重新显示时,错误信息将出现在每个元素旁边。
  1. $valid = $this->validator->process($this->data);
  2. foreach ($this->elements as $element) {
  3. if (isset($this->validator->getResults()
  4. [$element->getName()])) {
  5. $element->setErrors($this->validator->getResults()
  6. [$element->getName()]->messages);
  7. }
  8. }
  9. return $valid;
  10. }
  1. 以类似的方式,我们定义了一个filter()方法,该方法调用嵌入式Application/Filter/Filter实例的process()方法。与步骤3中描述的validate()方法一样,我们需要检查$data$filter是否存在。如果缺少任何一个,我们将抛出一个带有适当消息的RuntimeException
  1. public function filter()
  2. {
  3. if (!$this->data)
  4. throw new RuntimeException(self::DATA_NOT_FOUND);
  5. if (!$this->filter)
  6. throw new RuntimeException(self::FILTER_NOT_FOUND);
  1. 然后我们运行process()方法,产生一个Result对象数组,其中$item属性代表过滤链的最终结果。然后我们对结果进行循环,如果对应的$element键匹配,则将值属性设置为过滤后的值。我们还添加过滤过程中产生的任何消息。当表单重新显示时,所有的值属性都会显示过滤后的结果。
  1. $this->filter->process($this->data);
  2. foreach ($this->filter->getResults() as $key => $result) {
  3. if (isset($this->elements[$key])) {
  4. $this->elements[$key]
  5. ->setSingleAttribute('value', $result->item);
  6. if (isset($result->messages)
  7. && count($result->messages)) {
  8. foreach ($result->messages as $message) {
  9. $this->elements[$key]->addSingleError($message);
  10. }
  11. }
  12. }
  13. }
  14. }

如何运行…

你可以像上面描述的那样,先对Application/Form/Factory进行修改。对于测试目标,您可以使用链式$_POST过滤器示例中的如何做部分中所示的professor数据库表。各种列的设置应该能让您了解要定义哪些表单元素、过滤器和验证器。

作为一个例子,你可以定义一个chap_06_tying_filters_to_form_definitions.php文件,它将包含表单包装器、元素和过滤器分配的定义。下面是一些例子:

  1. <?php
  2. use Application\Form\Generic;
  3. define('VALIDATE_SUCCESS', 'SUCCESS: form submitted ok!');
  4. define('VALIDATE_FAILURE', 'ERROR: validation errors detected');
  5. $wrappers = [
  6. Generic::INPUT => ['type' => 'td', 'class' => 'content'],
  7. Generic::LABEL => ['type' => 'th', 'class' => 'label'],
  8. Generic::ERRORS => ['type' => 'td', 'class' => 'error']
  9. ];
  10. $elements = [
  11. 'first_name' => [
  12. 'class' => 'Application\Form\Generic',
  13. 'type' => Generic::TYPE_TEXT,
  14. 'label' => 'First Name',
  15. 'wrappers' => $wrappers,
  16. 'attributes'=> ['maxLength'=>128,'required'=>'']
  17. ],
  18. 'last_name' => [
  19. 'class' => 'Application\Form\Generic',
  20. 'type' => Generic::TYPE_TEXT,
  21. 'label' => 'Last Name',
  22. 'wrappers' => $wrappers,
  23. 'attributes'=> ['maxLength'=>128,'required'=>'']
  24. ],
  25. // etc.
  26. ];
  27. // overall form config
  28. $formConfig = [
  29. 'name' => 'prospectsForm',
  30. 'attributes' => [
  31. 'method'=>'post',
  32. 'action'=>'chap_06_tying_filters_to_form.php'
  33. ],
  34. 'row_wrapper' => ['type' => 'tr', 'class' => 'row'],
  35. 'form_wrapper' => [
  36. 'type'=>'table',
  37. 'class'=>'table',
  38. 'id'=>'prospectsTable',
  39. 'class'=>'display','cellspacing'=>'0'
  40. ],
  41. 'form_tag_inside_wrapper' => FALSE,
  42. ];
  43. $assignments = [
  44. 'first_name' => [ ['key' => 'length',
  45. 'params' => ['min' => 1, 'max' => 128]],
  46. ['key' => 'alnum',
  47. 'params' => ['allowWhiteSpace' => TRUE]],
  48. ['key' => 'required','params' => []] ],
  49. 'last_name' => [ ['key' => 'length',
  50. 'params' => ['min' => 1, 'max' => 128]],
  51. ['key' => 'alnum',
  52. 'params' => ['allowWhiteSpace' => TRUE]],
  53. ['key' => 'required','params' => []] ],
  54. 'address' => [ ['key' => 'length',
  55. 'params' => ['max' => 256]] ],
  56. 'city' => [ ['key' => 'length',
  57. 'params' => ['min' => 1, 'max' => 64]] ],
  58. 'state_province'=> [ ['key' => 'length',
  59. 'params' => ['min' => 1, 'max' => 32]] ],
  60. 'postal_code' => [ ['key' => 'length',
  61. 'params' => ['min' => 1, 'max' => 16] ],
  62. ['key' => 'alnum',
  63. 'params' => ['allowWhiteSpace' => TRUE]],
  64. ['key' => 'required','params' => []] ],
  65. 'phone' => [ ['key' => 'phone', 'params' => []] ],
  66. 'country' => [ ['key' => 'in_array',
  67. 'params' => $countries ],
  68. ['key' => 'required','params' => []] ],
  69. 'email' => [ ['key' => 'email', 'params' => [] ],
  70. ['key' => 'length',
  71. 'params' => ['max' => 250] ],
  72. ['key' => 'required','params' => [] ] ],
  73. 'budget' => [ ['key' => 'float', 'params' => []] ]
  74. ];

你可以使用前面示例中介绍的已经存在的chap_06_post_data_config_callbacks.phpchap_06_post_data_config_messages.php文件。最后,定义一个chap_06_tying_filters_to_form.php文件,设置自动加载并包含这三个配置文件。

  1. <?php
  2. require __DIR__ . '/../Application/Autoload/Loader.php';
  3. Application\Autoload\Loader::init(__DIR__ . '/..');
  4. include __DIR__ . '/chap_06_post_data_config_messages.php';
  5. include __DIR__ . '/chap_06_post_data_config_callbacks.php';
  6. include __DIR__ . '/chap_06_tying_filters_to_form_definitions.php';

接下来,你可以创建表单工厂、过滤器和验证器类的实例。

  1. use Application\Form\Factory;
  2. use Application\Filter\ { Validator, Filter };
  3. $form = Factory::generate($elements);
  4. $form->setFilter(new Filter($callbacks['filters'], $assignments['filters']));
  5. $form->setValidator(new Validator($callbacks['validators'], $assignments['validators']));

然后你可以检查是否有任何$_POST数据。如果有,则进行验证和过滤。

  1. $message = '';
  2. if (isset($_POST['submit'])) {
  3. $form->setData($_POST);
  4. if ($form->validate()) {
  5. $message = VALIDATE_SUCCESS;
  6. } else {
  7. $message = VALIDATE_FAILURE;
  8. }
  9. $form->filter();
  10. }
  11. ?>

视图逻辑非常简单:只需渲染表单。任何验证信息和各种元素的值将作为验证和过滤的一部分被分配。

  1. <?= $form->render($form, $formConfig); ?>

下面是一个使用不良表格数据的例子。

将验证绑定到表单 - 图1

请注意过滤和验证信息。也请注意不良标签。

将验证绑定到表单 - 图2