大家好,我是你们今天的菜单品鉴师,啊不,是WordPress菜单源码分析师。今天咱们就来好好扒一扒 WordPress 的 wp_nav_menu()
这个大厨,看看它如何与 wp_get_nav_menu_items()
这个食材供应商配合,最终端出一盘秀色可餐的菜单。
开胃小菜:wp_nav_menu()
的基本用法和参数
首先,咱们得知道 wp_nav_menu()
是干啥的。简单来说,它就是用来在 WordPress 主题中显示导航菜单的。你只要在你的主题模板文件中调用它,它就能根据你的设置,把菜单渲染出来。
<?php
wp_nav_menu( array(
'theme_location' => 'primary', // 菜单位置,需要在主题 functions.php 中注册
'menu' => '', // 指定要显示的菜单 ID 或名称,如果 theme_location 有值,则忽略
'container' => 'div', // 菜单容器标签
'container_class' => 'menu-primary-container', // 容器 class
'container_id' => '', // 容器 ID
'menu_class' => 'menu', // 菜单 ul 标签 class
'menu_id' => '', // 菜单 ul 标签 ID
'echo' => true, // 是否直接输出,false 则返回 HTML 字符串
'fallback_cb' => 'wp_page_menu', // 如果菜单不存在,则使用的回调函数
'before' => '', // 菜单链接前的内容
'after' => '', // 菜单链接后的内容
'link_before' => '', // 链接文本前的内容
'link_after' => '', // 链接文本后的内容
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>', // 菜单 ul 标签的 HTML 结构
'depth' => 0, // 菜单深度,0 表示不限制
'walker' => '', // 用于遍历菜单项的 Walker 对象
) );
?>
上面的代码片段展示了 wp_nav_menu()
的基本用法。可以看到,它接收一个数组作为参数,这个数组定义了菜单的各种属性,比如位置、容器、样式等等。
参数 | 描述 |
---|---|
theme_location |
指定菜单位置,需要在主题的 functions.php 文件中使用 register_nav_menu() 函数注册。这是最常用的参数,因为它允许你在 WordPress 后台的“外观” -> “菜单”中,将一个菜单分配到特定的主题位置。 |
menu |
指定要显示的菜单 ID 或名称。如果设置了 theme_location ,则此参数将被忽略。通常不推荐直接使用菜单 ID 或名称,而是使用 theme_location ,因为它更灵活,允许用户在后台自由地分配菜单。 |
container |
指定菜单容器的 HTML 标签。默认为 'div' ,也可以设置为 'nav' 、'span' 等等。如果不需要容器,可以设置为 false 。 |
container_class |
指定菜单容器的 CSS class。可以根据需要自定义 CSS 样式。 |
container_id |
指定菜单容器的 HTML ID。 |
menu_class |
指定菜单 <ul> 标签的 CSS class。 |
menu_id |
指定菜单 <ul> 标签的 HTML ID。 |
echo |
指定是否直接输出菜单 HTML。如果设置为 true (默认值),则直接输出;如果设置为 false ,则返回 HTML 字符串,可以将其存储在变量中,并在需要的时候输出。 |
fallback_cb |
指定一个回调函数,当指定的菜单不存在时,该函数会被调用。默认为 wp_page_menu() ,它会显示一个基于页面结构的简单菜单。可以自定义一个回调函数,来显示一个默认菜单或提示信息。 |
before |
在每个菜单项链接之前添加的内容。 |
after |
在每个菜单项链接之后添加的内容。 |
link_before |
在每个菜单项链接文本之前添加的内容。 |
link_after |
在每个菜单项链接文本之后添加的内容。 |
items_wrap |
指定菜单 <ul> 标签的 HTML 结构。%1$s 会被替换为 menu_id ,%2$s 会被替换为 menu_class ,%3$s 会被替换为菜单项的 HTML。可以自定义这个参数来修改菜单的 HTML 结构。 |
depth |
指定菜单的深度。0 表示不限制深度,1 表示只显示顶级菜单项,2 表示显示顶级菜单项和一级子菜单项,以此类推。 |
walker |
指定一个用于遍历菜单项的 Walker 对象。Walker 对象用于自定义菜单项的 HTML 输出。可以自定义一个 Walker 类,来完全控制菜单的 HTML 结构。 |
主菜上桌:wp_nav_menu()
的源码剖析
现在,咱们来深入 wp_nav_menu()
的源码,看看它是如何工作的。
function wp_nav_menu( $args = array() ) {
static $menu_id_slugs = array();
$defaults = array(
'menu' => '',
'container' => 'div',
'container_class' => 'menu-{menu slug}-container',
'container_id' => '',
'menu_class' => 'menu',
'menu_id' => '',
'echo' => true,
'fallback_cb' => 'wp_page_menu',
'before' => '',
'after' => '',
'link_before' => '',
'link_after' => '',
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
'depth' => 0,
'walker' => '',
'theme_location' => ''
);
$args = wp_parse_args( $args, $defaults );
$args = (object) $args;
// 允许插件修改参数
$args = apply_filters( 'wp_nav_menu_args', $args );
// 获取菜单对象
$menu = wp_get_nav_menu_object( $args->menu );
// 如果指定了主题位置,并且没有指定菜单,则尝试从主题位置获取菜单
if ( ! $menu && $args->theme_location && ( $locations = get_nav_menu_locations() ) && isset( $locations[ $args->theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
}
// 如果菜单不存在,则调用 fallback_cb
if ( ! $menu ) {
if ( $args->fallback_cb && is_callable( $args->fallback_cb ) ) {
return call_user_func( $args->fallback_cb, (array) $args );
}
return false;
}
$menu_id = $menu->term_id;
// 构建菜单容器
$container = 'nav' === $args->container ? 'nav' : $args->container;
$container_id = $args->container_id ? ' id="' . esc_attr( $args->container_id ) . '"' : '';
if ( $container ) {
$container_class = 'menu-'. $menu->slug .'-container';
$container_class = apply_filters( 'wp_nav_menu_container_class', $container_class, $args, $menu );
$container_class = ' class="' . esc_attr( $container_class ) . '"';
echo '<' . $container . $container_id . $container_class . '>';
}
// 构建菜单 ul 标签
$menu_id_slug = sanitize_title( $menu->slug );
if ( isset( $menu_id_slugs[ $menu_id_slug ] ) ) {
$menu_id_slugs[ $menu_id_slug ]++;
$menu_id_slug .= '-' . $menu_id_slugs[ $menu_id_slug ];
} else {
$menu_id_slugs[ $menu_id_slug ] = 0;
}
$menu_id = $args->menu_id ? $args->menu_id : 'menu-' . $menu_id_slug;
$wrap_id = $menu_id;
$wrap_class = $args->menu_class;
$items_wrap = apply_filters( 'wp_nav_menu_items_wrap', $args->items_wrap, $args, $menu );
$wrap = sprintf( $items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), '%3$s' );
// **核心步骤:获取菜单项**
$nav_menu_args = array(
'menu' => $menu,
'container' => false,
'items_wrap' => false,
'echo' => false,
'walker' => $args->walker,
'depth' => $args->depth,
);
$nav_menu = wp_get_nav_menu_items( $menu->term_id, $nav_menu_args );
// 允许插件修改菜单项
$nav_menu = apply_filters( 'wp_nav_menu_objects', $nav_menu, $args );
$items = '';
$output = '';
if ( $nav_menu ) {
$args = (object) $args;
$sorted_menu_items = array();
$menu_items = wp_list_sort( $nav_menu, array( 'menu_order' => SORT_ASC, 'ID' => SORT_ASC ) );
$sorted_menu_items = wp_nav_menu_items( $menu_items, $args );
unset($menu_items);
$items .= walk_nav_menu_tree( $sorted_menu_items, $args->depth, $args );
unset($sorted_menu_items);
if ( ! empty( $items ) ) {
$output = apply_filters( 'wp_nav_menu', $wrap, $args, $menu );
$output = str_replace('%3$s', $items, $output);
}
}
if ( $container ) {
echo $output;
echo '</' . $container . '>';
} else {
echo $output;
}
return $output;
}
咱们来一步步解读这个大厨的操作流程:
-
参数解析与准备:
wp_parse_args()
:将传入的参数与默认参数合并,确保所有需要的参数都有值。apply_filters( 'wp_nav_menu_args', $args )
:允许插件修改传入的参数,增加了灵活性。wp_get_nav_menu_object()
:根据传入的menu
参数(可以是菜单 ID、名称或 slug)获取菜单对象。如果menu
参数为空,但指定了theme_location
,则尝试从主题位置获取菜单。
-
菜单验证与回退:
- 如果找不到菜单,并且设置了
fallback_cb
,则调用fallback_cb
指定的回调函数。wp_page_menu()
是一个常用的回退函数,它会根据页面结构生成一个简单的菜单。
- 如果找不到菜单,并且设置了
-
容器构建:
- 根据
container
、container_class
和container_id
参数,构建菜单的容器 HTML 标签。
- 根据
-
<ul>
标签构建:- 生成唯一的菜单 ID,并根据
menu_class
和menu_id
参数,构建菜单的<ul>
标签的 HTML 结构。
- 生成唯一的菜单 ID,并根据
-
获取菜单项(重头戏):
wp_get_nav_menu_items()
: 这是关键的一步,调用wp_get_nav_menu_items()
函数,从数据库中获取菜单项。传入的参数包括菜单 ID 和一个参数数组,用于指定获取菜单项的方式。
-
菜单项处理:
apply_filters( 'wp_nav_menu_objects', $nav_menu, $args )
:允许插件修改获取到的菜单项。wp_list_sort()
: 对菜单项进行排序,通常按照menu_order
字段进行升序排序。wp_nav_menu_items()
: 对菜单项进行预处理,为后续的 Walker 类做准备。walk_nav_menu_tree()
:使用 Walker 类遍历菜单项,生成最终的 HTML 结构。Walker 类是一个用于遍历树状结构的通用类,可以自定义 Walker 类来完全控制菜单项的 HTML 输出。
-
输出:
- 将生成的 HTML 结构插入到容器中,并输出到页面。
配料准备:wp_get_nav_menu_items()
的详解
现在,咱们来详细看看 wp_get_nav_menu_items()
这个食材供应商是如何工作的。
function wp_get_nav_menu_items( $menu, $args = array() ) {
$menu_id = ( is_object( $menu ) ) ? $menu->term_id : (int) $menu;
$locations = get_nav_menu_locations();
$location = array_search($menu_id, $locations);
$args = (object) wp_parse_args( $args, array() );
$items = wp_cache_get( "nav_menu_items:$menu_id", 'nav_menu' );
if ( false === $items ) {
$items = array();
$args = array(
'post_type' => 'nav_menu_item',
'posts_per_page' => -1,
'tax_query' => array(
array(
'taxonomy' => 'nav_menu',
'field' => 'term_id',
'terms' => array( $menu_id ),
),
),
'orderby' => 'menu_order',
'order' => 'ASC',
'suppress_filters' => true,
);
$posts = get_posts( $args );
if ( $posts ) {
update_post_caches( $posts );
$items = array_map( 'wp_setup_nav_menu_item', $posts );
wp_cache_add( "nav_menu_items:$menu_id", $items, 'nav_menu' );
}
}
if ( $items ) {
$items = apply_filters( 'wp_get_nav_menu_items', $items, $menu, $args );
}
return $items;
}
-
参数准备:
wp_parse_args()
:解析传入的参数。$menu_id
:获取菜单 ID。
-
缓存机制:
wp_cache_get()
:尝试从缓存中获取菜单项。如果缓存中存在,则直接返回缓存的菜单项,避免重复查询数据库。
-
数据库查询:
- 如果缓存中不存在菜单项,则构建一个
WP_Query
对象,查询nav_menu_item
类型的文章,这些文章属于指定的菜单(通过tax_query
参数指定)。 get_posts()
:执行查询,获取菜单项文章。
- 如果缓存中不存在菜单项,则构建一个
-
菜单项对象化:
update_post_caches()
:更新文章缓存。array_map( 'wp_setup_nav_menu_item', $posts )
:将查询到的文章对象转换为菜单项对象。wp_setup_nav_menu_item()
函数会为每个文章对象添加一些额外的属性,使其更方便在菜单中使用。
-
缓存存储:
wp_cache_add()
:将获取到的菜单项存储到缓存中,以便下次使用。
-
过滤:
apply_filters( 'wp_get_nav_menu_items', $items, $menu, $args )
:允许插件修改获取到的菜单项。
-
返回:
- 返回菜单项数组。
菜品加工:Walker 类的作用
Walker 类是 WordPress 中用于遍历树状结构的通用类。在菜单渲染中,Walker 类用于遍历菜单项,并生成最终的 HTML 结构。
WordPress 默认提供了一个 Walker_Nav_Menu
类,用于渲染菜单。你可以自定义 Walker 类,来完全控制菜单的 HTML 输出。
class My_Custom_Walker extends Walker_Nav_Menu {
function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
// 在这里自定义菜单项的 HTML 输出
$output .= '<li class="my-custom-menu-item">';
$output .= '<a href="' . esc_attr( $item->url ) . '">' . esc_html( $item->title ) . '</a>';
$output .= '</li>';
}
}
上面的代码片段展示了一个自定义 Walker 类的示例。start_el()
方法用于输出每个菜单项的 HTML。你可以根据需要,修改这个方法,来生成你想要的 HTML 结构。
总结
wp_nav_menu()
和 wp_get_nav_menu_items()
协同工作,完成了 WordPress 菜单的渲染。wp_nav_menu()
负责接收参数、获取菜单对象、构建容器和调用 wp_get_nav_menu_items()
获取菜单项。wp_get_nav_menu_items()
负责从数据库中查询菜单项,并将其转换为菜单项对象。最后,通过 Walker 类遍历菜单项,生成最终的 HTML 结构。
理解了 wp_nav_menu()
和 wp_get_nav_menu_items()
的工作原理,你就可以更好地自定义 WordPress 菜单,满足你的各种需求。
希望今天的讲解能帮助你更好地理解 WordPress 菜单的渲染过程。如果你还有其他问题,欢迎提问。下次有机会,咱们再一起研究 WordPress 的其他源码。