探究 WordPress `wp_nav_menu()` 函数源码:如何通过 `wp_get_nav_menu_items()` 获取菜单项。

各位观众老爷们,晚上好!欢迎来到今晚的“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;
}

代码有点长,咱们一段一段来分析:

  1. 参数过滤:

    $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() 函数的参数。 这提供了一个扩展点,可以根据不同的需求定制菜单项的获取方式。

  2. 获取菜单对象:

    $menu_object = wp_get_nav_menu_object( $menu );

    wp_get_nav_menu_object() 函数根据传入的菜单名称、ID 或 slug,获取对应的菜单对象(WP_Term 对象)。 这个对象包含了菜单的各种信息,比如菜单的 ID、名称、slug 等。

  3. 缓存检查:

    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')从缓存中查找菜单项。 如果缓存中存在,就直接返回缓存的菜单项,避免重复查询数据库,提高性能。

  4. 从数据库获取菜单项:

    $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_typenav_menu_item,并且属于指定菜单的(通过 tax_query 指定)所有文章。

  5. 处理菜单项类名:

    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 数组缓存起来,以便下次使用。

  6. 菜单项过滤:

    if ( $menu_items ) {
        $menu_items = apply_filters( 'wp_get_nav_menu_items', $menu_items, $args );
    }

    这段代码再次使用 apply_filters() 函数对菜单项进行过滤。 wp_get_nav_menu_items 是另一个过滤器钩子,允许开发者修改菜单项的内容。 这提供了一个更强大的扩展点,可以根据不同的需求修改菜单项的标题、URL、CSS 类名等等。

  7. 返回菜单项:

    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-itemcurrent-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(菜单项数组)和 $argswp_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 大牛!

今天的节目就到这里,感谢大家的收看,咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注