天天看點

雲客Drupal源碼分析之内容實體資料庫表結構及表映射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

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