你在这里

Drupal 8 表单API

原文链接:https://www.drupal.org/node/2117411

 

Drupal 8 表单API

Druapl 8 表单API大体上和Drupal 7的版本类似, 表现形式仍然是嵌套的数组结构.当然还有分开的表单验证和提交步骤.现在可以添加一些新的(html 5)表单元素了,而如何将这些组件集成到其他Drupal系统中是有一些变化的.

新的(html 5) 表单元素

你可以查看 system_element_info()来知道所有drupal内核支持的表单元素.举一些新的html 5表单元素的例子:'#type' => 'tel', '#type' => 'email', '#type' => 'number', '#type' => 'date', '#type' => 'url', '#type' => 'search', '#type' => 'range'等等.如果你需要的表单元素不仅仅只是能录入任意字符,你应该考虑它们, 因为它们能让设备找到最合适的输入方法.举个例子,你想让用户输入一个电话号码时应该弹出拨号面板. 也有其他表单元素也加入到drupal 8来丰富你的表单.'#type' => 'details' 这是一个带有概括文字的分组元素;'#type' => 'language_select' 这个是一个容易配置的语言选择器;像'#type' => 'dropbutton' 和 '#type' => 'operations'这些下拉链接的控件你可能以及在Views模块里以及见过了,现在它们在drupal 8里面被广泛运用.

概述

表单类实现了\Drupal\Core\Form\FormBuilderInterface接口.一个表单的工作流程是由该接口的buildForm, validateForm和submitForm方法来定义的.当一个表单被请求时,它是被定义承一个可渲染的数组(表单API数组或者简单的说$form数组).$form数组被内部处理转承html代码然后展示给用户.当一个用户提交表单时,请求被发往包含表单的当前地址,Drupal会注意到有POST请求,它先处理表单,接着调用合适的数据验证和提交的代码(没有POST时只是处理表单,展示html).

把表单定义成有结构的数组相比直接写成html代码有很多优势:

  • 所有表单统一的html输出
  • 一个模块定义的表单可以很容易的被另一个模块改变,而不需要做复杂的字符串搜索替换.
  • 可以封装一些复杂包括展示和数据处理的控件,比如文件上传,投票控件等.

定义表单

定义表单必须实现\Drupal\Core\Form\FormBuilderInterface接口.所有表单相关的逻辑如表单的创建,提交,验证都由表单类处理.

有一些不同的预定义父类你可以根据情况选择.绝大多数情况下在创建表单时,你只需要继承下面的类.

  • ConfigFormBase 为创建类似admin/config/system/site-information的系统配置表单.
  • ConfirmFormBase 提供带确认提交功能的表单,比如你要删除一个内容时.
  • FormBase 创建表单最通用的基类.

随便你继承哪个父类,你很可能实现的第一个接口都是

public function getFormId()

这只需要返回一个字符串来作为表单的唯一标识符.最佳的做法是用你的模块名称做其前缀.

举例:

<?php
 
public function getFormId() {
    return
'mymodule_settings';
  }
?>
public function buildForm(array $form, FormStateInterface $form_state) buildForm方法返回一个定义所有表单元素的表单API数组.

举例:

<?php
 
public function buildForm(array $form, FormStateInterface $form_state) {
   
$form['my_text_field'] = array(
     
'#type' => 'textfield',
     
'#title' => 'Example',
    );
.
   
//别忘了调用父类的buildForm方法,这样我们可以利用父类以及实现的功能.
   
return parent::buildForm($form, $form_state);
  }
?>
验证表单

在用户填完表单并提交之后,正常情况是需要验证数据的.用Drupal的表单API来做数据验证是非常简单的,只需要实现\Drupal\Core\Form\FormBuilderInterface接口的validateForm方法.你可以在我们的FormExample类里查看相关例子.

用户提交的数据可以在$form_state['values']里找到.$form_state['values']也是一个数组,它的每一个键值都和你创建表单数组$form时添加的表单元素的名称一一对应(查看FormExample::buildFrom()了解更多). 如果你有一个电话号码的表单项,你可以从$form_state['values']['phone_number']读取到电话号码的值然后用其做自定义的数据验证.

表单验证方法可以用任意合适的PHP代码来判断表单值是否正确,不正确的值话可以报告一个错误事件.下面的例子我们继承了\Drupal\Core\Form\FormBase类,所以我们能用\Drupal\Core\Form\FormBase::setFormError()在特定的表单元素上注册一个错误,并且提供相应的消息来描述该错误.

当表单被提交时,Drupal同时用内部预定义验证方法和我们自定义的验证方法去发现所有的输入错误.然后该表单的html代码被重新生成并带有高亮错误信息.这样用户可以修正错误以再次提交表单.

下面是一个简单的validateForm()方法例子:

<?php
/**
 * {@inheritdoc}
 */
public function validateForm(array &$form, FormStateInterface $form_state) {
  if (
strlen($form_state['values']['phone_number']) < 3) {
   
$this->setFormError('phone_number', $form_state, $this->t('电话号码太短了. 请输入完整的电话号码.'));
  }
}
?>
如果表单验证过程中没有任何错误被注册,Drupal就继续处理表单本身.到这个点的时候Drupal就认为$form_state['values']数组是合法的,然后我们的模块就可以对提交的数据做任何操作了.

提交表单/处理表单数据

最后,我们已经确保可以使用提交的数据了, 我们可以将数据持久化到数据库或者发一封电子邮件,或者其他任何事情. 要这样做我们需要实现FormExample类中\Drupal\Core\Form\FormBuilderInterface的submitForm接口. 像上面的验证方法的用法,用户提交的数据都可以从$form_state['values']数组读取到,并且我们可以认为数据已经验证过,可以直接使用.要获取电话号码的值,只需读取$form_state['values']['phone_numer']就可以了. 下面是一个简单的submitForm方法,它会将我们提交的 'phone_numer'表单项的值用drupal_set_message()显示在页面上.

<?php
/**
 * {@inheritdoc}
 */
public function submitForm(array &$form, FormStateInterface $form_state) {
 
drupal_set_message($this->t('Your phone number is @number', array('@number' => $form_state['values']['phone_number'])));
}
?>
这个处理提交数据的例子有点简单过头.如果你想接触更复杂的例子,请参考drupal内核一些继承FormBase的类. 下面是一个完整的表单类例子: 如果你的模块是/modules/example, 该文件则位于 modules/example/lib/Drupal/example/Form/ExampleForm.php:
<?php
/**
 * @file
 * Contains \Drupal\example\Form\ExampleForm.
 */
namespace Drupal\example\Form;
use
Drupal\Core\Form\FormBase;
use
Drupal\Core\Form\FormStateInterface;
/**
 * Implements an example form.
 */
class ExampleForm extends FormBase {
 
/**
   * {@inheritdoc}.
   */
 
public function getFormId() {
    return
'example_form';
  }
 
/**
   * {@inheritdoc}.
   */
 
public function buildForm(array $form, FormStateInterface $form_state) {
   
$form['phone_number'] = array(
     
'#type' => 'tel',
     
'#title' => $this->t('Your phone number')
    );
   
$form['actions']['#type'] = 'actions';
   
$form['actions']['submit'] = array(
     
'#type' => 'submit',
     
'#value' => $this->t('Save'),
     
'#button_type' => 'primary',
    );
    return
$form;
  }
 
/**
   * {@inheritdoc}
   */
 
public function validateForm(array &$form, FormStateInterface $form_state) {
    if (
strlen($form_state['values']['phone_number']) < 3) {
     
$this->setFormError('phone_number', $form_state, $this->t('The phone number is too short. Please enter a full phone number.'));
    }
  }
 
/**
   * {@inheritdoc}
   */
 
public function submitForm(array &$form, FormStateInterface $form_state) {
   
drupal_set_message($this->t('Your phone number is @number', array('@number' => $form_state['values']['phone_number'])));
  }
}
?>
在Drupal 7里表单生成器的函数名称就是表单的ID, 而在Drupal 8里表单的ID需要由表单类的getFormId方法返回。生成器方法叫做buildForm(), 并且验证和提交都有专门的方法。你在这些方法里写的逻辑,表单数组结构,表单处理过程都和Drupal 7中用法类似。 将表单融入请求 路由系统可以让表单类被提供承路由处理者,也就是说路由系统负责实例化这个类并且调用合适的方法。你得有下面一种路由配置来将该表单融入Drupal网站的URI结构: 如果模块在 /modules/example下, 则文件内容应该在/modules/example/example.routing.yml中: example.form: path: '/example-form' defaults: _title: 'Example form' _form: '\Drupal\example\Form\ExampleForm' requirements: _permission: 'access content' 这个_form的键告诉路由系统,提供的类名称是一个表单类,可以初始化并且处理成一个表单。 注意,要让一个表单工作,写表单类和配置路由是仅需要的两项工作,没任何其他代码要写。 在路由之外取得该表单 虽然drupal 7的drupal_get_form()函数在drupal 8里面以及不见了,但是我们有一个FormBuilder服务,可以用来获取和处理表单。Drupal 8的drupal_get_form()替代品如下:
<?php
$form
= \Drupal::formBuilder()->getForm('Drupal\example\Form\ExampleForm');
?>
方法getForm()接受的参数就是你定义的表单(实现了\Drupal\Core\Form\FormBuilderInterface)的类名.如果你想传入额外的参数给表单,就传在类名称后面。 例子:
<?php
$extra
= '612-123-4567';
$form = \Drupal::formBuilder()->getForm('Drupal\mymodule\Form\ExampleForm', $extra);
...
public function
buildForm(array $form, FormStateInterface $form_state, $extra = NULL)
 
$form['phone_number'] = array(
   
'#type' => 'tel',
   
'#title' => $this->t('Your phone number'),
   
'#value' => $extra,
  );
  return
$form;
}
?>
在一些特殊情况下,你可能需要在FormBuilder调用你的类的buildForm()方法前操作表单对象,这种情况下你可以这么做:
<?php
 $form_object
= new \Drupal\mymodule\Form\ExampleForm($something_special); $form_builder->getForm($form_object);
?>
改变表单 在Drupal 8中改变表单和drupal 7中差不多。假设你提供了表单ID,则利用hook_form_alter()和/或hook_form_FORM_ID_alter()来改变表单。
<?php
/**
 * Implements hook_form_FORM_ID_alter().
 */
function example2_form_example_form_alter(&$form, &$form_state) {
 
$form['phone_number']['#description'] = t('Start with + and your country code.');
}
?>
模块名称(example2)加上表单ID(example_form)便可以用来命名hook_form_FORM_ID_alter()钩子。如果你使用过Drupal 7的改变表单相关功能,你会发现在drupal 8中是一样的。