108. 导航菜单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%21m…
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