雲客Drupal源碼分析之導航菜單Navigation menus

菜單系統概述:
drupal8的菜單系統主要包含四大部分:導航菜單Navigation menus、本地任務Local tasks、本地動作Local actions、上下文鏈接Contextual links,都非常有用,本篇講解第一部分:導航菜單,後簡稱菜單

菜單概述:
在講述菜單前,需要先明確一些概念,菜單menu用於在網站中導航,是由菜單鏈接menu link組成的層次結構,也就是說一個菜單會包含0個或多個鏈接,是一個鏈接集,這些鏈接具備樹型層次結構(父、子、兄弟),這個層次結構稱爲菜單樹,菜單中的條目一定是鏈接,但反過來鏈接不一定是菜單,有時候會混淆“菜單”和 “菜單鏈接”,這需要根據語境上下文來判斷,嚴格來講“父菜單”這個說法是錯誤的,正確的說法是“父菜單鏈接”,因爲在層次結構中討論位置關係是指菜單內的菜單鏈接,而不是指菜單本身,沒有父菜單一說,但生活中我們經常聽到這樣的講法,約定俗成同時爲了敘述簡潔,本文並不排斥這種講法,因此當遇到詞語:父菜單、子菜單、兄弟菜單時,務必明白是指“父、子、兄弟菜單鏈接”,單獨使用“菜單”一詞時通常不是指一個菜單鏈接。

系統中可以有多個菜單以便用於不同地方的導航,因此每一個菜單都需要一個獨一無二的名字,稱爲菜單名,系統默認提供的菜單及菜單名有:
   管理菜單(admin):用於系統後臺管理操作
   主導航菜單(main):前臺頁面的主要導航菜單
   用戶賬戶菜單(account):用於登錄後管理自己的賬戶
   頁腳菜單(footer):在前臺頁面顯示一些導航鏈接

菜單不但需要菜單名,還需要描述、語言信息等,因此係統提供了一個配置實體來保存菜單信息,這就是菜單實體,有了菜單實體後,就可以向其添加菜單鏈接,實際上添加的菜單鏈接是在菜單實體之外單獨保存,通過菜單名和菜單實體關聯,添加菜單鏈接有三種方式:
靜態定義:模塊通過.links.menu.yml文件定義,是普通菜單鏈接
後臺自定義:通過管理後臺界面定義菜單鏈接,這樣的菜單鏈接由實體支持,是實體菜單鏈接
程序方式定義:以上兩種菜單鏈接都可以通過程序添加

菜單系統的實現涉及多個地方:
核心:提供基本功能(\core\lib\Drupal\Core\Menu)
系統system模塊:提供菜單實體等(\Drupal\system\Entity\Menu)
菜單鏈接內容模塊:提供菜單鏈接內容實體(\Drupal\menu_link_content\Entity\MenuLinkContent)等
菜單UI模塊:提供系統後臺用戶操作接口(\core\modules\menu_ui)

在理解了菜單系統的整體結構後我們來看一下系統的具體實現。

菜單顯示示例:
通常我們在區塊配置中採用一個塊來顯示菜單,大多數時候應該這樣使用,這裏也給出程序方式顯示一個菜單的示例:

        //取得菜單鏈接樹對象,這是使用入口
        $menu_tree = \Drupal::service('menu.link_tree');
        //準備菜單樹參數
        $parameters = new \Drupal\Core\Menu\MenuTreeParameters();
        $parameters->setMinDepth(2)->setMaxDepth(5)->onlyEnabledLinks();
        //按菜單名加載菜單樹 這裏我們加載管理菜單
        $tree = $menu_tree->load('admin', $parameters);
        //準備菜單樹操縱器,操縱器用於修改菜單樹
        $manipulators = [
            ['callable' => 'menu.default_tree_manipulators:checkAccess'], //權限檢查
            ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'], //菜單排序
        ];
        //運用操縱器
        $tree = $menu_tree->transform($tree, $manipulators);
        //返回顯示菜單樹的渲染數組
        $element['menu'] = $menu_tree->build($tree);
        return $element;

請在控制器中運行以上代碼,接下來講解其原理和各組件

菜單鏈接插件管理器:
用於收集yml文件定義的菜單鏈接,添加、加載、移除菜單鏈接等,系統將yml文件定義的菜單鏈接視爲插件,因此這是一個插件管理器,定義如下:
服務id:plugin.manager.menu.link
類:Drupal\Core\Menu\MenuLinkManager
使用Yaml發現機制(詳見本系列插件下集),查找模塊根目錄下的“模塊名.links.menu.yml”文件,以該文件內的根鍵做插件id(也是菜單鏈接id),對應數組做插件定義,這稱爲菜單鏈接的靜態定義,該定義數組有如下鍵:
menu_name:菜單名,默認值“tools”,可選,如沒有提供則子菜單自動繼承父菜單的值(如果指定了但和父菜單不同,將以父菜單值爲準),如果是頂級菜單鏈接往往需要指定,這樣才能將菜單鏈接樹和菜單實體相關聯,如頂級菜單鏈接沒有指定,將使用默認值,菜單名在菜單UI模塊中被限制爲只能使用小寫字母、數字、連字符
route_name:路由名,字符串值,默認爲空字符串,和url二選一必填
route_parameters:路由參數,數組值,默認爲空數組 [],可選
url:外部url,默認爲空字符串,和路由名二選一必填
title:菜單標題,字符串值,被系統理解爲可翻譯的,在系統內部被轉化爲翻譯對象TranslatableMarkup,用於html鏈接標籤的文本,並非title屬性值
title_context:可選,標題翻譯上下文,見翻譯系統
description:菜單描述,同標題,可用於html鏈接標籤的title屬性
description_context:可選,描述翻譯上下文,見翻譯系統
parent:父菜單鏈接id,字符串值,無定義時表示頂級菜單(此時也可定義爲NULL或空字符串),如果指定一個不存在的父菜單鏈接將被替換爲空字符串值,相當於頂級菜單
weight:菜單權重,在同級菜單中越小越靠前,默認爲0,可選
options:鏈接選項,用於URL對象,默認爲空數組,可選
expanded:在顯示時是否展開其子菜單,默認爲0,表示不展開
enabled:是否啓用,默認爲1,如果不啓用則其所有子菜單也將不可見
provider:提供本菜單鏈接定義的模塊名,不用定義,在系統內部會自動定義
metadata:默認爲空數組,額外的元數據,內部使用,比如保存菜單鏈接實體id等
class:表示菜單鏈接對象的菜單鏈接類,默認值:Drupal\Core\Menu\MenuLinkDefault,靜態定義和後臺定義用不同的類
form_class:表單類,默認爲Drupal\Core\Menu\Form\MenuLinkDefaultForm,靜態定義和後臺定義用不同的類
id:菜單id,也就是菜單鏈接的機器名(yml根鍵),也做插件id,不必定義,系統會自動採用根鍵

在其中title和description鍵名是可翻譯的,在yml發現器返回的定義數組中已經轉變爲TranslatableMarkup對象,翻譯上下文可保存在“title_context”和“description_context”中
和其他插件定義一樣可用deriver指定一個派生類,系統在menu_link_content.links.menu.yml中指定了如下派生類:
    \Drupal\menu_link_content\Plugin\Deriver\MenuLinkContentDeriver
該派生類會將管理後臺定義的菜單鏈接(菜單鏈接內容實體,見後)添加到插件定義中

獲取到所有插件定義後系統執行修改鉤:menu_links_discovered,函數如下:
   hook_menu_links_discovered_alter(&$definitions);
參數$definitions是yml發現器依據yml文件返回的定義(因爲插件派生也包含了網站後臺添加的菜單鏈接),鍵名爲插件id,鍵值爲定義數組,尚未附加默認值,我們可以通過該鉤子以程序方式添加靜態菜單鏈接定義,但不要通過該方式添加後臺方式定義的鏈接,在添加時標題和描述應當使用TranslatableMarkup對象,否則無法使用翻譯功能

關於該插件管理器的bug提示(drupal8.6時尚未修復):
1、 \Drupal\Core\Menu\MenuLinkManager::menuNameInUse
這個方法將始終返回非真值 因爲忘記了“return”
如果修復她,那麼就不可以先用yml去定義新菜單名(非菜單鏈接),然後再通過後臺創建對應的菜單實體(程序方式創建不受影響),順序必須倒過來 這可能讓一些測試失效  或模塊異常,如果不修復 那顯然這樣是不對的
2、在\Drupal\Core\Menu\MenuLinkManager::getDefinitions中$definition['id'] = $plugin_id;是多於的,會在processDefinition()中被執行。

通過該插件管理器獲取到菜單鏈接定義後,就可以將其送入菜單樹儲存器中保存了(rebuild()方法),但在此前系統還提供了定義覆寫功能,以增加靈活性。

菜單鏈接定義覆寫:
提供對yml靜態定義的菜單鏈接定義(含鉤子添加)的覆寫修改,提供一個覆寫數據層以達到修改目的,相當於一個覆寫數據存儲器,覆寫數據也會被保存到菜單樹存儲器中,有覆寫數據意味着菜單鏈接在後臺被編輯保存過,被標記爲可重置的,重置時將重新發現並刪除覆寫數據。
服務id:menu_link.static.overrides
類:Drupal\Core\Menu\StaticMenuLinkOverrides
在插件管理器重建菜單鏈接定義數據的過程中,添加覆寫數據運行在修改鉤menu_links_discovered之後,且覆寫範圍僅僅限於以下鍵:

[
      'menu_name' => '',
      'parent' => '',
      'weight' => 0,
      'expanded' => FALSE,
      'enabled' => FALSE,
];

我們可以通過以下代碼添加覆寫數據:

\Drupal::service("menu_link.static.overrides") ->saveOverride($id, array $definition);

參數$definition中以上鍵名外的其他鍵將被丟棄,在提供覆寫時應該爲以上每一個鍵提供值,否則將運用以上默認值,這可能不是你想要的。
覆寫數據被儲存在配置系統中,可以通過以下代碼得到全部覆寫定義:

  $config=\Drupal::configFactory()->getEditable("core.menu.static_menu_link_overrides");
  $overrides=$config->get("definitions");

其中$overrides是一個數組,鍵名是經過轉化的菜單鏈接id(點號被替換爲雙下劃線,雙下劃線被替換爲三下劃線,因爲在配置系統中點號代表層次劃分,配置項鍵名是不允許有點號的,但配置值可以有點號),鍵值是定義數組,用於對靜態定義進行覆寫替換,注意在配置系統中這裏儲存的覆寫數據不會經過配置系統的覆寫層覆寫。
通過管理後臺(菜單UI)添加的菜單鏈接並不使用覆寫功能,因爲其由菜單鏈接內容實體提供,相當於覆寫功能了

bug提示(已經提交官方,在drupal8.6時尚未修復):
在\Drupal\Core\Menu\StaticMenuLinkOverrides::saveOverride中
$all_overrides[$id] = $definition + $this->loadOverride($id);
加操作是沒有意義的,不會有任何補充,且$id已經是經過編碼轉化,這樣做也是錯誤的,但該bug不會對結果產生任何錯誤影響,只是浪費性能沒有意義罷了。

菜單樹儲存:
實現菜單鏈接信息的儲存、加載、構造菜單樹結構等功能,詳見接口
服務id:menu.tree_storage
類:Drupal\Core\Menu\MenuTreeStorage
這是一個私有服務,不能從容器中直接獲取使用,僅提供給服務plugin.manager.menu.link和menu.link_tree內部使用
緩存表:cache.menu
儲存菜單鏈接信息的數據庫表:menu_tree
該類實現稍複雜,爲了幫助理解以下列出數據庫表設計的重要字段及含義:

p1-p9字段:
drupal支持的菜單最大深度爲9層,這就是p1-p9字段的來由,她們儲存的值是菜單鏈接id號(字段mlid的值,這裏用id號區別於id),從P1到P9分別對應菜單鏈接路徑層次中從頂級菜單鏈接到自己的菜單鏈接id號,又稱實現路徑或物化路徑materialized path,未達最大深度的菜單後續P字段全部設置爲0;也就是說p1是頂級菜單鏈接(祖先菜單鏈接)的id號(如自己就是頂級菜單鏈接則就是自己的id號),依次爲第二個,第三個…最後一個值不爲0的p字段儲存菜單鏈接自己的id號,後面的p字段值全爲0
discovered:是否是通過插件發現機制找到的,如果是則值爲1,如果是後臺添加或修改的則爲0,如果是通過發現機制找到的,但又經過了後臺編輯保存則值爲0,此時編輯界面會出現重置按鈕
route_param_key:爲便於通過路由參數去查詢菜單鏈接而專門設置的一個輔助字段,以字符串方式儲存路由參數
depth:記錄菜單的深度,最小值爲1,最大值爲9,,值可以通過P1-P9判斷出來,設置她是爲了查詢方便
url:外部url鏈接,注意長度不能超過255個字符
options:代表url對象的選項參數,見本系列的url主題篇

關於該儲存器有以下幾個注意點或難點:
在刪除一個菜單鏈接時,會讓其子菜單接續到其父菜單上(也就是做父菜單鏈接的子菜單),而不是被全部刪除,如果某個菜單鏈接被移動,那麼其子菜單鏈接會一併跟隨移動並保持結構

方法doFindChildrenRelativeDepth返回子菜單鏈接中具備最大深度的子菜單鏈接的深度值減去當前深度值,如沒有子菜單鏈接,將返回0.

bug提示(已經提交官方,在drupal8.6時尚未修復):
以下方法始終返回true:
\Drupal\Core\Menu\MenuTreeStorage::menuNameInUse
修復如下:
return (bool) $this->safeExecuteSelect($query)->fetchField();

菜單樹參數:
在菜單樹儲存器的loadTreeData方法中可以依據條件來構造菜單的樹結構,系統爲了規範和方便使用這些條件,將條件設置封裝到了一個對象中,這就是菜單樹參數對象,她用於在構建菜單樹時設置菜單鏈接的查詢條件,是純粹的值對象,類定義如下:
\Drupal\Core\Menu\MenuTreeParameters
通常用於:\Drupal\Core\Menu\MenuTreeStorage::loadLinks
因爲該參數對象以對象方式傳遞,屬性又是公開可直接訪問的,在查詢過程中參數可能會因爲優化而被調整
設置的最大最小深度是包含最大最小值的

菜單鏈接樹:
實現菜單鏈接樹的加載、操縱器應用、菜單渲染數組構造等
服務id:menu.link_tree
類:Drupal\Core\Menu\MenuLinkTree
獲取方法:\Drupal::service("menu.link_tree");
從菜單樹儲存器中取回的是以數組方式構建的樹結構,在該服務的load方法中被轉化爲以對象方式構建的樹結構,也就是說樹結構中每一個節點從數組表示變爲了對象表示,每一個節點代表一個菜單鏈接,用以下對象表示:
   \Drupal\Core\Menu\MenuLinkTreeElement
該對象的屬性全部是公開可訪問的
在菜單鏈接樹服務的transform方法中,可以對以對象表示節點的菜單樹運用操縱器
操縱器:
操作器用於對菜單進行訪問控制等,單個操縱器以一個數組表示,鍵名callable表示回調,她可以是“controller_resolver”服務的getControllerFromDefinition方法解析的回調,也就是說可以使用容器服務或類來提供回調;操縱器會以第一個參數來接收菜單樹,如果還需要更多參數,可以通過鍵名args傳遞,這是可選的,是一個數組值,在操縱器的菜單樹參數後面依次傳遞。
操縱器接收的是以\Drupal\Core\Menu\MenuLinkTreeElement對象表示的菜單樹,需要返回調整後的新菜單樹。
默認的操縱器有:
   menu.default_tree_manipulators:checkAccess(訪問權限控制)
   menu.default_tree_manipulators:generateIndexAndSort(菜單樹節點元素排序)

關於菜單樹的顯示需要注意一下幾點:
在菜單鏈接樹中如果父菜單鏈接沒有開啓或不可訪問,那麼子菜單鏈接會繼承,而不管子菜單鏈接本身是否開啓或可訪問

菜單鏈接樹的渲染數組默認的主題鉤子是:

$build['#theme'] = 'menu__' . strtr($menu_name, '-', '_');

活動路徑:
在點擊某個菜單鏈接後,在新開的頁面裏如果還存在該菜單,則該菜單鏈接應當是展開顯示的,那麼在處理新開頁面的請求時就需要對此進行判斷,以確定當前的菜單路徑,這就是活動路徑服務的作用。
服務id:menu.active_trail
用於查找活動菜單路徑,接口對外只有兩個方法:
getActiveLink($menu_name = NULL)
依據當前路由匹配器中的路由信息查詢得到當前菜單鏈接,如果同樣的路由及參數在菜單系統中出現多次,那麼取查詢所得的第一個作爲當前活動菜單鏈接(依次按照depth、weight、id以升序ASC排序後的第一個),如果沒有找到路由名將直接返回null,該服務的實現非常簡單,如果有特殊用途,可以自己實現一個服務
getActiveTrailIds($menu_name)
依據上面方法返回的當前菜單鏈接查找菜單活動路徑,也就是當前菜單鏈接到菜單根的路徑上的所有菜單鏈接id,返回值是一個菜單id構成的數組值,包含自己的id,以菜單深度降序排序,也就是說數組中第一個元素是自己的id,倒數第二個是根菜單id,最後一個是['' => '']。如果當前菜單鏈接爲null,那麼直接返回['' => '']

該服務繼承了\Drupal\Core\Cache\CacheCollector,關於緩存集在本系列的“主題鉤子註冊”一章中有講,用於可能有大量緩存,但爲了性能只加載實際使用到的緩存。

菜單鏈接對象:
在系統中菜單鏈接可以用一個對象來表示,對此係統有以下實現:
實現接口:\Drupal\Core\Menu\MenuLinkInterface
基類:\Drupal\Core\Menu\MenuLinkBase
靜態yml文件定義的菜單鏈接默認類:
   \Drupal\Core\Menu\MenuLinkDefault
通過菜單UI後臺定義的菜單鏈接默認類:
   Drupal\menu_link_content\Plugin\Menu\MenuLinkContent
這兩個默認類都繼承以上的基類

菜單鏈接是否可重置:
依據以上對象的isResettable()方法判斷:在後臺菜單編輯界面是否顯示重置按鈕取決於菜單鏈接是否可被重置,在默認實現中如果在服務menu_link.static.overrides中有覆寫內容的菜單即被認爲是可重置的,重置操作就是刪除覆寫數據並重新保存從菜單鏈接插件發現器返回的數據,默認後臺定義的菜單鏈接不可重置。

更新菜單鏈接:
我們不應該直接調用菜單鏈接對象上的updateLink方法,而應該通過菜單鏈接插件管理器的updateDefinition方法來更新,前者僅更新覆寫數據(插件發現的鏈接)或菜單鏈接內容實體(後臺定義的鏈接),而不會去更新菜單樹儲存器,後者將進行全部處理。

菜單鏈接可刪除性:
依據菜單鏈接對象的isDeletable()方法返回值決定,默認插件發現的菜單鏈接不可刪除,後臺定義的可以刪除,刪除時不應該直接調用菜單鏈接對象上的deleteLink方法,而應該使用菜單鏈接插件管理器的removeDefinition方法,這樣才能從菜單樹儲存器中一併刪除

bug提示(drupal8.6):
在\Drupal\Core\Menu\MenuLinkDefault::updateLink方法中以下行無意義,是多於的:
$overrides['menu_name'] = $this->pluginDefinition['menu_name'];

菜單實體:
通過本文概述一節已經解釋了菜單實體的用途,該實體的釋文中只有有限的信息,更多信息是其他模塊通過鉤子添加的,比如菜單UI模塊通過以下鉤子動態添加的:
   menu_ui_entity_type_build(array &$entity_types)
這裏列出必要的信息以備查詢(你可以通過本系列配套的 “yunke_help” 模塊查看):
實體類:\Drupal\system\Entity\Menu
接口:Drupal\system\MenuInterface
添加及編輯表單類:\Drupal\menu_ui\MenuForm
刪除表單:Drupal\menu_ui\Form\MenuDeleteForm
列表構建器:Drupal\menu_ui\MenuListBuilder
訪問控制器:Drupal\system\MenuAccessControlHandler
儲存處理器:Drupal\Core\Config\Entity\ConfigEntityStorage
通過程序添加菜單實體示例如下:

        $menu = [
            'id'          => 'yunke',
            'label'       => '雲客',
            'description' => '使用程序創建菜單',
        ];
        \Drupal::entityTypeManager()->getStorage('menu')->create($menu)->save();

注意:在實際使用過程中需要判斷異常,通過後臺表單方式創建時會檢查是否已經存在實體以及是否已經有使用該菜單名的鏈接,而通過以上程序方式將不會檢查後者。


菜單鏈接內容實體:
由核心模塊menu_link_content提供,該模塊除了提供菜單鏈接內容實體外,還提供菜單鏈接派生髮現、必要的路由信息等,菜單UI模塊依賴她共同提供管理後臺的菜單管理、添加、刪除等功能
在管理後臺自定義添加的菜單鏈接採用本實體儲存(同時也儲存到菜單樹儲存器中),採用實體可以方便使用實體統一標準的表單、訪問控制等,關於實體請見本系列實體相關主題,以下列出概要信息(你也可以通過yunke_help模塊查看):
實體類:\Drupal\menu_link_content\Entity\MenuLinkContent
儲存器:Drupal\Core\Entity\Sql\SqlContentEntityStorage
儲存模式:Drupal\menu_link_content\MenuLinkContentStorageSchema
訪問控制器:Drupal\menu_link_content\MenuLinkContentAccessControlHandler
添加編輯表單:Drupal\menu_link_content\Form\MenuLinkContentForm
刪除表單:Drupal\menu_link_content\Form\MenuLinkContentDeleteForm

下面列出一些注意點:
1、在後臺編輯的菜單鏈接如果是插件發現機制找到的,那麼在保存時將保存到覆寫,而不是實體
2、路由entity.menu.add_link_form所示的控制器需要一個菜單實體(不是菜單鏈接內容實體),那麼她是從哪裏來的呢?這是因爲該路由被添加了參數轉換選項(可用yunke_help模塊查看最終的路由定義信息),添加者是“resolver_manager.entity”服務,類:
\Drupal\Core\Entity\EntityResolverManager
該服務在路由定義修改事件中被調用(訂閱器服務:route_subscriber.entity),她會依據路由所指控制器的參數的類型暗示決定是否添加參數轉化選項
3、在菜單鏈接內容實體的 “保存後”和“刪除前”方法中會調用菜單鏈接插件管理器,使得菜單樹儲存器同步更新數據
4、實體字段rediscover指示菜單鏈接是否應該被重新發現,有些菜單鏈接是動態的,每次訪問不一樣,在實體保存時如果是內部鏈接該值爲真,外部鏈接爲假,默認安裝中該字段沒有使用案例

創建菜單鏈接內容實體:
程序方式創建示例如下:

        $menu_link = [
            'title'       => '雲客自定義菜單', //必選,菜單鏈接文本
            'description' => '本例來自雲客D8源碼分析系列', //可選,鏈接中title屬性的值
            'parent'      => 'system.admin', //可選,父菜單鏈接,默認爲空表示頂級菜單
            'link'        => ['uri' => 'http://www.qq.com', 'options' => ['attributes' => ['target' => '_blank']]],
            //必選,鏈接,options數組將傳遞給url對象,可用選項值控制額外行爲或數據,見url對象
            'menu_name'   => 'admin', //可選,菜單名,默認爲tools
            'external'    => true, //可選,是否外部鏈接,默認不是,須依據實際的uri設置
            'expanded'    => true, //可選,是否展開顯示
            'enabled'     => true, //可選,是否啓用該菜單鏈接
            'weight'      => 100, //可選,排序權重,默認爲0
        ];
        $menu_link_content = \Drupal::entityTypeManager()->getStorage('menu_link_content')->create($menu_link);
        $menu_link_content->save();

程序方式時往往採用路由來生成link,如($url=\Drupal::url($route_name, $route_parameters))見本系列URL篇

管理後臺方式創建:
通過管理界面創建時,鏈接輸入框中可以輸入以下內容:
首頁:“<front>”,也可以是“/”、“<front>#foo”、“<front>?foo=bar”這樣的內容
外部鏈接:必須帶協議,如“http:”
內部鏈接:不要以協議開始,如“/node/add”,內部會轉化爲“internal: /node/add”儲存
實體鏈接:“entity:node/1”,目前僅支持節點實體類型
該控件使用了實體自動完成,在輸入時會自動匹配節點實體的標題給出提示,如果需要,選中後會成爲一個實體鏈接,注意該控件不支持路由方式,默認的界面有許多東西沒辦法設置,比如設置菜單以新窗口打開等,這怎麼解決呢?由於鏈接字段採用了“link_default”類型的輸入控件,我們可以修改字段定義,以採用自定義的鏈接控件,在自定義控件中加入渲染各種選項的設置,最終把這些選項值保存到鏈接字段的“options”屬性中即可,詳見本系列字段控件主題

菜單塊:
系統提供了一個菜單塊:
   \Drupal\system\Plugin\Block\SystemMenuBlock
這是一個派生塊,派生類爲:
    Drupal\system\Plugin\Derivative\SystemMenuBlock
她將把每一個菜單映射爲一個塊,從而可以放置到頁面上
塊的緩存失效標籤爲:config:system.menu.菜單名
注意:在保存或刪除菜單實體時清除了塊插件定義的緩存,因此塊能實時更新,這裏並未使用失效標籤
這是一個很簡單的塊,不做過多介紹,關於塊請見本系列塊主題


父菜單選擇器:
在構造菜單鏈接編輯表單時用於提供選擇父菜單鏈接的選項:
服務id:menu.parent_form_selector
類:\Drupal\Core\Menu\MenuParentFormSelector
獲取方法:\Drupal::service ('menu.parent_form_selector');
只有兩個公共方法:
getParentSelectOptions($id = '', array $menus = NULL, CacheableMetadata &$cacheability = NULL)
提供一個數組表示的選項,鍵名是“$menu_name : $PluginId”,頂級菜單沒有插件id,鍵值爲菜單鏈接標題,限定30個字符,在標題前使用“--”代表菜單層級,該服務會依據菜單鏈接的深度來過濾掉不適合作爲父菜單的菜單鏈接,以避免總深度超過9層,同時將自己排除在外。
parentSelectElement($menu_parent, $id = '', array $menus = NULL)
提供編輯表單使用的表單選擇器元素,如果菜單鏈接已經存在,則將其父菜單鏈接設置爲默認值

如需個性化父菜單鏈接選擇控件可以覆寫該服務

工具欄系統管理菜單:
該菜單不是通過塊添加的,而是在鉤子toolbar_toolbar()中添加,詳見本系列系統工具欄主題,由菜單系統提供內容,在學習了本主題後就不難理解了,在以下函數中添加:
toolbar_prerender_toolbar_administration_tray
所在文件: /core/modules/toolbar/toolbar.module


補充:
1、菜單API官網文檔:
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Menu%21menu.api.php/group/menu/8.6.x
2、yunke_help模塊下載地址:
https://blog.csdn.net/u011474028/article/details/80888152
3、改進建議:我們在yml文件中定義菜單鏈接時,需要知道父菜單鏈接的id,但獲取是個比較麻煩的事情,因此建議在後臺菜單編輯界面顯示菜單鏈接id,目前你可以通過yunke_help模塊來獲取任意菜單鏈接id,除此外你可以使用一些ide工具,比如sublime,在文件系統中搜索路由名,進而搜索到菜單id
4、bug提示:
服務:toolbar.menu_tree傳遞了七個參數,但構造函數只用了五個,'@cache.menu', '@current_route_match'多餘
5、學習完菜單,你可能會想到麪包屑路徑,這個將在單獨的主題裏面講解,這裏給出一些信息:
塊:Drupal\system\Plugin\Block\SystemBreadcrumbBlock
服務id: breadcrumb
類:Drupal\Core\Breadcrumb\BreadcrumbManager

 

 

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

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