天天看點

雲客Drupal源碼分析之字段控件FieldWidget

   在某些語境下控件等同于表單中的輸入标簽,如input、select、textarea等,如在談論前端設計時,而本篇所指字段控件是程式上的控件對象,用于為字段類型産生輸入表單的渲染數組,并負責在表單處理流程中提取輸入值、标記驗證錯誤等,解決字段類型的使用者輸入問題,本篇講解drupal控件的實作。

   字段控件是以插件方式提供,在其釋文定義中用field_types指定她可以被哪些字段類型使用,每個字段類型的插件定義中應該以default_widget指定預設使用的控件,管理者可以在管理表單顯示頁面為字段指定其他可使用的控件

字段控件插件管理器:

服務id:plugin.manager.field.widget

類:\Drupal\Core\Field\WidgetPluginManager

擷取方式:\Drupal::service('plugin.manager.field.widget')

插件定義緩存位置:緩存表cache_discovery 的field_widget_types_plugins條目

插件儲存目錄:Plugin/Field/FieldWidget

字段控件插件定義修改鈎子名:field_widget_info

釋文類:\Drupal\Core\Field\Annotation\FieldWidget

控件接口:Drupal\Core\Field\WidgetInterface

控件插件管理器方法說明:

public function getInstance(array $options)

public function createInstance($plugin_id, array $configuration = [])

見下文:控件插件的執行個體化

public function prepareConfiguration($field_type, array $configuration)

參數$field_type為字段類型id,參數$configuration來自字段定義中的以下方法:

$field_definition->getDisplayOptions(‘form’);

合并顯示選項中的子健settings與控件的預設設定選項,字段定義優先,其中無效多餘的settings将去除

public function getOptions($field_type = NULL)

傳回字段類型可用的控件,參數為字段類型插件id,傳回值是一個數組,鍵名為字段類型可用的控件插件id,鍵值為控件label,如果沒有傳遞字段類型參數,将傳回所有字段類型的該數組,此時第一級鍵名為字段類型插件id

public function getDefaultSettings($type)

參數為控件插件id,擷取某個字段控件的預設配置選項,也就是調用控件的靜态方法:defaultSettings()

控件插件的執行個體化:

在渲染内容實體表單時,有顯示選項且允許顯示的字段,其表單渲染數組的産生、值提取等在實體表單顯示對象中進行,在該對象内部通過字段的表單顯示配置确定一個控件類型,并通過控件插件管理器執行個體化控件對象,最終由控件對象來完成相應工作,詳見以下方法:

    \Drupal\Core\Entity\Entity\EntityFormDisplay::getRenderer

該方法執行個體化過程相當于在執行以下代碼:

    \Drupal::service('plugin.manager.field.widget') ->getInstance($options);

參數$options是一個數組,有如下鍵名:

field_definition:字段定義對象

form_mode:原始請求的表單模式originalMode

prepare:布爾值,是否需要合并控件預設值,預設為FALSE,因為表單顯示對象中儲存的配置是合并了控件預設配置的

configuration:字段對應的表單顯示配置,儲存在表單顯示配置的字段鍵名下(詳見本系列實體表單顯示主題),這裡假設表單顯示對象為$entityFormDisplay,字段名為$field_name(字元串值),可通過以下代碼檢視:

$entityFormDisplay->getComponent($field_name)

執行個體化為什麼沒有直接使用createInstance方法呢?那是因為如果配置錯誤(如控件不支援字段類型、不适用等),将回退使用字段的預設控件,最終還是會統一調用插件管理器的建立執行個體方法:

    \Drupal::service('plugin.manager.field.widget')->createInstance($plugin_id, $configuration)

其中$configuration來自前文提到的字段表單顯示配置,是一個數組,有以下鍵:

type:要使用的控件類型id,如省略、配置錯誤或不适用将使用字段類型定義中指定的預設控件

weight:字段顯示排序的權重

region:顯示字段所在的區域通常為content

settings:用于控件本身的設定,來自字段定義顯示選項和控件預設值,可由控件提供的配置表單設定

third_party_settings:第三方子產品附加的設定,以子產品名做第一級鍵名

此外被附加了鍵名field_definition其值為字段定義對象,請熟知該數組的結構,對于插件而言:

如果實作了容器工廠接口那麼将用插件類自身的建立方法執行個體化,并傳入如下參數:

    $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition);

反之則直接執行個體化,傳入如下參數:

    $plugin_class($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);

後三個參數來自$configuration的相應鍵名

控件對象執行個體化後在内容實體表單流程中的執行入口為:

建構表單:

$form[$name] = $widget->form($items, $form, $form_state);

提取表單值:

$widget->extractFormValues($items, $form, $form_state);

标記驗證錯誤:

$widget->flagErrors($items, $field_violations, $form, $form_state);

以上方法中$items為内容實體的字段對象屬性:

$items = $entity->get($name);

參數$form為建構中的完整的内容實體表單渲染數組

控件插件定義:

和drupal其他插件定義一樣,詳見本系列插件篇,這裡對字段控件的釋文說明如下:

id:控件ID

label:控件人類可讀标簽

description:控件人類可讀描述

field_types:控件支援的字段類型,數組值,元素值為字段類型id

weight:控件預設權重,用于與其他控件在一起時的排序

multiple_values:布爾值,單個字段條目控件能否一次性處理多個值,預設為false,如為true則需要指定

這裡需要對multiple_values詳細解釋一下:

其值為true的控件稱為多值控件,這裡的多值是什麼意思呢?我們知道一個字段對象是一個清單類型的資料對象,在将其傳給控件對象渲染時是通過以下方法:

    $widget->form($items, $form, $form_state);

在該方法内部會對清單中的每一個條目調用以下方法進行渲染:

    $widget->formElement($items, $delta, $element, $form, $form_state);

在大多數情況下,字段對象中有多少個條目(換句話說就是該字段對象有多少個值)就會調用formElement方法多少次,每次調用隻渲染一個條目,然後将每次調用的結果組合為一個數組,該數組就是這個字段的控件渲染結果,但是有一種控件隻需要調用一次formElement方法就能渲染字段對象中所有的條目,這樣的控件就是多值控件,其插件釋文中的multiple_values屬性就應該辨別為true,多值控件也用于處理字段對象不可能會有多值的字段類型,如布爾字段類型(雖然字段對象依然是清單資料類型,但隻會有一個條目),在預設提供的控件中有以下控件是多值的,控件id及類名如下:

boolean_checkbox:

Drupal\Core\Field\Plugin\Field\FieldWidget\BooleanCheckboxWidget

entity_reference_autocomplete_tags:

Drupal\Core\Field\Plugin\Field\FieldWidget\EntityReferenceAutocompleteTagsWidget

options_buttons:

Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsButtonsWidget

options_select:

Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget

注意不要将多值控件和字段的多屬性混淆,多屬性字段是指字段對象中單個條目就有多個屬性,這些屬性會在一次formElement方法調用中渲染;通常多屬性字段不會使用多值控件,這裡假設一個字段類型由A、B兩個屬性組成,A是單值的 B是多值的 那麼控件依然應該使用單值控件,因為一個字段對應一個控件,而不是一個屬性對應一個控件

簡而言之:如果單次調用formElement()的結果能處理字段對象中多個條目的送出,則控件就是多值的

在字段的儲存定義中有一個方法getCardinality()用于傳回字段對象可以輸入多少個值,如果是單值控件該值将限制formElement方法調用的次數,如果是多值控件,則對控件渲染沒有影響,僅調用一次,但在表單驗證時會限制送出值的個數

字段輸入控件類需要實作控件接口:Drupal\Core\Field\WidgetInterface

該接口繼承了控件基接口:\Drupal\Core\Field\WidgetBaseInterface

為何要定義成兩個接口呢?drupal将通常來說會直接繼承使用的方法規劃在控件基接口中,而将通常需要被覆寫的方法定義在控件接口中。

預設基類:

系統提供了以下字段控件的預設實作:

\Drupal\Core\Field\WidgetBase

她實作了以上的接口,控件類通常需要繼承該基類,這裡結合接口對方法說明如下:

public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL);

為一個字段對象建立完整表單元素,參數$items來自$entity->get($name);,如果是建立的實體會填充預設值,參數$get_delta指定隻傳回多值字段(不是多屬性字段)中的某個下标值的表單元素,注意建立的表單元素是傳回而不是直接修改傳入的表單數組

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state);

字段對象是一個清單型資料對象,可以有一個或多個元素,她們代表字段的多個值(注意這裡不是指字段的多屬性),每一個元素都需要一個控件表單,在渲染字段對象的完整表單時,如果控件對象是單值控件對象,則會按下标依次為每個值調用一次該方法,以傳回每個值的控件表單(字段條目表單渲染數組),如果是多值控件對象,則該方法僅被調用一次,她傳回代表整個字段對象的控件表單;參數$element是依據字段配置産生的基本表單元素,包含了基本資訊,該方法應該在此基礎上繼續建構

public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state);

從送出的表單值中提取字段值到字段對象,第一個參數因為是對象,是以預設被以引用方式修改

public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state);

标記字段級别的所有驗證錯誤,實體表單有兩個級别的驗證錯誤:實體級别和字段級别,該方法僅報告字段級錯誤,且如果在實體級驗證中已經報告了該字段的錯誤,則直接傳回,不再報告,參數$items是字段對象,來自$entity->get($name);,參數$violations是Symfony原生的限制違列清單對象:

    \Symfony\Component\Validator\ConstraintViolationList

其中的限制違列對象的屬性路徑($violation->getPropertyPath();)被去掉了字段名字首,如:

    “title.0.value” 将變為“0.value”

關于錯誤标記和實體表單驗證更多資訊可參見本系列實體表單驗證上下集

public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state);

在标記字段對象的每個條目錯誤時,該方法進一步傳回正确的條目表單子元素,錯誤消息将标記到傳回的子元素上,參數$element是由formElement方法傳回的内容,該方法允許依據控件内部結構進一步精确标記到更加下一級的控件子元素上,如字段條目對象的屬性對象對應的表單控件上,如果傳回false将忽略驗證錯誤;如果字段對象隻有一個屬性,通常原樣傳回。

public static function getWidgetState(array $parents, $field_name, FormStateInterface $form_state);

public static function setWidgetState(array $parents, $field_name, FormStateInterface $form_state, array $field_state);

設定和取回控件狀态資訊,狀态資訊輔助其他功能的執行,如值提取、錯誤标記,這是一個數組,包含以下兩個鍵名:

items_count:為顯示字段對象所需要的控件數量(前端頁面可以動态添加)

array_parents:數組值,表示控件在表單結構中的位置,注意是數組位置,而不是值位置

可能還會有original_deltas,其表示原始的下标值(在過濾空條目後,下标值會變)

參數$parents是控件的值位置,也就是#parents

public function settingsForm(array $form, FormStateInterface $form_state);

傳回控件自身的配置表單,在管理表單顯示頁面使用,以供管理者配置控件,見:

\Drupal\field_ui\Form\EntityDisplayFormBase

系統會将送出的值存放到表單顯示配置中字段元件的settings鍵下,該資料也是控件的構造參數之一,注意該表單不要設定#parents、#tree屬性以免影響值提取,更多詳細可參見本系列實體表單顯示主題

public function settingsSummary();

用于在管理表單顯示頁面的字段行中顯示控件目前的設定資訊,傳回一個字元串數組,每個元素代表一條目前控件配置的摘要資訊,子產品可以通過field_widget_settings_summary修改鈎去修改傳回值

public function massageFormValues(array $values, array $form, FormStateInterface $form_state);

将送出的表單值轉化為字段對象要求的資料類型,以便字段對象的$items->setValue($values)方法直接使用,參數$values是表單送出的整個字段的值,而不是某個條目,該方法在值提取過程中被調用

public static function isApplicable(FieldDefinitionInterface $field_definition);

傳回控件能否用于被提供的字段,雖然在控件插件釋文中已經通過field_types表明了她支援的字段類型,但由于字段是可配置的,該方法通過字段定義對象進一步精确判斷是否可用,傳回布爾值

public static function defaultSettings()

傳回控件的預設設定,注意這是一個靜态方法,在産生控件設定資料時将合并該資料

控件鈎子:

針對字段表單中字段對象的單個值的控件修改鈎子:

   field_widget_form

   field_widget_控件插件id_form

有三個參數,順序如下:

$element:單個值控件的表單渲染數組,為了修改應該以引用接收,有如下基本鍵名:

#title、#description、#field_parents(字段所在表單中的值位置)、#required(布爾值是否必填)、#delta(值下标)、#weight;以及控件相關鍵名

$form_state:整個表單的表單狀态對象,可以在其中得到表單顯示對象以判斷實體類型和bundle

$context:包含更多資訊的上下文數組,鍵名及解釋如下:

   form:正在建構的整個表單數組

   widget:控件對象

   items:字段對象,可以從中得到字段定義和實體等

   delta:所在字段對象中的條目下标值

   default:布爾值,狀态對象中是否設定了預設值控件

針對整個字段的控件表單的修改鈎子:

   field_widget_multivalue_form

   field_widget_multivalue_控件插件id_form

在字段對象所有條目的控件渲染數組都構造完成後,會形成一個索引數組(如果是多值控件則不一定是索引數組),該數組代表整個字段對象的表單,此時會派發這兩個修改鈎子,參數按順序如下:

$elements:整個字段對象的表單渲染數組,為了修改應該以引用接收

$form_state:整個表單的表單狀态對象,可以在其中得到表單顯示對象以判斷實體類型和bundle

$context:包含更多資訊的上下文數組,鍵名及解釋如下:

   form:正在建構的整個表單數組

   widget:控件對象

   items:字段對象,可以從中得到字段定義和實體等

   default:布爾值,狀态對象中是否設定了預設值控件

經過修改鈎子修改後的字段表單會被包裝到一個容器渲染數組('#type' => 'container')中傳回,該容器指定了和字段類型、字段名、控件類型相關的類名:

      field--type-字段類型插件id

      field--name-字段名

      field--widget-控件插件id

這樣做是為了友善前端定位元素進行主題控制

補充:

1、多值字段如果定義中要求必須有值,則在控件中隻有第一個值控件被要求必填

2、多值字段的表單中,每個值控件會預設追權重重控件,該控件隻用于排序,并不被儲存

3、控件對象并不執行表單驗證,僅轉化值類型、值提取、錯誤标記、表單構造

我是雲客,【雲遊天下,做客四方】,聯系方式見首頁,歡迎轉載,但須注明出處

繼續閱讀