Drupal 7模块及表单编写教程(form_example)

admin 提交于 星期五, 12/02/2016 - 16:07

本教程将以最小的篇幅解释Drupal 7里最核心的两个概念:怎样创建自己的模块和怎样操作表单。

首先下载examples模块,启用其中的form_example子模块。我们将分析form_example的代码。examples模块是学习Drupal的很好资料,读者可以自行学习其它子模块。

1. 模块文件结构

Drupal 7的模块以模块名作为文件夹的名称,放在Drupal代码目录树里的某个modules文件夹内。系统模块放在Drupal根目录的modules下。Drupal官网上可以下载的模块可以放在sites/all/modules下。而自己创建的模块可以放在sites/default/modules下。这不是绝对的,不按这样的规则去放,模块也能被识别出来。如果在不同modules里放有同名的模块,会产生冲突。

一个模块的的文件夹里至少包含两个文件:foo.info和foo.module。这里的foo是指模块名,对于form_example来说就是form_example.info和form_example.module。

一个模块目录可以包含子模块目录。examples模块位于sites/all/modules/examples,而form_example位于sites/all/modules/examples/form_example。Drupal可以自动检测出modules下的多层模块结构。

2. info文件

Drupal模块的基本信息定义在info文件里,含义基本上是很明显的,不用多作解释。其中package参数可以帮助模块的归类,例如把form_example和其它example模块都归类在examples package里会比较好找。dependencies参数则可以声明模块的依赖,表示启用该模块必须用时启用哪些模块。还有一些参数可以声明模块需要用到的js和css文件等。

 

name = "Good Class"
description = "文明班评比管理系统"
package = "Weike"
core = "7.x"
version = "7.x-1.0"
dependencies[] = "school_users"
dependencies[] = 'weike_utility'

3. module文件

我们按照form_example.module代码一段一段分析。

 

/**
 * @file
 * Examples demonstrating the Drupal Form API.
 */

/**
 * @defgroup form_example Example: Form API
 * @ingroup examples
 * @{
 * Examples demonstrating the Drupal Form API.
 *
 * The Form Example module is a part of the Examples for Developers Project
 * and provides various Drupal Form API Examples. You can download and
 * experiment with this code at the
 * @link http://drupal.org/project/examples Examples for Developers project page. @endlink
 */

这一段代码是注释,和很多语言的风格类似,按照这种风格写注释的好处是可以利用类似Doxygen的功能自动为代码生成文档,这样做比较专业。

@file是Drupal特有的标记,叫做docblocks,是Drupal对通用注释语法的扩展。更多细节可以参考API documentation and comment standards

 

/**
 * Implements hook_menu().
 *
 * Here we set up the URLs (menu entries) for the
 * form examples. Note that most of the menu items
 * have page callbacks and page arguments set, with
 * page arguments set to be functions in external files.
 */
function form_example_menu() {
  $items = array();
  $items['examples/form_example'] = array(
    'title' => 'Form Example',
    'page callback' => 'form_example_intro',
    'access callback' => TRUE,
    'expanded' => TRUE,
  );

  $items['examples/form_example/tutorial'] = array(
    'title' => 'Form Tutorial',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_example_tutorial_1'),
    'access callback' => TRUE,
    'description' => 'A set of ten tutorials',
    'file' => 'form_example_tutorial.inc',
    'type' => MENU_NORMAL_ITEM,
  );


  $items['examples/form_example/tutorial/2'] = array(
    'title' => '#2',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_example_tutorial_2'),
    'access callback' => TRUE,
    'description' => 'Tutorial 2: Form with a submit button',
    'type' => MENU_LOCAL_TASK,
    'file' => 'form_example_tutorial.inc',
  );

...

  return $items;
}

hook是Drupal 7里面一个很重要的概念,是实现Drupal 7接口的机制。如果API文档里写着hook_menu,那么我们实现的时候需要把hook替换成自己的模块名。hook_menu是一个模块必须实现的接口,它定义了什么URL映射到什么PHP文件里的函数去处理,但不是说定义了一个入口就会显示在Drupal系统的某个菜单里,定义菜单树关系要通过另外的操作实现。 

hook_menu里定义的路径入口参数中title是页面标题,page callback是响应的函数,file是响应函数所在文件,page arguments是响应函数的参数。对于form页面来说,我们自己写的函数不是填在page callback里,而是作为drupal_get_form的一个参数填在page arguments里。

有些路径的类型是MENU_LOCAL_TASK,这表示它会以标签页(tab)的形式出现,但是同一组标签页的URL前缀需要相同,这是Drupal把标签页归类的标准。


/**
 * Page callback for our general info page.
 */
function form_example_intro() {
  $markup = t('The form example module provides a tutorial, extensible multistep example, an element example, and a #states example');
  return array('#markup' => $markup);
}

在前一段菜单的定义里,我们知道路径examples/form_example是由函数 form_example_intro负责响应的。用t函数把字符串括起来是为了国际化,把字符串翻译成不同的语言,如果觉得自己的模块不可能有国际化的需求就可以省略。对于form页面,我们把HTML代码放在array('#markup' => $markup)数组里返回。对于非form的页面,我们其实是可以直接返回HTML代码的。我们只需要返回局部代码,不需要写<HTML><BODY>等,Drupal会帮我们补充。我们的代码只负责内容区域,菜单等区域是由Drupal其它地方控制的。


/**
 * Implements hook_help().
 */
function form_example_help($path, $arg) {
  switch ($path) {
    case 'examples/form_example/tutorial':
      // TODO: Update the URL.
      $help = t('This form example tutorial for Drupal 7 is the code from the <a href="http://drupal.org/node/262422">Handbook 10-step tutorial</a>');
      break;

    case 'examples/form_example/element_example':
      $help = t('The Element Example shows how modules can provide their own Form API element types. Four different element types are demonstrated.');
      break;
  }
  if (!empty($help)) {
    return '<p>' . $help . '</p>';
  }
}

hook_help是返回模块的帮助信息,不是必须的。

 

/**
 * Implements hook_element_info().
 *
 * To keep the various pieces of the example together in external files,
 * this just returns _form_example_elements().
 */
function form_example_element_info() {
  require_once 'form_example_elements.inc';
  return _form_example_element_info();
}

hook_element_info描述了自定义的表单元素,并不是必须做的步骤,有点复杂, 不展开叙述。

 

/**
 * Implements hook_theme().
 *
 * The only theme implementation is by the element example. To keep the various
 * parts of the example together, this actually returns
 * _form_example_element_theme().
 */
function form_example_theme($existing, $type, $theme, $path) {
  require_once 'form_example_elements.inc';
  return _form_example_element_theme($existing, $type, $theme, $path);
}

hook_theme提供了用户按自己的样式渲染控件的机制,也不是必须的。

4. 模块权限机制

form_example里面没有定义权限,这里补充一下。在module文件里,我们可以增加hook_permission函数定义权限。

 

function good_class_permission() {
  return array(
      'access good class' => array('title' => '访问文明班评比'),
      'config good class' => array('title' => '配置文明班评比'),
  );
}

然后在hook_menu里面的菜单入口定义中增加access argument参数


  $items['good-class/seat'] = array(
      'title' => '座位',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('good_class_seat'),
      'access arguments' => array('access content'),
      'type' => MENU_NORMAL_ITEM,
      'file' => 'good_class_seat.inc',
  ); 

这样,如果用户要访问该URL,就需要在Drupal管理后台给用户对应的角色赋予该项目的访问权限。

5. 区块菜单

很多时候,我们需要为模块提供左侧导航菜单,把模块的功能入口都列出来。在Drupal里,可以通过区块实现这一需求。我们先通过实现hook_block_info来定义一个区块,定义了之后在Drupal管理菜单的“结构->区块”里就会多了一个可选的区块。我们定义的时候就指定了'region' => 'sidebar_first',大部分的Drupal主题都会有sidebar_first区域定义,就是左侧区域。这样模块一启用就会自动在左侧出现模块的导航菜单。

 

function good_class_block_info() {
  $blocks['good_class'] = array(
      'info' => '文明班',
      'status' => TRUE,
      'region' => 'sidebar_first',
  );

  return $blocks;
}

function good_class_block_view($delta) {
  if ($delta == 'good_class' &&
      strpos($_GET['q'], 'good-class/') !== FALSE) {
    $html = '<ul class="menu">';

    $can_query = user_access('access good class');
    $can_edit = good_class_is_admin();
    $can_config = user_access('config good class');

    if ($can_edit) {
      $html .= '<li class="leaf">' . l('录入', 'good-class/add') . '</li>';
    }

    if ($can_query) {
      $html .= '<li class="leaf">' . l('查询', 'good-class/list') . '</li>';
    }

    if ($can_edit) {
      $html .= '<li class="leaf">' . l('评比', 'good-class/evaluate') . '</li>';
    }

    if ($can_query) {
      $html .= '<li class="leaf">' . l('结果', 'good-class/result/list') . '</li>';
    }
    $html .= '<li class="leaf">' . l('座位', 'good-class/seat') . '</li>';

    if ($can_config) {
      $html .= '<li class="leaf">' . l('配置', 'good-class/config') . '</li>';
    }

    $html .= '</ul>';

    $block['subject'] = '文明班评比';
    $block['content'] = $html;
    return $block;
  }

  return array();
}

hook_block_view是模块区块菜单内容的实现,先判断当前区块是自己的区块,URL路径是模块路径的前缀。然后根据权限输出不同的链接。

6. form_example_tutorial_7

form_example_tutorial.inc里有一个简单的Drupal表单例子,可以通过http://localhost/?q=examples/form_example/tutorial/7访问。

Drupal的表单实现方式和普通PHP框架是不同的,一般MVC的表单是先用HTML模板语言实现表单的外观,然后用PHP处理表单的提交。而Drupal的表单只需要在PHP里通过定义若干数组就可以完成了,这是和普通框架很不同的地方。我们经常都需要查询Drupal form的API,在搜索引擎中搜索drupal7 form可以找到其API链接https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7.x/

Drupal通过一些列的数组完成了HTML表单的创建,然后通过hook_validate完成表单的数据校验,再通过hook_submit处理表单的提交。在hook_submit当中,表单提交的值都放在了$form_state['values']数组变量里。具体的用法含义可以通过对比页面显示效果和代码慢慢理解。

 

/**
 * Example 7: With a submit handler.
 *
 * From the handbook page:
 * http://drupal.org/node/717740
 *
 * @see form_example_tutorial_7_validate()
 * @see form_example_tutorial_7_submit()
 *
 * @ingroup form_example
 */
function form_example_tutorial_7($form, &$form_state) {
  $form['description'] = array(
    '#type' => 'item',
    '#title' => t('A form with a submit handler'),
  );
  $form['name'] = array(
    '#type' => 'fieldset',
    '#title' => t('Name'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
  );
  $form['name']['first'] = array(
    '#type' => 'textfield',
    '#title' => t('First name'),
    '#required' => TRUE,
    '#default_value' => "First name",
    '#description' => "Please enter your first name.",
    '#size' => 20,
    '#maxlength' => 20,
  );
  $form['name']['last'] = array(
    '#type' => 'textfield',
    '#title' => t('Last name'),
    '#required' => TRUE,
  );
  $form['year_of_birth'] = array(
    '#type' => 'textfield',
    '#title' => "Year of birth",
    '#description' => 'Format is "YYYY"',
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Submit',
  );
  return $form;
}


/**
 * Validation function for form_example_tutorial_7().
 *
 * @ingroup form_example
 */
function form_example_tutorial_7_validate($form, &$form_state) {
  $year_of_birth = $form_state['values']['year_of_birth'];
  if ($year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
    form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
  }
}

/**
 * Submit function for form_example_tutorial_7().
 *
 * Adds a submit handler/function to our form to send a successful
 * completion message to the screen.
 *
 * @ingroup form_example
 */
function form_example_tutorial_7_submit($form, &$form_state) {
  drupal_set_message(t('The form has been submitted. name="@first @last", year of birth=@year_of_birth',
    array(
      '@first' => $form_state['values']['first'],
      '@last' => $form_state['values']['last'],
      '@year_of_birth' => $form_state['values']['year_of_birth'],
    )
  ));
}

drupal_set_message是一个很常用的把后台消息显示在页面的语句,它包含了Drupal设计里的一种哲学,要在页面显示一样东西不需要写HTML代码。 

标签

添加新评论

Restricted HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id> <img src>
  • 自动断行和分段。
  • Web page addresses and email addresses turn into links automatically.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
请输入"Drupal8"