各位观众老爷们,晚上好!欢迎来到今晚的“WordPress 源码揭秘”特别节目。我是你们的老朋友,Bug 终结者,今晚咱们就来扒一扒 WordPress 的 wp_nav_menu()
函数,重点研究一下它背后的“男人”——wp_get_nav_menu_items()
函数,看看它到底是怎么把一个个菜单项给捞出来的。
准备好了吗?系好安全带,代码之旅即将开始!
一、wp_nav_menu()
:菜单的门面担当
首先,咱们得对 wp_nav_menu()
有个大致的了解。这玩意儿就是 WordPress 用来显示菜单的函数,你在主题模板里插入它,它就会把你的菜单华丽丽地呈现出来。
wp_nav_menu( array(
'theme_location' => 'primary',
'menu_class' => 'main-nav',
'container' => 'div',
'container_class'=> 'menu-container'
// ... 其他参数
) );
这段代码大概就是 wp_nav_menu()
的一个常见用法。 theme_location
定义了菜单的位置(在后台菜单管理界面设置的),menu_class
定义了菜单的 CSS 类,container
定义了菜单的容器标签, container_class
定义了菜单容器的CSS类。 当然,wp_nav_menu()
还有很多其他的参数,可以控制菜单的各种细节,比如菜单的深度、排序方式等等。
但是,wp_nav_menu()
本身并不直接从数据库里读取菜单项,它只是一个“门面”,真正干活的是 wp_get_nav_menu_items()
。
二、wp_get_nav_menu_items()
:菜单项的挖掘机
wp_get_nav_menu_items()
函数才是真正的“挖掘机”,它负责从数据库里把菜单项捞出来,并按照一定的结构组织好,交给 wp_nav_menu()
去展示。
咱们来看看 wp_get_nav_menu_items()
的基本用法:
$menu_name = 'main-menu'; // 你的菜单名称或 ID
$menu_items = wp_get_nav_menu_items( $menu_name );
if ( $menu_items ) {
foreach ( $menu_items as $menu_item ) {
echo $menu_item->title . '<br>';
echo $menu_item->url . '<br>';
// ... 其他属性
}
} else {
echo '菜单不存在或为空!';
}
这段代码首先指定了菜单的名称(或者 ID),然后调用 wp_get_nav_menu_items()
函数,把菜单项捞出来放到 $menu_items
变量里。 如果 $menu_items
不为空,就遍历它,输出每个菜单项的标题和 URL。
三、深入 wp_get_nav_menu_items()
的源码
现在,咱们来深入 wp_get_nav_menu_items()
的源码,看看它是怎么工作的。 打开 wp-includes/nav-menu.php
文件,找到 wp_get_nav_menu_items()
函数的定义。
(由于 WordPress 版本更新,源码可能会略有不同,以下分析基于 WordPress 6.x 版本)
function wp_get_nav_menu_items( $menu, $args = array() ) {
/**
* Filters the arguments used to retrieve nav menu items.
*
* @since 3.0.0
*
* @param WP_Term|string|int $menu The menu ID, slug, or name.
* @param stdClass $args An object containing wp_get_nav_menu_items() arguments.
*/
$args = apply_filters( 'wp_get_nav_menu_items_args', (object) $args, $menu );
$menu_items = false;
$menu_object = wp_get_nav_menu_object( $menu );
if ( $menu_object ) {
$menu_items = wp_cache_get( 'nav_menu_items_' . $menu_object->term_id, 'nav_menu' );
if ( false === $menu_items ) {
$items = get_posts(
array(
'numberposts' => -1,
'orderby' => 'menu_order',
'order' => 'ASC',
'post_type' => 'nav_menu_item',
'post_status' => 'publish',
'output' => ARRAY_A,
'output_key' => 'menu_order',
'update_post_term_cache' => false,
'suppress_filters' => true,
'tax_query' => array(
array(
'taxonomy' => 'nav_menu',
'field' => 'term_id',
'terms' => (array) $menu_object->term_id,
),
),
)
);
if ( $items ) {
_wp_menu_item_classes_by_context( $items );
$menu_items = array();
foreach ( $items as $item ) {
$menu_items[] = new WP_Post( $item );
}
wp_cache_set( 'nav_menu_items_' . $menu_object->term_id, $menu_items, 'nav_menu' );
}
}
if ( $menu_items ) {
/**
* Filters the sorted array of nav menu items before they're processed.
*
* @since 3.0.0
*
* @param WP_Post[] $menu_items The array of menu items.
* @param stdClass $args An object containing wp_get_nav_menu_items() arguments.
*/
$menu_items = apply_filters( 'wp_get_nav_menu_items', $menu_items, $args );
}
}
return $menu_items;
}
代码有点长,咱们一段一段来分析:
-
参数过滤:
$args = apply_filters( 'wp_get_nav_menu_items_args', (object) $args, $menu );
这段代码用
apply_filters()
函数对传入的参数进行过滤。wp_get_nav_menu_items_args
是一个过滤器钩子,允许开发者修改传递给wp_get_nav_menu_items()
函数的参数。 这提供了一个扩展点,可以根据不同的需求定制菜单项的获取方式。 -
获取菜单对象:
$menu_object = wp_get_nav_menu_object( $menu );
wp_get_nav_menu_object()
函数根据传入的菜单名称、ID 或 slug,获取对应的菜单对象(WP_Term
对象)。 这个对象包含了菜单的各种信息,比如菜单的 ID、名称、slug 等。 -
缓存检查:
if ( $menu_object ) { $menu_items = wp_cache_get( 'nav_menu_items_' . $menu_object->term_id, 'nav_menu' ); if ( false === $menu_items ) { // ... 从数据库获取菜单项 } }
这段代码首先判断菜单对象是否存在。 如果存在,就尝试从缓存中获取菜单项。
wp_cache_get()
函数会根据键名('nav_menu_items_' . $menu_object->term_id
)和组名('nav_menu'
)从缓存中查找菜单项。 如果缓存中存在,就直接返回缓存的菜单项,避免重复查询数据库,提高性能。 -
从数据库获取菜单项:
$items = get_posts( array( 'numberposts' => -1, 'orderby' => 'menu_order', 'order' => 'ASC', 'post_type' => 'nav_menu_item', 'post_status' => 'publish', 'output' => ARRAY_A, 'output_key' => 'menu_order', 'update_post_term_cache' => false, 'suppress_filters' => true, 'tax_query' => array( array( 'taxonomy' => 'nav_menu', 'field' => 'term_id', 'terms' => (array) $menu_object->term_id, ), ), ) );
如果缓存中没有菜单项,就使用
get_posts()
函数从数据库中获取。 注意,这里post_type
设置为nav_menu_item
,表示要获取的是菜单项类型的文章。tax_query
参数用于指定菜单项所属的菜单。menu_order
用于指定菜单项的排序方式。numberposts
设置为 -1, 代表获取所有的菜单项。简单来说,这段代码就是从
wp_posts
表中查找post_type
为nav_menu_item
,并且属于指定菜单的(通过tax_query
指定)所有文章。 -
处理菜单项类名:
if ( $items ) { _wp_menu_item_classes_by_context( $items ); $menu_items = array(); foreach ( $items as $item ) { $menu_items[] = new WP_Post( $item ); } wp_cache_set( 'nav_menu_items_' . $menu_object->term_id, $menu_items, 'nav_menu' ); }
如果从数据库中获取到了菜单项,就调用
_wp_menu_item_classes_by_context()
函数,为每个菜单项添加一些 CSS 类名,比如current-menu-item
(当前菜单项)、current-menu-ancestor
(当前菜单项的祖先菜单项)等等。 这些类名可以方便地在 CSS 中设置菜单的样式。然后,把每个菜单项从数组转换为
WP_Post
对象,并存储到$menu_items
数组中。 最后,把$menu_items
数组缓存起来,以便下次使用。 -
菜单项过滤:
if ( $menu_items ) { $menu_items = apply_filters( 'wp_get_nav_menu_items', $menu_items, $args ); }
这段代码再次使用
apply_filters()
函数对菜单项进行过滤。wp_get_nav_menu_items
是另一个过滤器钩子,允许开发者修改菜单项的内容。 这提供了一个更强大的扩展点,可以根据不同的需求修改菜单项的标题、URL、CSS 类名等等。 -
返回菜单项:
return $menu_items;
最后,
wp_get_nav_menu_items()
函数返回$menu_items
数组,这个数组包含了所有菜单项的WP_Post
对象。
四、wp_get_nav_menu_items()
的核心逻辑总结
为了方便大家理解,咱们用一张表格来总结一下 wp_get_nav_menu_items()
函数的核心逻辑:
步骤 | 描述 | 涉及函数/变量 |
---|---|---|
1 | 对传入的参数进行过滤,允许开发者修改参数。 | apply_filters( 'wp_get_nav_menu_items_args' ) |
2 | 根据菜单名称、ID 或 slug,获取菜单对象(WP_Term 对象)。 |
wp_get_nav_menu_object() |
3 | 尝试从缓存中获取菜单项。如果缓存中存在,则直接返回缓存的菜单项。 | wp_cache_get() |
4 | 如果缓存中没有菜单项,则使用 get_posts() 函数从数据库中获取菜单项(nav_menu_item 类型的文章)。 |
get_posts() |
5 | 为每个菜单项添加一些 CSS 类名,比如 current-menu-item 、current-menu-ancestor 等等。 |
_wp_menu_item_classes_by_context() |
6 | 把每个菜单项从数组转换为 WP_Post 对象,并存储到 $menu_items 数组中。 |
WP_Post |
7 | 把 $menu_items 数组缓存起来,以便下次使用。 |
wp_cache_set() |
8 | 对菜单项进行过滤,允许开发者修改菜单项的内容。 | apply_filters( 'wp_get_nav_menu_items' ) |
9 | 返回 $menu_items 数组,这个数组包含了所有菜单项的 WP_Post 对象。 |
五、菜单项的结构
wp_get_nav_menu_items()
函数返回的是一个包含 WP_Post
对象的数组,每个 WP_Post
对象代表一个菜单项。 WP_Post
对象有很多属性,常用的属性包括:
属性 | 描述 |
---|---|
ID |
菜单项的 ID |
post_title |
菜单项的标题(与 title 属性相同) |
title |
菜单项的标题 |
url |
菜单项的 URL |
menu_order |
菜单项的排序顺序 |
post_content |
菜单项的描述 |
post_excerpt |
菜单项的目标属性(target, 如_blank) |
classes |
菜单项的 CSS 类名(是一个数组,需要用 implode() 函数转换为字符串) |
target |
链接打开方式 (通常是 _blank, _self 等) |
xfn |
链接关系 (link relationship) |
description |
描述 |
object_id |
关联对象的ID (如文章ID, 分类ID) |
object |
关联对象的类型 (如 post, category) |
type |
菜单项的类型 (如 post_type, taxonomy, custom) |
type_label |
菜单项类型标签 |
db_id |
数据库ID |
menu_item_parent |
父级菜单项ID |
你可以通过访问这些属性,获取菜单项的各种信息。
六、实战演练:自定义菜单项的输出
现在,咱们来做一个实战演练,演示如何自定义菜单项的输出。 假设你想在菜单项的标题后面加上一个小图标。
function my_custom_menu_item_output( $items, $args ) {
foreach ( $items as $item ) {
$item->title .= ' <i class="fa fa-star"></i>'; // 假设你使用了 Font Awesome 图标库
}
return $items;
}
add_filter( 'wp_get_nav_menu_items', 'my_custom_menu_item_output', 10, 2 );
这段代码定义了一个名为 my_custom_menu_item_output()
的函数,这个函数接收两个参数:$items
(菜单项数组)和 $args
(wp_get_nav_menu_items()
函数的参数)。 在函数内部,我们遍历 $items
数组,为每个菜单项的标题后面加上一个 Font Awesome 的星标图标。 最后,我们使用 add_filter()
函数,把 my_custom_menu_item_output()
函数挂载到 wp_get_nav_menu_items
过滤器钩子上。
这样,每次调用 wp_get_nav_menu_items()
函数获取菜单项时,都会执行 my_custom_menu_item_output()
函数,为每个菜单项的标题后面加上一个星标图标。
七、总结
今天咱们深入探讨了 WordPress 的 wp_nav_menu()
函数和 wp_get_nav_menu_items()
函数,特别是 wp_get_nav_menu_items()
函数的源码,了解了它是如何从数据库中获取菜单项,并按照一定的结构组织好,交给 wp_nav_menu()
去展示的。 咱们还学习了如何使用过滤器钩子,自定义菜单项的输出。
希望今天的分享对你有所帮助。 记住,源码是最好的老师,多看源码,多思考,你也能成为 WordPress 大牛!
今天的节目就到这里,感谢大家的收看,咱们下期再见!