各位观众老爷们,晚上好!今儿咱们来聊聊 WordPress 菜单系统里一个非常重要的函数:wp_get_nav_menu_items()
。这玩意儿就像个辛勤的园丁,负责把菜单里的一个个“小花朵”(菜单项)从数据库里挖出来,然后按照它们之间的关系,搭成一个漂亮的“花架子”(层级结构)。
准备好了吗?咱们这就开挖!
一、先亮个相:wp_get_nav_menu_items()
的基本用法
在开始深入源码之前,咱们先来个“热身”,看看 wp_get_nav_menu_items()
怎么用。
<?php
$menu_name = 'my-custom-menu'; // 你的菜单名称或 ID
$menu_items = wp_get_nav_menu_items( $menu_name );
if ( $menu_items ) {
foreach ( $menu_items as $menu_item ) {
echo '<li><a href="' . $menu_item->url . '">' . $menu_item->title . '</a></li>';
}
} else {
echo '菜单不见啦!';
}
?>
这段代码很简单,就是根据菜单名称(或 ID)获取菜单项,然后循环输出。每个 $menu_item
对象都包含了菜单项的各种信息,比如标题、URL、ID 等等。
二、扒开源码:wp_get_nav_menu_items()
的核心逻辑
好了,热身完毕,现在咱们要开始“扒皮”了,看看 wp_get_nav_menu_items()
到底是怎么工作的。
1. 参数处理:菜单的“身份验证”
首先,函数会接收一个菜单标识符(可以是菜单名称、ID 或者菜单对象)。它会先验证这个标识符是否有效,确保咱们要找的菜单确实存在。
function wp_get_nav_menu_items( $menu, $args = array() ) {
$menu_object = wp_get_nav_menu_object( $menu );
if ( ! $menu_object ) {
return false; // 菜单不存在,直接打道回府
}
// ... 后面还有很多代码
}
这里用到了 wp_get_nav_menu_object()
函数,它的作用是根据传入的菜单标识符,获取对应的菜单对象。如果找不到,就返回 false
。
2. 查询菜单项:从数据库里“捞鱼”
接下来,函数会构建一个数据库查询,从 wp_posts
表里捞出属于这个菜单的所有菜单项。
$items = wp_cache_get( $cache_key, 'nav_menu_items' );
if ( false === $items ) {
$args = wp_parse_args( $args, array( 'output' => OBJECT, 'depth' => 0, 'walker' => '' ) );
$args = (object) $args;
$items = (array) wp_get_nav_menu_items_custom( $menu, $args );
if ( ! is_wp_error( $items ) ) {
wp_cache_set( $cache_key, $items, 'nav_menu_items', DAY_IN_SECONDS );
}
}
这里有两个关键点:
- 缓存机制:
wp_cache_get()
和wp_cache_set()
用于缓存菜单项,避免每次都查询数据库,提高性能。 wp_get_nav_menu_items_custom()
: 这个函数才是真正执行数据库查询和菜单项组装的核心函数。wp_parse_args
合并参数
咱们再深入看看 wp_get_nav_menu_items_custom()
:
function wp_get_nav_menu_items_custom( $menu, $args = array() ){
global $wpdb;
$menu_id = is_object( $menu ) ? $menu->term_id : (int) $menu;
$locations = get_nav_menu_locations();
$location_id = array_search( $menu_id, $locations, true );
$args = wp_parse_args( $args, array(
'output' => OBJECT,
'depth' => 0,
'walker' => '',
) );
$args = (object) $args;
$fields = array(
'posts.ID',
'posts.post_title',
'posts.post_name',
'posts.post_excerpt',
'posts.post_content',
'posts.post_author',
'posts.post_date',
'posts.post_date_gmt',
'posts.post_modified',
'posts.post_modified_gmt',
'posts.post_status',
'posts.post_type',
'posts.post_mime_type',
'posts.comment_status',
'posts.ping_status',
'posts.menu_order',
'posts.guid',
);
$fields = apply_filters( 'wp_get_nav_menu_items_fields', $fields, $menu, $args );
$query = "SELECT " . implode( ',', $fields ) . "
FROM {$wpdb->posts} AS posts
LEFT JOIN {$wpdb->term_relationships} AS tr ON (posts.ID = tr.object_id)
LEFT JOIN {$wpdb->term_taxonomy} AS tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)
WHERE tt.taxonomy = 'nav_menu'
AND tt.term_id = %d
AND posts.post_type = 'nav_menu_item'
ORDER BY posts.menu_order ASC";
$query = $wpdb->prepare( $query, $menu_id );
$posts = $wpdb->get_results( $query );
if ( ! $posts ) {
return array();
}
$menu_items = array();
_wp_menu_item_classes_by_context( $menu_items );
$post_ids = array();
foreach ( (array) $posts as $post ) {
$menu_items[] = $menu_item = WP_Nav_Menu_Item::get( $post );
$post_ids[] = $post->ID;
}
unset( $posts );
update_post_caches( $post_ids, 'nav_menu_item', true );
// Prime object caches for any custom links.
$linked_object_ids = array();
foreach ( $menu_items as $menu_item ) {
if ( ! empty( $menu_item->object_id ) ) {
$linked_object_ids[] = (int) $menu_item->object_id;
}
}
if ( $linked_object_ids ) {
$linked_object_ids = array_unique( $linked_object_ids );
_prime_post_caches( $linked_object_ids, false, true );
_prime_term_caches( $linked_object_ids, false, true );
}
return $menu_items;
}
这段代码主要做了以下几件事:
- 构建 SQL 查询: 根据菜单 ID,构建一个 SQL 查询语句,从
wp_posts
、wp_term_relationships
和wp_term_taxonomy
三个表里联合查询,获取菜单项的信息。 - 执行查询: 使用
$wpdb->get_results()
函数执行 SQL 查询,获取查询结果。 - 创建
WP_Nav_Menu_Item
对象: 循环查询结果,为每个菜单项创建一个WP_Nav_Menu_Item
对象。这个对象包含了菜单项的所有信息,比如 ID、标题、URL、父菜单 ID 等等。
3. 构建层级结构:把“小花朵”搭成“花架子”
到目前为止,我们已经拿到了所有菜单项,但是它们还是散乱的,没有层级关系。接下来,函数就要把这些菜单项按照父子关系,构建成一个树状结构。
这个过程并没有直接在 wp_get_nav_menu_items()
函数里完成,而是需要在外部调用函数后,自己处理。wp_get_nav_menu_items()
返回的是一个扁平的菜单项数组。
下面是一个构建层级结构的示例代码:
<?php
function build_menu_tree( array &$elements, $parent_id = 0 ) {
$branch = array();
foreach ($elements as $element) {
if ($element->menu_item_parent == $parent_id) {
$children = build_menu_tree($elements, $element->ID);
if ($children) {
$element->children = $children;
}
$branch[$element->ID] = $element;
unset($elements[$element->ID]);
}
}
return $branch;
}
$menu_name = 'my-custom-menu';
$menu_items = wp_get_nav_menu_items( $menu_name );
if ($menu_items) {
$menu_tree = build_menu_tree($menu_items, 0); // 0 代表顶级菜单项
function display_menu_tree($menu_tree) {
echo '<ul>';
foreach ($menu_tree as $menu_item) {
echo '<li><a href="' . $menu_item->url . '">' . $menu_item->title . '</a>';
if (isset($menu_item->children) && !empty($menu_item->children)) {
display_menu_tree($menu_item->children); // 递归输出子菜单
}
echo '</li>';
}
echo '</ul>';
}
display_menu_tree($menu_tree);
} else {
echo '菜单不见啦!';
}
?>
这段代码定义了两个函数:
build_menu_tree()
: 这是一个递归函数,它接收菜单项数组和一个父菜单 ID,然后根据menu_item_parent
属性,构建一个树状结构。display_menu_tree()
: 这也是一个递归函数,它接收一个菜单树,然后按照 HTML 列表的格式输出菜单。
WP_Nav_Menu_Item
对象详解
咱们前面提到了 WP_Nav_Menu_Item
对象,它包含了菜单项的所有信息。下面是这个对象的一些常用属性:
属性名 | 类型 | 描述 |
---|---|---|
ID |
int | 菜单项的 ID(对应 wp_posts 表的 ID 字段) |
post_author |
string | 作者 |
post_date |
string | 发布时间 |
post_title |
string | 菜单项的标题 |
post_status |
string | 菜单项状态 |
menu_order |
int | 菜单项的排序 |
post_type |
string | 菜单项的类型 (nav_menu_item) |
object_id |
int | 关联对象的 ID (例如,文章 ID、分类 ID) |
object |
string | 关联对象的类型 (例如,post、category、custom) |
type |
string | 菜单项的类型 (例如,post_type、taxonomy、custom) |
type_label |
string | 菜单项类型的标签 |
url |
string | 菜单项的 URL |
title |
string | 菜单项的标题 |
target |
string | 链接的目标 (例如,_blank) |
attr_title |
string | 链接的 title 属性 |
description |
string | 菜单项的描述 |
classes |
array | 菜单项的 CSS 类名 |
xfn |
string | 链接的 XFN 关系 |
menu_item_parent |
string | 父菜单项的 ID |
三、高级用法:自定义菜单项的输出
WordPress 允许我们自定义菜单项的输出,比如添加自定义的 CSS 类名、修改 URL 等等。这可以通过使用 wp_nav_menu_objects
过滤器来实现。
<?php
add_filter( 'wp_nav_menu_objects', 'my_custom_nav_menu_classes', 10, 2 );
function my_custom_nav_menu_classes( $items, $args ) {
foreach ( $items as $item ) {
// 在这里添加你的自定义逻辑
if ( $item->object == 'category' ) {
$item->classes[] = 'category-item'; // 给分类菜单项添加 category-item 类名
}
}
return $items;
}
?>
这段代码使用了 wp_nav_menu_objects
过滤器,它会在 wp_get_nav_menu_items()
函数返回菜单项之后,但在输出菜单之前被调用。我们可以在这个过滤器里修改菜单项的属性,比如添加 CSS 类名。
四、总结:wp_get_nav_menu_items()
的“前世今生”
总的来说,wp_get_nav_menu_items()
函数的作用就是从数据库里获取菜单项,并把它们转换成 WP_Nav_Menu_Item
对象。它本身不负责构建层级结构,需要我们自己编写代码来实现。
这个函数的核心逻辑包括:
- 参数验证: 确保菜单标识符有效。
- 数据库查询: 从
wp_posts
、wp_term_relationships
和wp_term_taxonomy
三个表里联合查询菜单项。 - 对象创建: 为每个菜单项创建一个
WP_Nav_Menu_Item
对象。 - 缓存机制: 使用
wp_cache_get()
和wp_cache_set()
缓存菜单项,提高性能。
理解了 wp_get_nav_menu_items()
的工作原理,我们就能更好地控制 WordPress 的菜单系统,实现各种各样的自定义需求。
好了,今天的“源码扒皮”就到这里,希望大家有所收获!如果还有什么疑问,欢迎随时提问。下次再见!