雲客Drupal源碼分析之實體視圖顯示及格式化器

在實體視圖構建器中構建完實體的基本渲染數組後,會調用實體視圖顯示對象繼續構建實體字段對象的渲染數組,然後合併到基本數組中(合併過程基本數組的優先級更高)以形成完整實體渲染數組,實體視圖顯示對象內部又依據配置調用字段格式化器來構建每一個字段對象的渲染數組。
實體顯示對象在視圖構建器中的調用入口如下:
構建多個實體的字段渲染數組:
  $display->buildMultiple($entities);
該方法用同一視圖模式渲染多個有相同實體類型、bundle的實體,保持傳入鍵名不變
構建單個實體的字段渲染數組:
  $display->build($entity);
傳入的實體必須屬於顯示對象對應的實體類型和budle
字段格式化器在視圖顯示對象中的調用入口如下:
讓格式化器執行準備工作:
  $formatter->prepareView($grouped_items);
返回字段完整渲染數組:
  $build_list[$id][$name]=$formatter->view($items, $view_langcode);

實體視圖顯示EntityViewDisplay:
和實體表單顯示一樣,“實體視圖顯示EntityViewDisplay”同樣有兩個身份,既是一個配置實體,實體類型id:entity_view_display,用來儲存實體類型的bundle在某個視圖模式下的顯示配置數據,如字段採用何種格式化器、顯示順序、是否顯示等等,同時也是一個實體視圖構建對象,負責在內容實體視圖構建流程中對具備顯示選項的字段進行視圖渲染數組構建,類定義如下:
  \Drupal\Core\Entity\Entity\EntityViewDisplay
該類實例化的對象既是配置實體對象又是視圖構建對象,下文簡稱爲視圖顯示對象或顯示對象
作爲一個配置實體,加載儲存的所有視圖顯示配置代碼如下:
  \Drupal::entityTypeManager()->getStorage('entity_view_display')->loadMultiple();
這些配置實體的id格式如下:
  實體類型id.bundle.模式名
其中模式名不帶實體類型前綴,如:user.user.default、node.article.full、node.article.rss
作爲一個視圖構建對象,她實現了以下接口:
  \Drupal\Core\Entity\Display\EntityViewDisplayInterface
在控制器中運行以下代碼可查看視圖顯示配置數據:

  $entities = \Drupal::entityTypeManager()->getStorage('entity_view_display')->loadMultiple();
  $data = [];
  foreach ($entities as $id => $entity) {
        $data[$id] = $entity->toArray();
  }
  print_r(array_keys($data));
  print_r($data);
  die;

這些數據除了配置實體的通用數據外,最重要的有以下鍵:
 id:實例化的視圖顯示對象id,和加載實體的實體id相同
 targetEntityType:本視圖顯示對象代表的實體類型id
 bundle:本視圖顯示對象代表的實體類型的某個bundle
 mode:視圖顯示模式名
 content:子健爲字段名,包括僞字段,在視圖顯示對象中稱爲組件Component,其值爲字段顯示配置
 hidden:子健爲被禁用的字段名,包括僞字段,他們不會被顯示,其值爲true
其中字段顯示配置有以下鍵:
 type:該字段要使用的格式化器類型id,如省略、配置錯誤或不適用將使用字段類型定義中指定的默認格式化器,僞字段沒有該項
 label:label所在的位置,inline、above、hidden之一,默認above
 weight:字段顯示排序的權重
 region:顯示字段所在的區域,值爲content表示正常顯示,值爲hidden表示隱藏
 settings:用於格式化器本身的設置,來自管理員設置、字段定義的顯示選項和格式化器默認值,優先級依次遞減,可由格式化器提供的配置表單設置
 third_party_settings:通過hook_field_formatter_third_party_settings_form()鉤子收集的第三方模塊的設置,以模塊名做第一級鍵名,傳遞給格式化器使用,詳見本系列表單顯示主題
在實體視圖構建器中通過以下方法實例化視圖顯示對象:
      public static function collectRenderDisplays($entities, $view_mode)

實體視圖顯示對象方法說明:
該類繼承自實體顯示基類,基類中的方法在本系列實體表單顯示中已經介紹,這裏僅介紹本類的方法
public static function collectRenderDisplays($entities, $view_mode)
獲取多個視圖顯示對象,參數$entities是一個要被顯示的實體對象構成的數組,鍵名是在顯示列表中的序號,鍵值是實體對象,這些實體對象必須屬於同一個實體類型,且使用相同視圖模式,參數$view_mode是這些實體對象正在被顯示的視圖模式,返回值是一個數組,以bundle作爲鍵名,鍵值是視圖顯示對象,該方法優先使用傳入的視圖模式的顯示對象,如果不存在將以視圖模式名爲“default”的顯示對象代替,如果仍然不存在,則將以傳入的視圖模式新建一個(不會執行保存),在經過這個過程得到顯示對象後會派發以下修改鉤子以便模塊參與修改:
   entity_view_display
鉤子函數如下:
  hook_entity_view_display_alter(EntityViewDisplayInterface $display, array $context)
傳遞的第一個參數是以上過程得到的視圖顯示對象,她即將被使用,如需整個替換則應以引用接收,第二個參數是一個上下文數組,鍵名及其值如下:
  entity_type:值爲字符串值的實體類型id
  bundle:值爲字符串值的bundle ID
  view_mode:值爲原始請求的視圖模式,也被儲存在視圖顯示對象的originalMode屬性下,這並不一定就是當前視圖顯示對象的顯示模式,換句話說$viewDisplay->getMode()可能和該值不相等,也可以使用$viewDisplay->getOriginalMode()獲得該值;默認安裝中僅節點實現了該修改鉤子(對節點實體的搜索索引視圖進行特殊處理)。

public function postSave(EntityStorageInterface $storage, $update = TRUE)
當顯示對象有保存動作時,視爲發生更新,失效該實體類型的視圖輸出,該方法有改進的餘地,最佳做法應該是失效和該顯示對象代表的視圖模式及bundle相關的視圖輸出,而不是魯莽的失效全部

public function getRenderer($field_name)
實例化字段格式化器,參數爲字段名,實例化工作由字段格式化器的插件管理器getInstance方法負責,注意這裏傳遞的配置信息是已經合併了默認值的最終配置信息,詳見下文

public function buildMultiple(array $entities)
構建多個實體對象的字段視圖渲染數組,該方法用同一視圖模式渲染多個有相同實體類型、bundle的實體,返回值以傳入鍵名做鍵名並保持對應關係不變;在顯示配置中隱藏的字段(沒有組件數據)不被渲染構造,沒有格式化器的字段也不被渲染構造(僞字段往往通過鉤子構造),prepareView方法調用見下
改造完成後會派發以下修改鉤子:
    entity_display_build
函數原型:
    hook_entity_display_build_alter(&$build, $context)
參數$build爲一個數組,鍵名爲實體的字段名,鍵值爲其視圖渲染數組
參數$context爲上下文數組,有如下鍵值:
   entity:實體對象
   view_mode:原始請求的實體模式,見$display->originalMode
   display:視圖顯示對象


實體視圖顯示相關表單:
配置編輯表單路由_entity_form爲entity_view_display.edit
類:\Drupal\field_ui\Form\EntityViewDisplayEditForm
繼承自:\Drupal\field_ui\Form\EntityDisplayFormBase
詳見本系列實體表單顯示

字段格式化器插件管理器:
字段格式化器以插件方式提供,後簡稱格式化器,其插件管理器定義如下
服務id :plugin.manager.field.formatter
類:Drupal\Core\Field\FormatterPluginManager
獲取方式:\Drupal::service('plugin.manager.field.formatter')
插件定義緩存位置:緩存表cache_discovery 的field_formatter_types_plugins條目
插件儲存目錄:Plugin/Field/FieldFormatter
字段格式化器插件定義修改鉤子名:field_formatter_info
釋文類:Drupal\Core\Field\Annotation\FieldFormatter
格式化器接口:Drupal\Core\Field\FormatterInterface
格式化器插件管理器方法說明:
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(‘view’);
合併顯示選項中的子健settings與格式化器的默認設置選項,字段定義優先,其中無效多餘的settings將去除

public function getOptions($field_type = NULL)
返回字段類型可用的格式化器,參數爲字段類型插件id,返回值是一個數組,鍵名爲字段類型可用的格式化器插件id,鍵值爲格式化器label,如果沒有傳遞字段類型參數,將返回所有字段類型的該數組,此時第一級鍵名爲字段類型插件id

public function getDefaultSettings($type)
參數爲格式化器插件id,獲取某個字段格式化器的默認配置選項,也就是調用格式化器的靜態方法:defaultSettings()

格式化器插件的實例化:
在渲染內容實體視圖時,有顯示選項且允許顯示的字段,其視圖渲染數組的產生在實體視圖顯示對象中進行,在該對象內部通過字段的視圖顯示配置確定一個格式化器類型,並通過格式化器插件管理器實例化格式化器對象,最終由格式化器對象來完成相應工作,詳見以下方法:
  \Drupal\Core\Entity\Entity\EntityViewDisplay::getRenderer
該方法實例化過程相當於在執行以下代碼:
  \Drupal::service('plugin.manager.field.formatter') ->getInstance($options);
參數$options是一個數組,有如下鍵名:
field_definition:字段定義對象
form_mode:原始請求的視圖模式originalMode
prepare:布爾值,是否需要合併格式化器默認值,默認爲FALSE,因爲視圖顯示對象中儲存的配置已經合併了格式化器默認配置
configuration:字段對應的視圖顯示配置,儲存在視圖顯示配置的字段鍵名下,這裏假設視圖顯示對象爲$display,字段名爲$field_name(字符串值),可通過以下代碼查看:
   $display->getComponent($field_name)

實例化爲什麼沒有直接使用createInstance方法呢?那是因爲如果配置錯誤(如格式化器不支持字段類型、不適用等),將回退使用字段的默認格式化器,最終還是會統一調用插件管理器的創建實例方法:
   \Drupal::service('plugin.manager.field.formatter')->createInstance($plugin_id, $configuration)
其中$configuration來自前文提到的字段視圖顯示配置,是一個數組,有以下鍵:
type:要使用的格式化器類型id,如省略、配置錯誤或不適用將使用字段類型定義中指定的默認格式化器,因此其值未必等於插件id
weight:字段顯示排序的權重
region:顯示字段所在的區域,值爲content表示正常顯示,值爲hidden表示隱藏
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的相應鍵名


字段格式化器插件定義:
和drupal其他插件定義一樣,詳見本系列插件篇,這裏對字段格式化器的釋文說明如下:
id:格式化器ID
label:格式化器人類可讀標籤
description:格式化器人類可讀描述
field_types:格式化器支持的字段類型,數組值,元素值爲字段類型id,如不支持,則無法被其使用
weight:格式化器默認權重,用於與其他控件在一起時的排序,可選,默認爲0

格式化器接口:
  Drupal\Core\Field\FormatterInterface
格式化器對象在顯示對象中的執行入口爲:
讓格式化器執行準備工作:
  $formatter->prepareView($grouped_items);
該方法的介紹見下文
返回字段完整渲染數組:
  $build_list[$id][$name]=$formatter->view($items, $view_langcode);
該方法中的參數$items爲內容實體的字段對象,來自:$items = $entity->get($name);

字段格式化器默認基類:
系統提供了以下字段格式化器的默認實現:
  \Drupal\Core\Field\FormatterBase
她實現了格式化器接口,格式化器插件類通常需要繼承該基類,這裏結合接口對方法說明如下:
public function prepareView(array $entities_items);
該方法將在view方法前調用,用於格式化器在開始構造渲染數組前做一些準備工作,大部分格式化器用不到該方法,但有些情況下是必要的,因此接口設計了該方法,並在視圖顯示對象中給予了先行調用,目前在系統中默認情況下僅引用字段類型用到了她:爲提高性能在該方法中一次性加載需要渲染的所有被引用實體對象,詳見:
  \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase::prepareView
如果沒有該方法那麼每次view方法調用都要進行一次數據庫查詢,性能是非常糟糕的,相反就可以一次性知道有哪些實體需要渲染,就能做到一次查詢多個,這就大大提高了性能,可見該方法是很有必要的
參數$entities_items是一個數組,鍵名爲渲染序號,鍵值爲字段對象,詳見顯示對象的buildMultiple方法

public function view(FieldItemListInterface $items, $langcode = NULL)
返回單個完整的字段對象渲染數組,參數$items爲內容實體的字段對象,來自:$entity->get($name);,參數$langcode指明需要的語言id,如果沒有傳遞將使用內容協商產生的語言,字段對象的渲染數組構造分兩步完成,第一步構造字段對象的所有條目對象的渲染數組(由viewElements方法完成),第二步附加共用數據,有如下共用數據:


      $info = [
        '#theme' => 'field',
        '#title' => $this->fieldDefinition->getLabel(),
        '#label_display' => $this->label,
        '#view_mode' => $this->viewMode,
        '#language' => $items->getLangcode(),
        '#field_name' => $field_name,
        '#field_type' => $this->fieldDefinition->getType(),
        '#field_translatable' => $this->fieldDefinition->isTranslatable(),
        '#entity_type' => $entity_type,
        '#bundle' => $entity->bundle(),
        '#object' => $entity,
        '#items' => $items,
        '#formatter' => $this->getPluginId(),
        '#is_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(),
      ];

在附加時採用array_merge($info, $elements);合併,也就是說共用數據優先級低,可被viewElements方法返回的內容覆寫

public function viewElements(FieldItemListInterface $items, $langcode)
構造字段對象中全部條目對象的渲染數組,通常內部以foreach遍歷$items的每個條目,構造後形成一個索引數組返回

public function settingsForm(array $form, FormStateInterface $form_state);
返回格式化器自身的配置表單,在管理顯示頁面使用,以供管理員配置格式化器,見:
   \Drupal\field_ui\Form\EntityDisplayFormBase
系統會將提交的值存放到視圖顯示配置中字段組件的settings鍵下,該數據也是格式化器的構造參數之一,注意該表單不要設置#parents、#tree屬性以免影響值提取,更多詳細可參見本系列實體表單顯示主題

public function settingsSummary();
用於在管理顯示頁面的字段行中顯示格式化器當前的設置信息,返回一個字符串數組,每個元素代表一條當前格式化器配置的摘要信息,模塊可以通過field_formatter_settings_summary修改鉤去修改返回值

public static function isApplicable(FieldDefinitionInterface $field_definition);
返回格式化器能否用於被提供的字段,雖然在格式化器插件釋文中已經通過field_types表明了她支持的字段類型,但由於字段是可配置的,該方法通過字段定義對象進一步精確判斷是否可用,返回布爾值

public static function defaultSettings()
返回格式化器的默認設置,注意這是一個靜態方法,在產生格式化器設置數據時將合併該數據

默認標準安裝中存在以下格式化器:
鍵名爲插件id,鍵值爲label

Array
(
    [comment_username] => 作者名
    [comment_default] => Comment list
    [comment_permalink] => Comment Permalink
    [datetime_custom] => 自定義
    [datetime_default] => 默認
    [datetime_plain] => 純文本
    [datetime_time_ago] => 以前
    [file_link] => File link
    [file_audio] => Audio
    [file_extension] => 文件擴展名
    [file_filemime] => File MIME
    [file_size] => 文件大小
    [file_uri] => File URI
    [file_video] => Video
    [file_default] => 通用文件
    [file_rss_enclosure] => RSS enclosure
    [file_table] => 文件表格
    [file_url_plain] => 文件鏈接
    [image] => 圖像
    [image_url] => URL to image
    [link] => 鏈接
    [link_separate] => Separate link text and URL
    [list_default] => 默認
    [list_key] => 鍵
    [entity_reference_rss_category] => RSS 分類
    [text_default] => 默認
    [text_summary_or_trimmed] => 總結摘要
    [text_trimmed] => 切邊的
    [author] => 作者
    [user_name] => 用戶名
    [basic_string] => 純文本
    [boolean] => 布爾值
    [number_decimal] => 默認
    [entity_reference_entity_view] => 輸出的實體
    [entity_reference_entity_id] => 實體ID
    [entity_reference_label] => 標籤
    [number_integer] => 默認
    [language] => 語言
    [email_mailto] => 電子郵件
    [number_unformatted] => 未格式化的
    [string] => 純文本
    [timestamp_ago] => 以前
    [timestamp] => 默認
    [uri_link] => 到URI的鏈接
)


補充:
1、在表單控件基類中,派發了一些鉤子,如:
   field_widget_form
   field_widget_multivalue_form
這些鉤子可以對字段條目進行表單渲染數組的修改,相比之下格式化器基類相對簡單,並沒有派發什麼鉤子,但模塊依然可以通過顯示對象中派發的鉤子進行修改,如果自定義格式化器,可在子類中自行派發鉤子

 

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

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章