問:如何校驗和提交表單?
答:Drupal容許定義默認的表單校驗處理函數和提交處理函數。html
function practice_demo_form($form, &$form_state) { ... ... return $form; } function practice_demo_form_validate($form, &$form_state) { if (...) { form_set_error(...); } } function practice_demo_form_submit($form, &$form_state) { ... ... }
這些函數在drupal_prepare_form()中查找:ajax
function drupal_prepare_form($form_id, &$form, &$form_state) { ... if (!isset($form['#validate'])) { // Ensure that modules can rely on #validate being set. $form['#validate'] = array(); // Check for a handler specific to $form_id. // 默認的表單校驗函數以_validate結尾 if (function_exists($form_id . '_validate')) { $form['#validate'][] = $form_id . '_validate'; } // Otherwise check whether this is a shared form and whether there is a // handler for the shared $form_id. elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) { $form['#validate'][] = $form_state['build_info']['base_form_id'] . '_validate'; } } if (!isset($form['#submit'])) { // Ensure that modules can rely on #submit being set. $form['#submit'] = array(); // Check for a handler specific to $form_id. // 默認的表單提交函數以_submit結尾 if (function_exists($form_id . '_submit')) { $form['#submit'][] = $form_id . '_submit'; } // Otherwise check whether this is a shared form and whether there is a // handler for the shared $form_id. elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_submit')) { $form['#submit'][] = $form_state['build_info']['base_form_id'] . '_submit'; } } ... }
問:$form_state['build_info']['args']是什麼用途?
答:$form_state['build_info']['args']是page arguments中除第一個參數外其它全部的參數,第一個參數是表單標識$form_id。瀏覽器
function practice_menu() { $items['practice/demo'] = array( 'title' => 'Demo', 'page callback' => 'drupal_get_form', // 第一個參數是表單標識practice_demo_from // 第二個參數是ultramen,第三個參數是25 'page_arguments' => array('practice_demo_from', 'ultramen', 25), 'access callback' => TRUE, ); return $items; } function practice_demo_form($form, &$form_state, $name, $age) { ... ... } function drupal_get_form($form_id) { $form_state = array(); $args = func_get_args(); // Remove $form_id from the arguments. array_shift($args); $form_state['build_info']['args'] = $args; return drupal_build_form($form_id, $form_state); }
問:form_build_id是什麼?
答:Drupal爲每一個表單定義一個惟一的form build id,是一組隨機字符串。form build id在客服端瀏覽器以hidden field的方式呈現,表單提交時回傳給Drupal。緩存
function drupal_prepare_form($form_id, &$form, &$form_state) { ... // Generate a new #build_id for this form, if none has been set already. The // form_build_id is used as key to cache a particular build of the form. For // multi-step forms, this allows the user to go back to an earlier build, make // changes, and re-submit. // @see drupal_build_form() // @see drupal_rebuild_form() // Drupal爲每一個表單定義一個惟一的form_build_id if (!isset($form['#build_id'])) { $form['#build_id'] = 'form-' . drupal_random_key(); } // form_buikd_id會在瀏覽器表單中以hidden的形式出現 $form['form_build_id'] = array( '#type' => 'hidden', '#value' => $form['#build_id'], '#id' => $form['#build_id'], '#name' => 'form_build_id', // Form processing and validation requires this value, so ensure the // submitted form value appears literally, regardless of custom #tree // and #parents being set elsewhere. '#parents' => array('form_build_id'), ); // 同form_build_id if (isset($form_id)) { $form['form_id'] = array( '#type' => 'hidden', '#value' => $form_id, '#id' => drupal_html_id("edit-$form_id"), // Form processing and validation requires this value, so ensure the // submitted form value appears literally, regardless of custom #tree // and #parents being set elsewhere. '#parents' => array('form_id'), ); } if (!isset($form['#id'])) { $form['#id'] = drupal_html_id($form_id); } ... }
form build id用來作什麼?Drupal用它來標識緩存的表單數據。session
function drupal_build_form($form_id, &$form_state) { ... // If the incoming input contains a form_build_id, we'll check the cache for a // copy of the form in question. If it's there, we don't have to rebuild the // form to proceed. In addition, if there is stored form_state data from a // previous step, we'll retrieve it so it can be passed on to the form // processing code. // 用$form_state['input']['form_id']標識表單,例如practice_demo_form // 用$form_state['input']['form_build_id']標識緩存 $check_cache = isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id']); if ($check_cache) { $form = form_get_cache($form_state['input']['form_build_id'], $form_state); } ... } function drupal_process_form($form_id, &$form, &$form_state) { ... // After processing the form, the form builder or a #process callback may // have set $form_state['cache'] to indicate that the form and form state // shall be cached. But the form may only be cached if the 'no_cache' property // is not set to TRUE. Only cache $form as it was prior to form_builder(), // because form_builder() must run for each request to accommodate new user // input. Rebuilt forms are not cached here, because drupal_rebuild_form() // already takes care of that. // 用$form_state['cache']和$form_state['no_cache']標識是否容許表單緩存 if (!$form_state['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) { form_set_cache($form['#build_id'], $unprocessed_form, $form_state); } ... }
問:form token是什麼?
答:Drupal爲每一個表單定義一個form token,與當前session會話關聯在一塊兒。form token的存在是爲了不處理長時間未提交的表單。app
function drupal_prepare_form($form_id, &$form, &$form_state) { ... // Add a token, based on either #token or form_id, to any form displayed to // authenticated users. This ensures that any submitted form was actually // requested previously by the user and protects against cross site request // forgeries. // This does not apply to programmatically submitted forms. Furthermore, since // tokens are session-bound and forms displayed to anonymous users are very // likely cached, we cannot assign a token for them. // During installation, there is no $user yet. if (!empty($user->uid) && !$form_state['programmed']) { // Form constructors may explicitly set #token to FALSE when cross site // request forgery is irrelevant to the form, such as search forms. if (isset($form['#token']) && $form['#token'] === FALSE) { unset($form['#token']); } // Otherwise, generate a public token based on the form id. else { $form['#token'] = $form_id; // 將form token發送到客戶端 $form['form_token'] = array( '#id' => drupal_html_id('edit-' . $form_id . '-form-token'), '#type' => 'token', '#default_value' => drupal_get_token($form['#token']), // Form processing and validation requires this value, so ensure the // submitted form value appears literally, regardless of custom #tree // and #parents being set elsewhere. '#parents' => array('form_token'), ); } } ... } function drupal_validate_form($form_id, &$form, &$form_state) { ... // If the session token was set by drupal_prepare_form(), ensure that it // matches the current user's session. // $form['#token']是表單標識,例如practice_demo_form if (isset($form['#token'])) { if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) { $path = current_path(); $query = drupal_get_query_parameters(); $url = url($path, array('query' => $query)); // Setting this error will cause the form to fail validation. form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url))); // Stop here and don't run any further validation handlers, because they // could invoke non-safe operations which opens the door for CSRF // vulnerabilities. $validated_forms[$form_id] = TRUE; return; } } ... }
問:$form_state['input']是怎麼來的?
答:$form_state['input']來自與$_GET或者$_POST,取決於$form_state['method']。less
function drupal_build_form($form_id, &$form_state) { ... if (!isset($form_state['input'])) { $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST; } ... }
問:$form_state['values']是怎麼來的?
答:$form_state['values']是$form_state['input']通過過濾處理後的結果。$form_state['input']是原始數據,$form_state['values']是過濾後的數據。
dom
問:$form_state['rebuild']是用來作什麼的?
答:不清楚。
問:$form_state['redirect']是用來作什麼的?
答:redirect用在表單提交成功後,表示須要重定向的新地址。等同於調用drupal_goto()。ide
function practice_demo_form_submit($form, &$form_state) { $form_state['redirect'] = 'admin/people/expiration'; // 等同 drupal_goto('admin/people/expiration'); } function drupal_redirect_form($form_state) { // Skip redirection for form submissions invoked via drupal_form_submit(). if (!empty($form_state['programmed'])) { return; } // Skip redirection if rebuild is activated. if (!empty($form_state['rebuild'])) { return; } // Skip redirection if it was explicitly disallowed. if (!empty($form_state['no_redirect'])) { return; } // Only invoke drupal_goto() if redirect value was not set to FALSE. if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) { if (isset($form_state['redirect'])) { if (is_array($form_state['redirect'])) { call_user_func_array('drupal_goto', $form_state['redirect']); } else { // This function can be called from the installer, which guarantees // that $redirect will always be a string, so catch that case here // and use the appropriate redirect function. $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto'; $function($form_state['redirect']); } } drupal_goto(current_path(), array('query' => drupal_get_query_parameters())); } }
問:$form_state['submitted']是用來作什麼的?
答:不清楚。
問:$form_state['executed']是用來作什麼的?
答:不清楚。
問:$form_state['programmed']是用來作什麼的?
答:不清楚。
問:$form_state['groups']是用來作什麼的?
答:不清楚。
問:$form_state['buttons']是用來作什麼的?
答:buttons是表單中全部的按鈕。什麼樣的元素是按鈕?在hook_element_info()中用button_type標識。函數
// If the form was submitted by the browser rather than via Ajax, then it // can only have been triggered by a button, and we need to determine which // button within the constraints of how browsers provide this information. if (isset($element['#button_type'])) { // All buttons in the form need to be tracked for // form_state_values_clean() and for the form_builder() code that handles // a form submission containing no button information in $_POST. $form_state['buttons'][] = $element; if (_form_button_was_clicked($element, $form_state)) { $form_state['triggering_element'] = $element; } } function system_element_info() { $types['submit'] = array( '#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#limit_validation_errors' => FALSE, '#process' => array('ajax_process_form'), '#theme_wrappers' => array('button'), ); $types['button'] = array( '#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE, '#limit_validation_errors' => FALSE, '#process' => array('ajax_process_form'), '#theme_wrappers' => array('button'), ); }
問:$form_state['triggering_element']是用來作什麼的?
答:triggering_element用來標識是哪一個元素觸發的表單提交事件。一般是按鈕。
問:可不能夠爲某個表單定製主題?
答:能夠的。Drupal默認用表單標識做爲該表單的主題。
function drupal_prepare_form($form_id, &$form, &$form_state) { ... // If no #theme has been set, automatically apply theme suggestions. // theme_form() itself is in #theme_wrappers and not #theme. Therefore, the // #theme function only has to care for rendering the inner form elements, // not the form itself. if (!isset($form['#theme'])) { $form['#theme'] = array($form_id); // 表單默認主題 if (isset($form_state['build_info']['base_form_id'])) { $form['#theme'][] = $form_state['build_info']['base_form_id']; } } ... }
問:可不能夠用alter鉤子修改表單?
答:能夠的。Drupal構造表單時,容許爲表單定義通用和專用兩種alter鉤子。
function drupal_prepare_form($form_id, &$form, &$form_state) { ... // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and // hook_form_FORM_ID_alter() implementations. $hooks = array('form'); // 表單通用鉤子 if (isset($form_state['build_info']['base_form_id'])) { $hooks[] = 'form_' . $form_state['build_info']['base_form_id']; } $hooks[] = 'form_' . $form_id; // 表單專用鉤子 drupal_alter($hooks, $form, $form_state, $form_id); ... }