Drupal专业开发指南 第10章 Drupal表单API(form API)--表单处理流程

表单API

 

译者:葛红儒    ESKALATE科技公司

 

       Drupal4.7及更高版本提供了一个应用程序接口(API)用来生成、验证和处理HTML表单。表单API将表单抽象为一个关于属性和值的嵌套数组。在生成页面时,表单呈现引擎会在适当的时候将数组呈现出来。这种方式包含多层含义:

              没有直接输出HTML,我们而是创建了一个数组并让引擎生成HTML

由于我们将表单的表示处理为结构化的数据,所以我们就可以对表单进行添加、删除、重新排序、和修改等操作。当你想用一种干净平和的方式对由其它模块创建的表单进行修改时,这会特别方便。

任意一个表单元素可以映射到任意一个主题函数上

可以对任意表单添加额外的表单验证或处理函数

对表单操作进行了保护,从而防止表单注入攻击,特别是用户修改了表单并接着试图提交它时。

使用表单的学习曲线有点高

 

在本章,我们将迎难而上。我们将学习如何创建、验证、处理表单,以及当我们想要一个个性化的外观时如何编写主题函数:本章所讲的都是关于Drupal5版本所实现的表单API

 

理解表单处理流程

 

10-1展示了表单的构建、验证、和提交的流程

10-1 Drupal是如何处理表单的

为了更好的与表单API进行交互,理解API背后的引擎是如何工作的将会非常有用。在接下来的部分解释了调用drupal_get_form()时都发生了什么。

 

流程初始化

调用drupal_get_form()后,首先初始化,$form_values(用来保存提交数据的数组)置为空数组,$form_submitted设为FALSE

 

设置一个令牌(token

表单系统的一个优点是,它尽力去保证被提交的表单是由Drupal真正创建的。这主要是为了安全性和防止垃圾信息的提交。为了达到这一点,Drupal为每一个Drupal安装都设置了一个私钥,Drupal将基于私钥生成一个随机的令牌作为隐藏于发送到表单中。当表单提交时,会对令牌进行测试。相关背景信息请参看drupal.org/node/28420。令牌只在登录用户中适用,因为匿名用户的页面通常会被缓存起来,这样它们就没有一个唯一的令牌了。

 

设置一个ID

一个包含了当前表单ID的隐藏域作为表单的一部份被发送给浏览器。该ID一般对应于定义表单的函数,它也是方法drupal_get_form()的第一个参数。例如函数user_register()定义了用户注册表单,它的调用方式如下:

$output = drupal_get_form('user_register');

 

 

收集所有可能的表单元素定义

接着,调用element_info()。它将触发所有实现了钩子函数hook_elements()的模块上的钩子函数。在Drupal核心内部,标准表单元素,比如单选按钮和复选框,都定义在 

system.module中的钩子函数hook_elements()中。当模块需要定义他们自己的元素类型时,就需要实现这个钩子。在以下几种情况中,你可能需要在你的模块中实现hook_elements():你想要一个特定的表单元素类型时,比如一个可在预览节点时展示图像缩略图的图像上传按钮;或者,你想通过定义更多的属性来扩展已有的表单元素时。

       例如,模块views定义了它自己的元素类型:

/*

* Custom form element to do our nice images.

*/

function views_elements() {

$type['imagebutton'] = array(

'#input' => TRUE,

'#button_type' => 'submit',

);

return $type;

}

模块TinyMCE通过禁用textarea的自适应大小能力修改了一个默认的已存在的表单元素类型(它还添加了一个属性#process,这样当构建表单时,它将调用process_textarea()从而修改该表单元素)。

/**

* Implementation of hook_elements().

*/

function tinymce_elements() {

$type = array();

if (user_access('access tinymce')) {

// Set resizable to false to prevent drupal.js resizable function from

// taking control of the textarea.

$type['textarea'] = array(

'#process' => array('tinymce_process_textarea' => array()),

'#resizable' => FALSE

);

}

return $type;

}

钩子element_info()为所有的表单元素收集所有的默认属性并将其放到一个本地缓存中。在进入下一步—为表单寻找一个验证器—以前,任何在表单定义中未曾出现的默认属性将被添加进来。

 

寻找一个验证函数

通过将表单的属性#validate设置为一个数组,其中函数名为键,一个数组作为值,从而为表单指定一个验证函数。在调用验证函数时,后面数组中的任何数据将被作为参数传递给验证函数。可以使用下面的方式定义多个验证器:

$form['#validate'] = array(

'foo_validate' => array($extra_info),

'bar_validate' => array()

);

如果表单中没有定义属性#validate,那么接下来就要寻找名为”表单ID”+“_validate”的函数。如果这个函数也不存在,Drupal将寻找名为”表单#base属性的值”+“_validate”的函数。如果你有多个表单,它们仅有细微的差别,并且你想让它们共有一个验证器。你可以设置$form[‘#base’],从而用它代替表单ID来构造验证函数的名称。例如,节点模块将$form[‘#base’]设为“node_form”,这样就可以调用验证函数node_form_validate()了。

 

寻找一个提交函数

与前面的验证函数类似,通过将表单的属性#submit设置为一个数组,其中处理表单提交的函数的名称为键,一个向提交函数提供参数的数组作为值,从而为表单指定一个提交函数。(另外,$form_id和$form_values恒为提交函数的第一第二参数)

// Call my_special_submit_function() on form submission.

$form['#submit'] = array(

'my_special_submit_function' => array($extra_info)

);

如果表单中没有定义属性#submit,那么接下来就要寻找名为”表单ID”+“_submit”的函数。如果这个函数也不存在,Drupal将寻找名为”表单#base属性的值”+“_submit”的函数。如果由任何一种情况成立,那么Drupal会将属性#submit设置为他第一个找到的表单处理器函数。(找到一个以后就不继续寻找了)

 

允许模块在构建表单以前修改表单

在构建表单以前,将调用钩子函数form_alter(),任何实现了该钩子函数的模块都可以修改表单中的任何部分。这是修改、覆盖、混合由别人的模块所构造的表单的主要方式。

 

构建表单

现在表单被传递到了函数form_builder()中,该函数迭代的处理表单树,并添加必须的标准值。

 

在表单构建完成后允许函数修改表单

在函数form_builder()中,每次遇到$form树中的一个新的分支时(例如,一个新的字段集或表单元素),它都寻找一个名为#after_build的可选属性。该属性是一个数组,一旦当前表单被构建完成以后,就会立即调用数组中的函数。当整个表单被构建完成以后,将会调用可选属性$form[‘#after_build’]中定义的函数。所有的#after_build函数都接收$form_id和$form_values作为参数。

 

检查表单是否已被提交

我们将在稍后讨论这一点(参看本章后面的“验证表单”部分)。现在我们将假定表单是第一次出现在这里。

 

为表单寻找主题函数

如果已将form[‘#theme’]设置为了一个已存在的函数,Drupal只需要简单的使用该函数就可以了。如果没有设置,Drupal将调用函数theme_get_function()来决定是否存在一个主题函数供本表单使用。我们知道表单是通过它的表单ID来标识的,Drupal将根据优先级来寻找下面的主题函数。如果找到了bluebeach_foo(),Drupal将停止寻找。假定表单ID为foo,并且我们使用了一个名为“bluebeach”的PHPTemplate主题:

1. bluebeach_foo()

2. phptemplate_foo()

3. theme_foo()

当找到了一个主题函数时,就会将其指定到form[‘#theme’]上。如果一个也没有找到,将会使用可选的$form[‘#base’]代替表单ID以继续寻找。

 

允许模块在表单呈现以前修改表单

剩下的最后一件事就是将表单从结构化数组转化为HTML。但是在这以前,模块还有最后一个机会来修改表单。这对于跨页面向导表单或者其它需要在最后时刻修改表单的方式,将会非常有用。此时将会调用属性#pre_render中定义的任何函数,函数参数有$form_id $form。

 

显示表单

为了将表单树从一个嵌套数组转化为HTML代码,表单构建器调用函数drupal_render()。这一递归函数将会遍历表单树的每个层次,对于每个层次,它将进行以下操作:

1.看是否定义了#children元素(与“为该元素已经生成内容”同义);如果没有,这样显示表单树中该节点的孩子:

• 看是否为该元素定义了一个#theme函数。

• 如果定义了,临时将该元素的#type设为markup(标识字体)。接着,接该元素传递到主题函数中,并将#type重置为原来的值。

• 如果没有内容生成(可能是由于没有为该元素定义#theme函数,或者由于调用的#theme函数没有返回值),那么逐一显示该元素的子元素(也就是,将子元素传递给drupal_render())。

• 相反,如果#theme函数生成了内容,那么将生成的内容存储在该元素的#children属性中。

2.如果该元素还没有被打印出来,调用该元素的#type所属类型的元素呈现器。如果没有为该元素设置#type属性,那么将其默认为markup。

3.在内容前面追加#prefix,在后面追加#suffix,并将它从函数中返回。

 

该递归反复执行的结果就是为表单树的每一层次生成HTML。例如,在一个表单包含一个字段集,而字段集又包含两个字段,那么该字段集的#children元素将包含两个字段的HTML,而表单的#children元素将包含整个表单的HTML(其中包括字段集的HTML)。

 

生成的HTML将会返回给函数drupal_get_form()的调用者。这就是显示表单所要做的全部工作!

 

验证表单

现在让我们看一下需要检查表单是否被提交的情景。决定一个表单是否已被提交有以下几点:$_POST是否为空,$_POST['form_id']中的字符串是否匹配表单的ID或者$form['#base']的ID。如果都匹配,那么Drupal

将开始验证了。

验证中首先检查的是该表单是否使用了Drupal的令牌机制(参看本章前面的“设置一个令牌”部分)。所有使用令牌的Drupal表单将会有一个唯一的令牌,它与表单一同发送给浏览器,并且将会和其字段值一同被提交。如果令牌不匹配或者不存在,验证失败(尽管剩下的验证会继续进行,以将其它验证错误标识出来)。

接着检查必填字段,看用户有没有漏填的。检查带有选项的元素(复选框、单选按钮、下拉选择框),看所选的值是否是位于构建表单时所生成的原始选项列表中。

如果为单个表单元素定义了一个#validate属性,将会调用该属性定义的函数。

最后,表单ID和表单值将被传递到为表单声明的验证函数中(一般函数名为“表单ID”+ “_validate”)。

 

提交表单

如果验证通过了,那么就该把表单和表单中的值传递到一个函数中,该函数将做一些实际逻辑处理作为表单提交的结果。事实上,可以有多个函数用来处理表单,这是由于#submit属性可以包含多个键值对,其中键为要调用的函数的名称,值为一个用来为函数传递参数的数组(另外,表单ID和表单值,分别为函数的第一第二参数)。

 

为用户重定向

处理表单的函数的返回值是一个为用户重定向的Drupal路径,比如node/1234。如果#submit属性中有多个函数,那么将会使用最后一个函数的返回值。如果函数没有返回一个Drupal路径的话,那么用户将回到原来的页面(也就是,$_GET['q']的值)。从最后一个函数返回FALSE将阻止重定向。

可以通过在表单中定义#redirect属性来覆盖函数的返回值,比如$form['#redirect'] = 'node/1' $form['#redirect'] = array('node/1',$query_string, $named_anchor) 。实际的重定向由drupal_goto()负责,它为Web服务器返回一个定位(Location)头部信息。

 

 

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值