Skip to content

Tutorial.Form Generation and Validation I.zh_cn

linzongshu edited this page Jul 4, 2013 · 2 revisions

English

  1. 引言

在上一章中,我们在phtml文件里用HTML标签写了一个text表单,Pi Engine通过扩展Zend库给用户提供了操作表单的类,包括Zend\Form\Form、Zend\Filter\AbstractFilter和Zend\Validator\AbstractValidator。其中,Zend\Form\Form类用于初始化表单的参数,如类型、name值、属性值以及其他参数;Zend\Filter\AbstractFilter类用于对用户输入的值进行预处理,如去掉两端空格、类型转换、对路径值提取文件名等;Zend\Validator\AbstractValidator类用于表单值的验证,如是否为Email格式、是否为空、长度是否符合限制等。在模块里对表单进行操作就需要继承这三个类来实现。

在这一章,我将会通过例子介绍如何生成一个表单,验证表单。

  1. 生成一个基本的表单

2.1 创建表单文件

创建表单需要先实例化Zend\Form\Form类,然后通过这个类调用其add()方法初始化表单元素。因此这步操作可以直接在action里完成,但是为了代码易读、方便维护,我们建议独立创建一个文件夹用于保存表单文件。在此,我们在src目录下创建一个Form文件夹,用于保存表单相关的文件。

现在我们以登陆页为例,介绍下如何创建一个基本的表单类。在src目录下创建一个Form目录,同时在Form里创建一个LoginForm.php的文件:

member
|- src
   |- Form
      |- LoginForm.php

需要注意的是Form和LoginForm.php的首字母都必须大写,而文件名也采用了{form name}Form的格式,这样更方便查看。在LoginForm.php里添加如下代码:

路径:usr/module/member/src/Form/LoginForm.php

Code 3.2.1

<?php
namespace Module\Member\Form;

use Pi\Form\Form as BaseForm;

class LoginForm extends BaseForm
{
    public function init()
    {
        $this->add(array(
            'name'          => 'username',
            'options'       => array(
                'label'     => __('Username'),
            ),
            'attributes'    => array(
                'type'      => 'text',
            ),
        ));

        $this->add(array(
            'name'          => 'password',
            'options'       => array(
                'label'     => __('Password'),
            ),
            'attributes'    => array(
                'type'      => 'password',
            ),
        ));

        $this->add(array(
            'name'          => 'submit',
            'attributes'    => array(
                'value'     => __('Create'),
            ),
            'type'          => 'submit',
        ));
    }
}

在上面的代码里,我们创建了一个LoginForm的类,用来初始化登陆页的表单元素。代码首先定义了命名空间,同时引用了Pi\Form\Form类并重命名为BaseForm,而Pi\Form\Form类继承自Zend\Form\Form,这样我们就可以调用Zend里封装好的方法来操作表单了。

接下来,创建了LoginForm类的主体内容,并让这个类继承自BaseForm。在LoginForm里创建了init()方法,这个方法在实例化LoginForm的时候会自动被调用,因此可以将初始化表单的代码放在这个方法里实现。在init()方法里,我们定义了三个表单,text类型的用户名表单、password类型的密码表单以及submit类型的提交表单,通过调用add()方法为这几个表单初始化参数值。

在传递的数组里,name为表单的唯一名称,如'name' => 'username'将会生成name="username",options字段里可以定义表单的标签值,在attributes字段里可以定义表单的类型以及表单的初始值,如'type' => 'text', 'value' => __('Create')

这样我们就创建了一个基本的表单类了。当然这个类需要实例化后才能最终用于生成表单,因此,接下来我们需要实例化表单类。

2.2 实例化LoginForm类

实例化表单的操作需要在action里完成,在action里我们需要完成LoginForm的实例化,并将实例化后的对象赋给模板。因此我们需要打开之前创建的LoginController.php文件,并添加如下的私有方法,用来实例化表单对象,因为这个方法不需要被其他类调用,所以设置成私有类型。

路径:usr/module/memeber/src/Controller/Front/LoginController.php

Code 3.2.2

<?php
...
use Module\Member\Form\LoginForm;

class LoginController extends ActionController
{
    protected function renderForm()
    {
        $form = new LoginForm('login');
        $form->setAttribute('action', $this->url('', array('action' => 'login')));

        return $form;
    }

    ...
}

在上面代码里,需要首先引用LoginForm的命名空间,然后在renderForm()私有方法里实例化LoginForm类,其中login参数值为<form>标签的name值,之后调用setAttribute方法,设置form的action属性,action的值就是请求loginAction的url,我们通过url() plugin来生成这个url,这个plugin的用法和之前介绍的url helper一样,而默认的提交方式为POST。在这里我们将提交表单后的处理action也设置为loginAction,因此后面表单生成和处理的逻辑都在loginAction里实现。

有了表单对象后,就要将其赋给模板,在loginAction()里添加如下代码:

路径:usr/module/memeber/src/Controller/Front/LoginController.php

Code 3.2.3

public function loginAction()
{
    $form = $this->renderForm();
    $this->view()->assign('form', $form);
    $this->view()->assign('title', __('Please enter'));
}

在上面的代码里,我们首先调用了renderForm方法,得到实例化后的LoginForm对象,然后将其赋给模板的$form变量。至此表单生成部分在C中的处理逻辑已经完成了。接下来需要在模板里将这些表单输出。

2.3 在模板里显示表单

在上一步里,我们已经将表单对象赋给了模板,因此在模板里,我们需要通过调用Zend\Form\Form里的方法,解析出之前初始化的参数并将这些表单以HTML标签的形式输出。在login-login.phtml里将input标签替换成如下加粗代码:

路径:usr/module/member/template/front/login-login.phtml

Code 3.2.4

<?php $this->jQuery() ?>
<h2><?php echo $title; ?></h2>

<?php if (isset($error)) {
    echo $error;
} ?>

<?php echo $this->form()->openTag($form) ?>
<div id="login-login-username">
<?php $element = $form->get('username'); ?>
    <div><?php echo $this->formLabel($element) ?></div>
    <div><?php
        echo $this->formElement($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>

<div id="login-login-password">
<?php $element = $form->get('password'); ?>
    <div><?php echo $this->formLabel($element) ?></div>
    <div><?php
        echo $this->formElement($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>

<div id="login-login-submit">
<?php $element = $form->get('submit'); ?>
    <div><?php echo $this->formElement($element) ?></div>
</div>
<?php echo $this->form()->closeTag() ?>
<div id="text-message"></div>
...

在上面代码里,先定义了是否要输出错误消息,这消息由action里赋值,在这里我们暂时没有用到。然后调用openTag()方法,将<form>标签输出,而标签里的属性在action里已经设定好了,所以要将$form作为参数值传递给这个方法。

接下来就是输出表单的主体内容了,先通过Zend\Form\Form的get()方法,获取指定表单的Element对象,参数值就是在LoginForm里name字段的值;然后通过formElement()方法输出表单的标签说明,通过formElement()输出表单元素,而formElementErrors()方法会判断表单是否含有错误消息,如果有,则将错误消息输出,这些错误消息将在表单提交验证的时候写入的。

在所有表单的最后面使用closeTag()方法输出form的结束标签</form>

这里采用formElement()方法输出表单,还有其他两种方法同样可以输出表单,分别是formRow和类似formInput。

  • formElement()

这个方法会根据add()时设置的type字段值,生成相应类型的表单,它对所有类型的表单都可用,如上面代码里的text、password和submit类型的表单。

  • formRow()

这个方法也和formElement()方法一样,根据type值输出相应表单标签,不同的是,这个方法会把<label></label>标签一同输出,所以就可以不需要formLabel()方法了。如上面输出用户名表单的代码可更改为:

<div id="login-login-username">
<?php $element = $form->get('username'); ?>
    <div><?php
        echo $this->formRow($element);
        echo $this->formElementErrors($element); ?>
    </div>
</div>
  • 类似formInput()的方法

这类型的方法其实就是form后面加上类型名,对于一些表单它将不关心add()时设置的type值,你指定了哪种输出类型,它就输出该类型的表单标签,如formInput()将会生成text类型标签,formSelect()将会生成select类型标签,而需要提交按钮,就得用formSubmit()。

因此上面代码可更改为:

用户名表单采用$this->formInput($element);

密码表单采用$this->formPassword($element);

提交表单采用$this->formSubmit($element);

经过这几步的操作后,就可以显示表单了,在浏览器里访问如下链接,就可以看到登陆页面:localhost/course/www/member/login/login

fig3-1

图3-1 member模块登陆页

2.4 表单验证

目前虽然能看到页面,但你会发现点击Create按钮后,页面重新加载了,但没有任何变化,原因是我们还没有在loginAction里添加表单验证的代码。当然我们还需要添加一个文件来定义如何过滤和验证表单。

  • 创建表单过滤和验证文件

在Form目录下创建一个LoginFilter.php文件,可以看出来,这个文件的命名也有规律,这样就方便用户查阅和修改了。

member
|- src
   |- Form
      | LoginFilter.php

在这个文件里添加如下代码:

路径:usr/module/member/src/Form/LoginFilter.php

Code 3.2.5

<?php
namespace Module\Member\Form;

use Zend\InputFilter\InputFilter;

class LoginFilter extends InputFilter
{
    public function __construct()
    {
        $this->add(array(
            'name'        => 'username',
            'required'    => true,
            'filters'     => array(
                array(
                    'name'    => 'StringTrim',
                ),
            ),
            'validators'  => array(
                array(
                    'name'    => 'StringLength',
                    'options' => array(
                        'min'     => 5,
                        'max'     => 25,
                    ),
                ),
            ),
        ));

        $this->add(array(
            'name'        => 'password',
            'required'    => true,
            'filters'     => array(
                array(
                    'name'    => 'StringTrim',
                ),
            ),
        ));
    }
}

这段代码里,定义了命名空间和指定了需要的命名空间后,我们定义了一个LoginFilter类并继承自Zend\InputFilter\InputFilter,在这个类的构造函数里通过调用add()方法配置了过滤器和验证器的参数值,这样在实例化这个类时,这些值就会被写入类变量里。

其中,name字段表示为name值为username的表单指定过滤器和验证器;required字段表明这个表单是否需要必填,这个字段可以省略,默认值为false;filters字段里定义了过滤器,每一个数组里就定义一个过滤器,在这里我们就定义了一个StringTrim的过滤器,它负责将字符串两端无效的字符全删掉,由于这个过滤器是Zend里自带,因此只要指定类名就可以了;validator字段定义了验证器,每一个数组对应一个验证器,在此我们为username表单定义了一个限制长度的验证器StringLength,而这个验证器的参数值通过options字段传递过去,这里定义最小和最大长度分别为5和25个字符,StringLength也是Zend自带的,因此也只需要指定类名就可以了。关于Zend自带的Filter和Validator可分别参考Zend\Filter和Zend\Validator目录。对于自定义Validator我们将在后面作详细介绍。

现在我们需要到loginAction里实例化这个类,并完成表单验证部分。

  • 添加验证代码

在loginAction里,添加如下的表单验证代码:

路径:usr/module/member/src/Controller/Front/LoginController.php

Code 3.2.6

<?php
...
use Module\Member\Form\LoginFilter;

...
    public function loginAction()
    {
        $form = $this->renderForm();

        if ($this->request->isPost()) {
            $post = $this->request->getPost();
            $form->setData($post);
            $form->setInputFilter(new LoginFilter);
            if (!$form->isValid()) {
                $this->view()->assign('form', $form);
                return ;
            }
            $data = $form->getData();
            if (in_array($data['username'], array('admin'))) {
                $this->view()->assign('form', $form);
                $this->view()->assign('error', __('Invalid username, please try again!'));
                return ;
            }
            $this->view()->setTemplate(false);
            $this->view()->assign('content', __('Success!'));
            return ;
        }
        $this->view()->assign('form', $form);
        $this->view()->assign('title', __('Please enter'));
}
....

在代码里,首先引用了LoginFilter的命名空间。在loginAction()方法里,我们调用了isPost()方法,判断是否为POST提交过来的请求,如果是,则开始进行表单验证及用户名处理。

getPost()方法会获取所有表单的值,这些值通过setData()方法保存到相应表单的value字段里,也就是与初始化时'value' => 'value1'效果一致;接下来实例化LoginFilter对象,并将这个对象保存到Zend\Form\Form的类变量里,这样Form类就可以在isValid()方法里根据配置的验证器和过滤器处理表单了。

如果验证失败,此时错误信息以及用户填入表单的值都保存在了$form变量里,所以需要将这个变量赋给模板,并返回,模板里的formElemenetErrors()方法会将错误信息打印出来,这样用户就可以看到错误信息,同时用户输入的数据也不会被清空。

fig3-2

图3-2 登陆页无效表单验证

若验证没有问题,调用getData()方法就可以得到经过过滤器过滤后的数据,在这里我们判断用户名是否为admin,如果是则返回错误信息,因为之前我们在模板里已经有了显示错误消息的代码,因此这里直接把错误信息赋给$error变量就可以了。

fig3-3

图3-3 登陆页提交后验证

如果这两步都没问题,就设置默认模板,并输出成功。

fig3-4

图3-4 登陆成功

至此,整个表单操作、显示和验证的流程都已经完成。用户可以根据自己的需求对上面的代码做修改,实现自己的功能。

[Form Generation and Validation II](Tutorial.Form Generation and Validation II.zh_cn)

Clone this wiki locally