9.8.1. hook_form_alter() 为现有表单添加提交和验证功能。
在之前的一节课中,我们已经了解了钩子(hooks)的概念,在本节课中,我们将实践使用 hook_form_alter() 钩子,并向现有表单添加功能。
代码示例可以在 GitHub 上找到:
https://github.com/levmyshkin/drupalbook8
在本节课中,我们将开始实践钩子的使用,稍后我们将回到钩子并查看其他一些示例。现在,让我们从 hook_form_alter() 开始。
为了向模块中添加钩子,我们需要创建一个名为 MODULENAME.module 的文件,因此我们将创建一个名为 drupalbook.module 的文件。这个文件将是一个 PHP 文件,我们的钩子和辅助函数将存储在其中,其余的最好将文件和类放在 src 文件夹中。现在,你只需要在文件中添加一个开头标签:
现在,让我们添加 hook_form_alter()。如果你使用 PhpStorm,开始输入钩子的名称,PhpStorm 会提示你选择一个钩子。当你选择钩子时,PhpStorm 会自动将参数插入到函数中,你无需记住或查阅帮助文档来添加必要的参数:
当你想添加一个钩子时,你需要将钩子名称中的 hook 替换为你的模块名称,然后 Drupal 会自动将你的代码插入到钩子位置。最终,你应该有这个函数:
/**
* Implements hook_form_alter().
*/
function drupalbook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
}
在上一节课中,我们已经讨论过,$form、$form_state 中存储的内容,以及 $form_id 中存储的内容,我们将存储表单 ID,该 ID 在相应的方法中定义:
9.8. 在 Drupal 中使用表单。通过代码添加配置表单。
现在让我们扩展上一节课中添加 API 密钥的表单。为此,我们需要定义表单 ID,你可以在这个方法中看到它:
function getFormId() {
return 'drupalbook_admin_settings';
}
或者打开 DOM 检查器并查看 HTML 中的 id 属性:
在这里,我们需要将 -(连字符)替换为 _(下划线)。但请小心,表单标签属性中的 ID 可能在 AJAX 请求后递增,例如,drupalbook-admin-settings-0、drupalbook-admin-settings-1、drupalbook-admin-settings-2 等。我们需要去掉结尾的数字,保留从 getFormId() 方法中指定的表单 ID 部分。
现在,我们需要限制我们的代码仅在 drupalbook_admin_settings 表单中执行,因为在 hook_form_alter() 中的代码将会为通过 Drupal 表单 API 生成的所有表单执行:
($form_id == 'drupalbook_admin_settings') {
// 这里是进一步的代码。
}
在 if 语句中,我们可以编写关于 API 密钥表单的代码。让我们为文本字段添加占位符,以便空字段显示应该填写的内容:
($form_id == 'drupalbook_admin_settings') {
$form['drupalbook_api_key']['#attributes']['placeholder'] = 'API key';
$form['drupalbook_api_client_id']['#attributes']['placeholder'] = 'API client ID';
}
要应用 hook_form_alter(),请清理缓存。
虽然 #attributes 没有在文档中列为 textfield 的可能键:
然而,这个键仍然可以用来指定标签的属性:class、placeholder、不同的 rel 属性。对于 Drupal 7 表单 API,有一个更清晰的表格:
https://api.drupal.org/api/drupal/developer%21topics%21forms_api_reference.html/7.x
你可以在这里查看 textfield 字段的 #attributes。
属性值是在生成(渲染)表单元素时通过 Form API 设置的,在 setAttributes 方法中:
虽然 API 文档中没有明确说明,但你可以看到它是如何工作的。
你还可以在基本的 FormElement 类中看到可以为表单字段设置的其他值:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21FormElement.php/class/FormElement/8.2.x
Drupal 的 Form API 看起来不太清晰和有些混乱,因此我仍然有些东西没有完全理解。但是,使用这个 API 是足够简单且清晰的,只要手边有一个有效的示例。因此,如果你有任何问题,不知道如何在表单中做到某一部分,请上网查找,你肯定会找到 100% 有效的示例。你需要知道的是,所有表单都会经过 Drupal 的缓存、渲染、主题化等 API,任何表单中的字段都可以通过 hook_form_alter() 来修改,关于如何做,你可以通过 Google 查找答案。此外,你接触 Form API 的次数越多,解析它的示例也会越多,Form API 对你来说就会变得越容易理解。不要担心它如此庞大,当前只需使用你所需要的部分即可。
验证
现在,让我们看一下如何通过 Form API 使用验证功能。我们检查 API 密钥字段,确保它以 "google" 开头,例如 google-KEY123a3sa。如果我们最初编写了表单代码,我们可以插入 validateForm() 方法并在其中进行所有检查:
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
}
但通常我们处理的表单都在贡献模块中,最好为这些表单编写自定义模块中的代码。所以我们将向表单添加一个新的验证函数:
['#validate'][] = 'drupalbook_settings_validate';
在 #validate 数组中,我们存储所有回调和验证函数。回调是通过函数名称调用函数,即我们将添加 ['callback_function1', 'callback_function2'] 数组,然后 Drupal 会调用这些函数来检查表单。现在,我们需要在 drupalbook.module 文件中创建 drupalbook_settings_validate 函数。在这个函数中,我们将有 $form 和 $form_state 参数:
/**
* Custom validation callback.
*/
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
}
现在让我们添加 API 密钥字段本身的检查:
/**
* Custom validation callback.
*/
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
if (strpos($form_state->getValue('drupalbook_api_key'), 'google') === FALSE) {
$form_state->setErrorByName('drupalbook_api_key', t('API Key must start from "google".'));
}
}
我们使用严格比较操作符 ===,因为如果 "google" 位于字符串的开头,strpos() 返回 0,而在 PHP 中(0 == FALSE)会返回 TRUE,因为在 PHP 中,''、0、NULL、FALSE 都是空值,它们在简单比较 == 中是相等的。
现在,每次保存设置表单时,都会执行检查,如果未通过检查,Drupal 将生成一个错误,设置不会保存:
提交
在所有验证函数执行并且没有错误的情况下,Drupal 会调用提交函数,它们会在提交表单数据后执行。你已经在上一节课中看过了 submitForm() 方法,我们在其中保存数据到配置中。但是我们也可以在提交时执行其他操作,例如更改数据或将数据保存到其他实体中。让我们做一个验证函数,当 API 密钥使用时,显示一条额外的消息。在 hook_form_alter() 中,我们将添加函数名称:
['#submit'][] = 'drupalbook_settings_submit';
现在,在 drupalbook_settings_settings_submit() 函数中,我们将传递 $form 和 $form_state 参数,在该函数中显示消息。
/**
* Custom submit callback.
*/
function drupalbook_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
\Drupal::messenger()->addStatus(
t(htmlentities('Insert API key in your tag: .'))
);
}
我们使用 htmlentities() 函数来确保所有用于输出的特殊字符(如 "<"、"/"、">")不会被 Drupal 删除。所有 HTML 文本都通过 t() 函数切割,因此它不能被粘贴到文本中。例如,如果这段代码包含 JavaScript 重定向到另一个站点,显示消息时就会发生重定向。这就是为什么我们使用 htmlentities 函数来显示标签。
当验证函数失败时,消息不会显示,只有在表单没有错误时,我们才能看到消息。
以前,drupal_set_message 函数用于在 Drupal 中输出消息:
(t('An error occurred and processing did not complete.'));
但现在大家都尽量统一并将 OOP 用到每个地方。
以下是完整的 drupalbook.module 文件当前代码:
/**
* Implements hook_form_alter().
*/
function drupalbook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
if ($form_id == 'drupalbook_admin_settings') {
$form['drupalbook_api_key']['#attributes']['placeholder'] = 'API key';
$form['drupalbook_api_client_id']['#attributes']['placeholder'] = 'API client ID';
$form['#validate'][] = 'drupalbook_settings_validate';
$form['#submit'][] = 'drupalbook_settings_submit';
}
}
/**
* Custom validation callback.
*/
function drupalbook_settings_validate(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
if (strpos($form_state->getValue('drupalbook_api_key'), 'google') === FALSE) {
$form_state->setErrorByName('drupalbook_api_key', t('API Key must start from "google".'));
}
}
/**
* Custom submit callback.
*/
function drupalbook_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
// drupal_set_message is deprecated
// drupal_set_message(t('An error occurred and processing did not complete.'));
\Drupal::messenger()->addStatus(
t(htmlentities('Insert API key in your tag: .'))
);
}
这就是我们关于 Form API 的课程结束了,但这并不是学习 Form API 的终点,我们将在接下来的课程中再次遇到验证、提交以及各种字段的函数。我们还将在其中一节课中了解如何通过 Form API 使用 AJAX 和 form_states。
代码示例可以在 GitHub 上找到:
https://github.com/levmyshkin/drupalbook8