1. 程式人生 > >雲客Drupal8原始碼分析之內容實體資料庫表結構及表對映table mapping

雲客Drupal8原始碼分析之內容實體資料庫表結構及表對映table mapping

“欄位”概念
在drupal中提到“欄位”這個概念時,請不要理解為資料庫中表的一個列,這不是一個概念,它是指一個欄位物件,充當著實體物件的屬性,也是一個列表型別的型別化資料物件,當本系列提到“欄位field”一般均是指欄位物件或欄位定義物件,而資料庫表中的欄位列,則將其稱為“列column”;一個欄位物件可以包含資料庫表中的多個列(取決於欄位型別定義中的schema),這些列稱為欄位物件的屬性,這樣的欄位叫做多屬性欄位;由於欄位物件是列表型別的資料因此可包含資料庫表中的多行資料,具體可以有多少行由欄位定義物件中的基數引數決定,每行對應列表中的一個元素,當基數大於一或無限時,這樣的欄位稱為多值欄位(注意和多屬性欄位的區別)。關於欄位物件的儲存,可以多個欄位儲存在一張表中,也可以一個欄位獨立儲存在一張屬於她的專用表中,在一些特殊情況下一個欄位物件還可以被儲存在幾張資料表中。

儲存結構概述:

在一些cms系統中,是先設計資料庫表結構,然後再圍繞這個結構去開發程式,這很直觀,但不夠靈活,也不夠強大,當需要額外的表時系統不能代為建立和感知,而drupal則不同,在drupal中內容管理主要是基於實體,實體又基於欄位,系統是先找出實體所有的欄位物件,再根據她們的儲存定義自動計算出需要多少資料庫表及每個欄位應該放置在哪張表中,欄位物件和她在資料庫中的儲存資料表的對應資訊由表對映物件託管,而怎麼決定這種對映關係,可以參看sql內容實體儲存處理器中的getTableMapping方法(見下文),她是系統關於欄位和儲存表的預設對映規則。

表對映物件:
類:Drupal\Core\Entity\Sql\DefaultTableMapping
介面:\Drupal\Core\Entity\Sql\TableMappingInterface
該物件儲存著實體欄位物件到資料庫表之間的對映資訊,該類例項化後是一個沒有資料的空物件,必須藉助sql內容實體儲存處理器為她注入資料,本質上該物件只是一個儲存者,決定這種對映關係的邏輯程式碼實際位於sql內容實體儲存處理器的getTableMapping方法中,我們明白了這個方法也就明白了內容實體的表結構。

預設內容實體資料庫表結構:

為什麼是預設呢?這是因為欄位物件我們可以為其定義自定義儲存,在預設情況下也是大多數情況下是用系統提供的儲存機制,這裡我們僅關注預設情況,要知道資料庫表結構,需要先明白欄位物件和資料庫表的對應邏輯,核心程式碼位於:
Drupal\Core\Entity\Sql\SqlContentEntityStorage::getTableMapping(array $storage_definitions = NULL);
下文我們將詳細分析該邏輯,這種對應邏輯適用於所有的內容實體,也就是說所有的內容實體型別的儲存資料表遵循共有的規則(如表如何命名、哪些欄位放哪個表、有哪些額外附加的列等),在系統中最主要的內容實體型別是節點實體,我們以它作為列子,然後你可以推及到其他內容實體,如使用者user實體等。

要建立資料庫表,需要知道內容實體的所有欄位資訊,這從實體欄位管理器中獲取(見本系列該主題),建表後再將這些欄位分門別類的放到這些表中,一個內容實體型別的所有欄位,系統使用五種型別的資料庫表來儲存,她們是:

基本表:
每個內容實體都必須存在的表,表名在實體類釋文鍵“base_table”中指定,無指定則為實體型別id,比如節點實體型別表名為“node”
版本表:
僅在內容實體型別是可版本化時才存在,表名在實體釋文鍵“revision_table”中指定,無指定則為實體型別id+'_revision',比如節點實體型別為“node_revision”
資料表:
僅在內容實體型別是可翻譯的時才存在,表名在實體釋文鍵“data_table”中指定,無指定則為實體型別id+'_field_data',比如節點實體型別為“node_field_data”
版本資料表:
僅在內容實體型別是可版本化且是可翻譯的時才存在,該表存在時資料表必然存在,表名在實體釋文鍵“revision_data_table”中指定,無指定則為實體型別id+'_field_revision',比如節點實體型別為“node_field_revision”
欄位專用表:
當欄位需要獨立儲存時為其建立的專用資料表,表名為:實體型別id+分隔符+欄位名,欄位專用表的分隔符為雙下劃線“__”,如果欄位是可版本化的,那麼還有欄位版本專用表,命名規則一樣,僅分隔符為“_revision__”,如果表名大於48個字元,那麼做雜湊處理,具體見上文表對映物件的generateFieldTableName方法,在系統管理後臺:結構/內容型別管理中新增的欄位,就是欄位專用表來儲存的,新增的欄位名稱被強制加上了“field_”字首

注意以上提到的表名並不包括在資料庫連線資訊中指定的表字首,如有指定需要加上才是資料庫中真實的表名。

那麼在一個內容實體中哪些欄位存放在哪張表中呢?我們來詳細分析上文提到的得到表對映方法:
Drupal\Core\Entity\Sql\SqlContentEntityStorage::getTableMapping(array $storage_definitions = NULL);
預設情況是從實體欄位管理器(見本系列實體欄位管理器主題)中獲取一個內容實體型別所有的欄位儲存定義,然後首先找出可以和其他欄位共同儲存在一張資料表中的那些欄位,她們被稱為可共享儲存欄位,這些可共享儲存表的欄位必須同時滿足三個規則:
1、非自定義儲存,也就是使用系統預設儲存
2、是基本欄位,不能為bundle欄位
3、不可多值儲存,也就是一個欄位物件只需要一行資料,不需要多行
該規則定義在以下方法中:
Drupal\Core\Entity\Sql\DefaultTableMapping::allowsSharedTableStorage
然後初始化以下變數並將可共享儲存欄位分類:
$key_fields:
關鍵欄位,唯一標識一條資訊的必要欄位,是所有內容實體共有的欄位,她們為:實體id、版本id、bundle、uuid、語言id,他們的欄位名和存在性由實體型別決定
$all_fields:
全部可共享儲存欄位,包含關鍵欄位且關鍵欄位排序靠前
$revisionable_fields:
可共享儲存欄位中可版本化的欄位
$revision_metadata_fields:
可版本化元資料欄位(主要用於新增版本標註),欄位名定義在實體釋文的revision_metadata_keys鍵中

然後根據內容實體型別是否可版本化和可翻譯性分四種情況,將可共享儲存欄位放入四種非專用表中,如下:
1、 不可版本化且不可翻譯
所有可共享儲存欄位$all_fields全部放在基本表中,此時不會有版本id和語言id欄位,也沒有其他共享表
2、 可版本化但不可翻譯
只建立基本表和版本表,全部可共享儲存欄位$all_fields排除版本元欄位$revision_metadata_fields後放入基本表中,在版本表中放入可共享儲存欄位$all_fields中的可版本化欄位,並加入版本關鍵欄位:實體id和版本id
3、 不可版本化但可翻譯
只建立基本表和資料表,基本表中只儲存關鍵欄位$key_fields,全部可共享儲存欄位$all_fields放入資料表中(但uuid欄位除外)。user實體型別即屬於此情況,可檢視資料庫表:“users”為基本表,“users_field_data”為資料表
4、 可版本化且可翻譯
四種非專用表全部建立,節點實體即屬於該種情況,基本表只儲存關鍵欄位$key_fields;可共享儲存欄位$all_fields排除uuid及版本化元欄位$revision_metadata_fields後放入資料表中;實體id、版本id、語言id和版本化元欄位一起放入版本表中;從可版本化欄位$revisionable_fields中排除版本元欄位及語言程式碼欄位後和實體id、版本id、語言程式碼欄位一起放入版本資料表中,這裡排除語言程式碼欄位又合併它是為了排序。
通過以上邏輯就將可共享儲存表的所有欄位劃分到了四種共享儲存資料表中,然後處理需要獨立儲存的欄位,判斷欄位是否需要專用儲存表的規則如下(同時滿足):
1、 非自定義儲存,也就是使用系統預設儲存的欄位
2、 可共享儲存欄位以外的全部欄位
該規則定義在如下方法中:
Drupal\Core\Entity\Sql\DefaultTableMapping::requiresDedicatedTableStorage
欄位儲存專用表只有資料表和版本表兩種,如果欄位不是可版本化的則沒有版本表,她們的表名按前文所述規則計算得出。

所有欄位專用資料表都被附加了以下列:
['bundle','deleted','entity_id','revision_id','langcode','delta']
注意是“列”而不是欄位,所有欄位會被轉化成為列然後和附加的列一起構成資料表的“列”。

欄位物件轉化為列的規則:
先需要明白欄位的屬性,她對應於欄位儲存定義物件的getColumns方法返回陣列的鍵名,預設情況下在欄位型別類的schema方法中定義。
在共享表中如果欄位僅有一個屬性,那麼列名為欄位名,注意此時不會理會欄位的屬性名,這將影響傳遞給實體類建構函式引數$values的結構,通常只有一個屬性的欄位屬性名設定為“value”,如果多於一個屬性名則列名為:$field_name . '__' . $property_name,注意是雙下劃線,在讀取時系統根據列名是否有雙下劃線去判斷該列是否為一個欄位的屬性。
在欄位專用表中,如果欄位屬性為“%delta”那麼列名為“delta”,如果屬性名在系統保留列名(見補充)中,那麼列名為屬性名,其他情況則列名為$field_name . '_' . $property_name,這和共享表不一樣,在欄位只有一個屬性的情況下並不以欄位名命名,且注意是單下劃線
以上欄位轉換成列的規則定義在以下方法中:
Drupal\Core\Entity\Sql\DefaultTableMapping::getColumnNames($field_name)


補充資料:
1、保留列名:用於系統功能,目前僅有“deleted”(表示實體是否已被刪除),在欄位型別設計中不能使用保留列名做欄位列(欄位屬性)。
2、在後臺錄入資訊時,預設情況下,錄入資訊的語言並不是根據當前介面的語言來自動識別的,而是語言選擇器中輸入,但該邏輯可以在管理內容型別的結構時在語言選項中指定,可以指定預設為介面語言


節點實體資料字典:
通過以上學習您應該明白了內容實體在資料庫中的表結構,以下列出系統中最重要的節點內容實體的表結構,及其表中的列含義,俗稱cms開發的“資料字典”(版本:drupal 8.4.0):

節點基本表(表名“node”),欄位含義如下:
nid:節點id,或叫做節點內容實體的實體id
vid:版本id,在該表中儲存節點當前版本(也是最新版本,版本的回退是在回退版本中複製資料形成新版本)
type:節點bundle名,也就是內容型別機器名
uuid:全域性通用識別id,見本系列UUID主題,用於跨系統唯一標識一個資訊物件
langcode :該節點該版本的源語言程式碼,未指定則為“und”,不適用則為“zxx”見本系列語言主題

節點版本表(表名“node_revision”),欄位含義如下:
nid:同上
vid:該節點的各版本id
langcode:同上,該節點該版本的源語言程式碼
revision_timestamp:版本建立時的unix時間戳,非修改時間
revision_uid:進行版本建立操作的使用者id
revision_log:版本註釋(版本標識日誌)

節點資料表(表名“node_field_data”),欄位含義如下:
該表儲存節點實體最新版本的所有翻譯資訊
nid:同上
vid:節點最新版本id
type:同上
langcode:該節點最新版本的各語言程式碼
status:布對外爾值,發表狀態,1為已發表,0位未發表
title:節點標題,也是節點實體的label
uid:建立第一個版本時的使用者id
created:該節點第一個版本建立時的時間戳,而不是當前版本的
changed:該節點最後一個版本的修改時間
promote:布林值,是否推薦到首頁,1為推薦
sticky:布林值,是否置頂,1為置頂
revision_translation_affected:當該節點有多個翻譯時,新建版本後,本語言是否已經進行了翻譯更新,1為是
default_langcode:該節點的該版本的該語言,是否為源語言(一個版本的源語言只有一個,可修改)
content_translation_source:本翻譯來自的源語言,如果本翻譯就是源語言,那麼值為“und”
content_translation_outdated:在內容翻譯模組啟用時才存在,布林值,表示翻譯是否已經過期,1為過期

節點版本資料表(表名“node_field_revision”),欄位含義如下:
該表儲存節點實體所有版本的所有翻譯資訊
nid:同上
vid:同上
langcode:該節點該版本的語言程式碼
status:同上
title:同上
uid:同上,注意在此表中,該欄位表示第一個版本的建立使用者,而不是指當前版本的建立使用者
created:同上,注意是該節點第一個版本建立時的時間戳,而不是當前版本的建立時間
changed:同上
promote:同上
sticky:同上
revision_translation_affected:同上
default_langcode:同上
content_translation_source:同上
content_translation_outdated:同上

節點專用表列舉,欄位名“node__body”,欄位含義如下(其他專用表類似):
bundle:通用附加列,儲存bundle,也就是內容型別
deleted:通用附加列,表示內容是否已經被刪除
entity_id:通用附加列,實體id
revision_id:通用附加列,實體版本id
langcode:通用附加列,語言程式碼
delta:通用附加列,當欄位包含多值時,本條資訊的陣列下標,起到排序作用
body_value:欄位body對應的value屬性
body_summary:欄位body對應的summary屬性,表示摘要資訊
body_format :欄位body對應的format屬性,表示欄位允許的標籤型別:受限制、基本的、完整的HTML

我是雲客,【雲遊天下,做客四方】,微訊號:PHP-world,歡迎轉載,但須註明出處,討論請加qq群203286137