-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial.Form Generation and Validation I.zh_cn
- 引言
在上一章中,我们在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格式、是否为空、长度是否符合限制等。在模块里对表单进行操作就需要继承这三个类来实现。
在这一章,我将会通过例子介绍如何生成一个表单,验证表单。
- 生成一个基本的表单
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
图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()方法会将错误信息打印出来,这样用户就可以看到错误信息,同时用户输入的数据也不会被清空。
图3-2 登陆页无效表单验证
若验证没有问题,调用getData()方法就可以得到经过过滤器过滤后的数据,在这里我们判断用户名是否为admin,如果是则返回错误信息,因为之前我们在模板里已经有了显示错误消息的代码,因此这里直接把错误信息赋给$error变量就可以了。
图3-3 登陆页提交后验证
如果这两步都没问题,就设置默认模板,并输出成功。
图3-4 登陆成功
至此,整个表单操作、显示和验证的流程都已经完成。用户可以根据自己的需求对上面的代码做修改,实现自己的功能。