表单是网站最重要的组成部分,以用户交互为主的站点,表单几乎无处不在。所以,表单的生成、验证、提交,也是程序中很重要的部分,成熟的程序,几乎都有自成体系的表单机制。drupal 也不例外。drupal 的表单系统相当强大,可以让程序员不写任何 html 代码,完成表单的生成、验证、提交、入库等一系列流程。在模块开发中,几乎都会和表单打交道。

这篇教程是 drupal 表单的基本应用实例,涉及表单的定义、验证、提交等基本环节。

本系列教程,都以 drupal 6 为基础,以模块为载体,每篇教程即为一个模块,以后不再做特殊说明。

web 表单交互的基本流程大致是:用户访问某个页面,输出表单界面,用户填写表单,提交,程序验证,通过验证或让用户重新填写。流程基本固定,但实现的方式有许多种,在 drupal 中,这整个流程均可以在模块中完成,甚至不用写一行 html 代码。下面开始创建模块。

模块名称:myform。模块名称根据需要自定义,一般来说,不与现有的第三方模块重名。这个模块我们命名为:myform。

实现功能:定义一个页面,生成一个表单,让所有可以提交信息。

权限设置:不做权限验证,所有人均可提交。本教程重点在表单的基本应用,权限部分留待以后。

以下是详细步骤:

第一步:定义模块信息,添加模块信息文件:myform.info

// $Id$
name = myform
description = 用户反馈表单
core = "6.x"

第二步:创建数据表,添加模块安装文件:myform.install

1、实现 hook_schema,drupal 为了实现多种数据库的兼容,有自成体系的数据库抽象层,包括创建表、增删查,均有详细接口。此部分留待后文讲述。

查看代码 打印代码?

function myform_schema() {
  $schema['myform'] = array(
    'description' => '反馈表单字段',
    'fields' => array(
      'id' => array(
        'type' => 'serial',
        'unsigned' => true,
        'not null' => true,
        'description' => '自增主键'
      ),
      'title' => array(
        'type' => 'varchar',
        'length' => 255,
        'not null' => true,
        'default' => '',

19 'description' => '标题', 20 ), 21 'name' => array( 22 'type' => 'varchar', 23 'length' => 64, 24 'not null' => true, 25 'default' => '', 26 'description' => '昵称', 27 ), 28 'mail' => array( 29 'type' => 'varchar', 30 'length' => 255, 31 'not null' => true, 32 'default' => '', 33 'description' => '邮件', 34 ), 35 'timestamp' => array( 36 'type' => 'int', 37 'not null' => true, 38 'default' => 0, 39 'description' => '留言时间' 40 ), 41 'body' => array( 42 'description' => '留言内容', 43 'type' => 'text', 44 'not null' => false, 45 'size' => 'big', 46 'serialize' => true 47 ), 48 ), 49 'indexes' => array( 50 'timestamp' => array('timestamp'), 51 ), 52 'primary key' => array('id'), 53 ); 54 return $schema; 55 }

2、实现 hook_install。在模块被安装时,将调用此钩子。通常,若以 hook_schema 定义了数据表结构,将使用 drupal_install_schema 来完成安装。同时,可以在 hook_install 进行其它操作,比如写入一些默认数据等。

查看代码 打印代码? 1
4 function myform_install() { 5 drupal_install_schema('myform'); 6 }

3、实现 hook_uninstall。在模块卸载时,将调用此钩子。这并不是必须的,但推荐实现,若你的模块创建了数据表,可调用 drupal_uninstall_schema 删除表。注意:用户禁用模块,并不会调用此钩子。 查看代码 打印代码? 1
4 function myform_uninstall() { 5 drupal_uninstall_schema('myform'); 6 }

第三步:创建模块主文件,添加模块主文件:myform.module

根据我们的需要,需要定义一个页面,使用 hook_menu 完成。drupal 是一个单入口程序,所有请求均由 index.php 解析分发,hook_menu 定义的路径,实际上就相当于 $_GET['q'],即:index.php?q={path}。用户发起访问请求,系统判断请求路径由哪个模块定义,根据最大匹配原则,找到最接近的定义者。

比如:用户请求的路径为:test/abcd,匹配的顺序大致如:test/abcd -> test/% -> test。如以上三个路径都没有被定义,则返回错误,无法找到页面。以下是本模块需要定义路径。 hook_menu 将被写入数据库 menu 表中,并被缓存,所以,有任何关于 hook_menu 的改动,建议先清除缓存。

注意:如果两个模块定义了相同的路径,将根据 weight 来决定使用谁的配置。

hook_menu 是一个相当重要的钩子,几乎每个模块都要用到,但不是本篇教程的重点,只做以上简单介绍。

查看代码 打印代码? 01
04 function myform_menu() { 05 $item = array(); 06
07 $item['myform'] = array( 08 'description' => '用户反馈', 09 'page callback' => 'drupal_get_form', 10 'page arguments' => array('myform_page_form'), 11 'access callback' => true, 12 'file' => 'myform.page.inc', 13 'type' => MENU_CALLBACK, 14 ); 15
16 return $item; 17 }

第四步:创建表单,创建文件:myform.page.inc

为什么要将表单代码写在另一个文件呢?drupal 在每次初始化时,将加载每个模块的主文件(如:myform.module),我们可以把只在特定路径使用到的代码,写在另一个文件里,减少主模块文件的大小,以加速系统运行。当然,这所带来的效率提升可能是微乎其微,甚至没有,但养成这个习惯也不是坏事,另外这也让模块的文件结构更清晰,易于维护。

根据 hook_menu 里的定义,drupal_get_form 是页面回调函数,相当于:drupal_get_form('myform_page_form')。我们只需要按规定格式定义表单,然后调用 drupal_get_form,便能得 html 代码,输出到页面即可。

在 myform.page.inc 中创建表单函数:myform_page_form。

查看代码 打印代码? 1
5 function myform_page_form(&$form_state) { 6 }

这儿有一个参数: &$form_state,这是 drupal_get_form 自动传递的,其中包含表单 id、token 等信息,若处于验证或提交环节,包含所有用户输入值,即 $_POST。使用 drupal_get_form 也可以传递自定义参数,比如:drupal_get_form('myform_page_form', $test),那么 myform_page_form 函数则要这样:

1
7 function myform_page_form(&$form_state, $test) { 8 }

此处不做深入讨论,回到本教程,创建表单的全部代码如下: 查看代码 打印代码? 01
05 function myform_page_form(&$form_state) { 06
07 $form['title'] = array( 08 '#title' => '主题', // 字段名称 09 '#type' => 'textfield', // 字段类型,此处为单行文本 10 '#required' => true, // 是否必填 11 '#default_value' => '', // 默认值,可选 12 ); 13
14 $form['name'] = array( 15 '#title' => '昵称', 16 '#type' => 'textfield', 17 '#required' => true, 18 '#description' => '请输入您的昵称,不低于 2 个字符' 19 ); 20
21 $form['mail'] = array( 22 '#title' => '邮件', 23 '#type' => 'textfield', 24 '#required' => true, 25 '#description' => '请输入您的常用邮件,便于我们联系您' 26 ); 27
28 $form['body'] = array( 29 '#title' => '内容', 30 '#type' => 'textarea', // 多行文本 31 '#required' => true, 32 ); 33
34
41 $form['#validate'] = array('myform_page_form_validate'); 42
43
50 $form['submit'] = array( 51 '#type' => 'submit', // 提交按钮 52 '#value' => '确认提交', // 按钮值 53 '#submit' => array('myform_page_form_submit'), // 提交函数 54 ); 55
56 return $form; 57 }

接下来是表单验证函数。刚接触 drupal 的朋友可能有这个疑惑,drupal 为什么要把表单的验证和提交环节分开呢,这不是影响效率吗,验证完毕不是可以直接做入库等提交操作吗?实际上,这是为了让表单机制更灵活,假设验证和提交环节不分开,某个模块定义了表单,其它模块也想要验证这个表单,怎么办?验证和提交分离,其它模块可以轻松的通过 hook_form_alter 添加一个验证函数,在该函数中,可阻止表单提交。国内很多程序,均不考虑这些需要,所以,如果要进行二次开发,只有在源代码上进行修改,缺乏灵活性。myform_page_form 表单的验证函数代码如下: 查看代码 打印代码? 01
08 function myform_page_form_validate(&$form, &$form_state) { 09
10
15 if (drupal_strlen($form_state['values']['name']) < 2) { 16
20 form_set_error('name', '昵称不能少于 2 个字符'); 21 } else if (drupal_strlen($form_state['values']['title']) > 255) { 22 form_set_error('title', '标题长度不能大于 255 个字符'); 23 } 24
25 // 如验证通过,将进入表单提交环节 26 }

以上只验证了长度是否合法,实际应用中,可能还需要验证电子邮件是否合法等。验证通过后,进行提交环节,代码如下:

查看代码 打印代码? 01
06 function myform_page_form_submit(&$form, &$form_state) { 07
10 db_query("INSERT INTO {myform} (id, title, name, mail, timestamp, body) 11 VALUES (null, '%s', '%s', '%s', %d, '%s')", $form_state['values']['title'], 12 $form_state['values']['name'], $form_state['values']['mail'], 13 $_SERVER['REQUEST_TIME'], $form_state['values']['body']); 14
15 // 获取上一条 insert sql 返回的主键,以此判断是否执行成功 16 if (db_last_insert_id('myform', 'id')) { 17
18
22 drupal_set_message('感谢您,反馈已经提交,我们将尽快和您取得联系'); 23
24 // 如果想将用户重定向到一个页面,可修改 $form['#redirect'] 值,如: 25
26 $form['#redirect'] = 'test/abcde'; 27
28
32
33 } else { 34
35 // 数据写入失败了 36 drupal_set_message('抱歉,遇到问题,提交失败了', 'error'); 37
38 } 39
40 }

表单成功写入数据库,整个表单的流程就走完了。很简单吧?因为规则不熟悉,可能不明白为什么要这样写,实际上,刚开始做模块开发,只需要知道要这样即可以,不必深究为什么要这样。倘若先拿一本书,务必把每个术语搞清楚,理解透彻,没有实际开发经验,我想还是比较困难的。好读书不求甚解,大概也是这个道理吧。做多了,自然就明白了,可能我是实践主义者。

以上就是本篇教程的全部内容,下一篇将继续研究表单,说说关于文件上传。

启用后,访问 http://www.mysite.com/?q=myform 即可提交表单。