天天看點

雲客Drupal源碼分析之主題協商theme negotiator

drupal主題系統十分靈活,你可以全站使用一套主題,用響應式設計去相容移動端和pc端,如果響應式無法滿足要求,你可以在各端分别使用不同的主題,但其靈活性遠不止如此,實際上在同一個站點中你可以根據任意條件使用不同的主題,系統背景設定的隻是預設值而已,比如在pc端或移動端你可以為不同語言采用不同主題,甚至不同使用者采用不同模闆,條件是任意的,隻需要簡單實作一個主題協商即可,這就是本篇所講的内容。

在drupal中我們可以安裝多個主題,前端和管理界面可以各自指定一個預設主題(前端預設為bartik,管理界面為seven),但并不意味着隻能使用被指定的預設主題,在站點中可以同時使用多個主題,隻要她們被安裝了,那麼一個頁面應該使用哪個主題呢?決定一個頁面使用什麼主題是由主題協商者完成的,她是系統定義的一個服務,實際上系統中有許多個主題協商者,她們按優先級排序形成一個隊列,依次執行,一旦有協商者做出決定那麼将忽略後續協商者,子產品可以自定義主題協商者,在其中我們可以通過任意條件指定任意頁面的主題,比如前文提到的pc端和移動端分别開發主題時,我們就可以根據浏覽器的代理标志來判斷使用哪個主題。

注意:這裡需要注意主題和模闆之間的差別,可以簡單的這樣了解:主題是一套模闆的集合,從系統角度看一個主題是一個擴充,除了模闆還有很多函數、js、css等等;主題協商是在主題選擇層面工作的,在一個主題内使用哪個模闆那是模闆鈎子建議的事情(本系列其他文章介紹),一個主題内所有頁面模闆風格應該是一緻的,不同主題風格往往不同,這不是一個概念。

自定義主題協商者:

這就是在定義一個服務,先定義一個類,放置在名字空間對應的檔案中,該類必須實作以下接口:

\Drupal\Core\Theme\ThemeNegotiatorInterface

該接口有兩個方法:

applies(RouteMatchInterface $route_match):

根據情況決定是否需要使用本協商者,傳回布爾值,如果傳回fasle那麼主題協商的工作将交由随後的主題協商者來完成,系統預設會傳遞目前主請求的路由比對器對象,但該方法内部我們也可以根據其他資訊來做決定

determineActiveTheme(RouteMatchInterface $route_match):

傳回主題的機器名(注意是機器名),也可以傳回NULL表示協商失敗,這将使協商工作讓給優先級低的随後主題協商者

注意:在主題協商者中我們無需實作對主題的權限檢查,該工作将由“access_check.theme”服務去完成(見後文)。

定義好類後,在MODULE.services.yml中将她聲明為服務,見下,重點是給出“theme_negotiator”标簽和優先級屬性,在容器編譯階段系統會自動收集她并按照優先級排序。

系統提供的主題協商者:

系統預設提供了六個主題協商者,可以參考她們的服務定義和實作,以下是按照優先級從高到底排序:

ajax基本頁:

基于POST變量中的ajax_page_state變量,她是一個數組,鍵名含'theme'(主題機器名)和'theme_token'(驗證令牌),隻要令牌正确将使用'theme'指定的主題,服務定義如下:

theme.negotiator.ajax_base_page:
    class: Drupal\Core\Theme\AjaxBasePageNegotiator
    arguments: ['@csrf_token', '@config.factory', '@request_stack']
    tags:
      - { name: theme_negotiator, priority: 1000 }
           

塊管理demo頁:

隻針對“block.admin_demo”路由,采用該路由中路由參數'theme'指定的主題,服務定義如下:

theme.negotiator.block.admin_demo:
    class: Drupal\block\Theme\AdminDemoNegotiator
    tags:
      - { name: theme_negotiator, priority: 1000 }
           

系統批處理頁面:

隻針對“system.batch_page”路由,服務定義如下:

theme.negotiator.system.batch:
    class: Drupal\system\Theme\BatchNegotiator
    arguments: ['@batch.storage', '@request_stack']
    tags:
      - { name: theme_negotiator, priority: 1000 }
           

系統資料庫更新頁:

隻針對'system.db_update'路由,用于資料庫維護更新時使用,可以在站點配置檔案中使用配置項“maintenance_theme”指定一個主題,如沒有設定該配置項将采用'seven',如果設定為false将使用預設前端主題,服務定義如下:

theme.negotiator.system.db_update:
    class: Drupal\system\Theme\DbUpdateNegotiator
    arguments: ['@config.factory']
    tags:
      - { name: theme_negotiator, priority: 100 }
           

管理(後端)頁主題:

使用者有'view the administration theme'權限,且通路的是管理頁(路由中有'_admin_route'選項),那麼将使用管理主題,服務定義如下:

theme.negotiator.admin_theme:
    class: Drupal\user\Theme\AdminNegotiator
    arguments: ['@current_user', '@config.factory', '@entity.manager', '@router.admin_context']
    tags:
      - { name: theme_negotiator, priority: -40 }
           

預設主題:

這是優先級最低的協商者,如果沒有其他協商者進行有效協商時總是被使用,她總是傳回前端預設主題,服務定義如下:

theme.negotiator.default:
    class: Drupal\Core\Theme\DefaultNegotiator
    arguments: ['@config.factory']
    tags:
      - { name: theme_negotiator, priority: -100 }
           

通過以上系統提供的六個預設協商者,可以看到最高的優先級是1000,預設協商者優先級最低,為-100,我們自定義的協商者優先級需要參考她們以進行恰當的排序,當高優先級作出有效協商後,低優先級将被忽略,最低不能低于-100,否則将使用預設主題。

比如前文提到的選擇pc端和移動端主題的協商者就可以定義在-40到-100之間,這樣管理界面不受影響,前端将在各端使用不同主題。

代理協商者:

在系統中對所有協商者的應用是在一個代理中完成的,她的服定義如下:

服務id:theme.negotiator

類:\Drupal\Core\Theme\ThemeNegotiator

在其中可以看到優先級的處理邏輯,她執行了主題通路檢查。

主題通路檢查:

服務id:access_check.theme

類:Drupal\Core\Theme\ThemeAccessCheck

預設檢查僅僅是基于主題是否被安裝,如果還沒有被安裝那麼不允許使用。如有需要,我們可以在子產品中定義自己的服務去覆寫該通路檢查器以實作進階特殊的檢查。

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

繼續閱讀