分析 WordPress `wp_get_nav_menu_items()` 函数的源码:如何查询菜单项并构建其层级结构。

各位观众老爷们,晚上好!欢迎来到今天的 WordPress 源码解剖现场。今晚咱们要扒一扒 WordPress 里一个非常重要的函数—— wp_get_nav_menu_items(),看看它是如何把一堆菜单项从数据库里捞出来,然后像叠积木一样,给它们搭出一个有模有样的层级结构的。准备好了吗?咱们开始咯!

1. 初探 wp_get_nav_menu_items():门面担当

首先,让我们简单了解一下 wp_get_nav_menu_items() 函数的作用。简单来说,它的职责就是根据指定的菜单 ID 或菜单名称,从数据库中获取该菜单的所有项目,并将它们整理成一个可用的数组,方便我们在主题中使用。这个函数就像一个餐厅的服务员,你告诉他你要哪个菜单,他就能把菜品(菜单项)给你端上来。

它的函数原型如下:

/**
 * Retrieve all menu items.
 *
 * @since 3.0.0
 *
 * @param mixed   $menu          Menu ID, slug, or name.
 * @param array   $args          Optional. Array of get_posts() arguments. Default empty array.
 * @return array|false Array of menu items, false if menu not found.
 */
function wp_get_nav_menu_items( $menu, $args = array() ) {
    // ...函数实现...
}

从注释可以看出,$menu 参数接受菜单的 ID、别名(slug)或名称,而 $args 参数则允许我们传递一些 get_posts() 函数的参数,以进一步定制查询。

2. 寻根溯源:参数校验与准备工作

进入函数内部,首先看到的是一连串的参数校验和准备工作。这段代码的主要目的是确保我们提供的 $menu 参数有效,并为后续的查询做好准备。

    $menu_object = wp_get_nav_menu_object( $menu );

    if ( ! $menu_object ) {
        return false;
    }

    $menu_id = is_object( $menu_object ) ? $menu_object->term_id : (int) $menu_object;

这里,wp_get_nav_menu_object() 函数负责根据我们提供的 $menu 参数,获取对应的菜单对象。如果找不到菜单,函数会直接返回 false,任务结束。否则,我们从菜单对象中提取出菜单的 ID,方便后续的数据库查询。

3. 数据库查询:大海捞针

接下来,就是核心的数据库查询环节。WordPress 使用 WP_Query 类来执行查询,从 wp_posts 表中检索所有与指定菜单关联的菜单项。

    $defaults = array(
        'posts_per_page' => -1,
        'orderby'        => 'menu_order',
        'order'          => 'ASC',
        'post_type'      => 'nav_menu_item',
        'post_status'    => 'publish',
        'output'         => ARRAY_A,
        'output_key'     => 'menu_order',
        'nopaging'       => true,
        'update_post_term_cache' => false,
    );

    $args = wp_parse_args( $args, $defaults );
    $args['menu'] = $menu_id;

    $posts = get_posts( $args );

这段代码首先定义了一组默认的查询参数,包括每页显示的条目数(设置为 -1 表示显示所有条目)、排序方式、排序顺序、文章类型(设置为 nav_menu_item)、文章状态等。然后,它使用 wp_parse_args() 函数将我们传入的 $args 参数与默认参数合并,确保所有必要的参数都已设置。最后,它将菜单 ID 添加到 $args 数组中,并使用 get_posts() 函数执行查询。

这里的 get_posts() 函数是 WP_Query 的一个封装函数,它简化了查询操作,并返回一个包含所有菜单项的数组。

4. 数据处理:化零为整

查询结果返回的是一个包含所有菜单项的数组,但这些菜单项之间并没有明确的层级关系。因此,我们需要对这些数据进行处理,才能构建出正确的菜单结构。

    if ( ! $posts ) {
        return array();
    }

    $menu_items = array();
    $_menu_items = array();

    foreach ( (array) $posts as $post ) {
        $menu_item = $post;
        $menu_item->ID = (int) $menu_item->ID;
        $menu_items[] = $menu_item;
        $_menu_items[$menu_item->ID] = $menu_item;
    }

    unset($posts);

这段代码首先检查查询结果是否为空。如果为空,则直接返回一个空数组。否则,它会遍历查询结果,并将每个菜单项转换为一个对象,并将其添加到 $menu_items$_menu_items 数组中。$_menu_items 数组使用菜单项的 ID 作为键,方便后续的查找。

5. 构建层级:抽丝剥茧

接下来,就是构建菜单层级的核心代码。这段代码遍历所有菜单项,并根据 menu_item_parent 属性将它们组织成一个树状结构。

    foreach ( (array) $menu_items as $menu_item ) {
        if ( 0 == $menu_item->menu_item_parent ) {
            $sorted_menu_items[$menu_item->ID] = $menu_item;
        } else {
            if ( isset( $_menu_items[$menu_item->menu_item_parent] ) ) {
                $_menu_items[$menu_item->menu_item_parent]->children[] = $menu_item;
            }
        }
    }

    unset($menu_items, $_menu_items);

这段代码首先创建一个 $sorted_menu_items 数组,用于存储排序后的菜单项。然后,它遍历所有菜单项,并检查 menu_item_parent 属性。如果该属性为 0,则表示该菜单项是一个顶级菜单项,直接将其添加到 $sorted_menu_items 数组中。否则,它会查找该菜单项的父菜单项,并将该菜单项添加到父菜单项的 children 数组中。

这里的 menu_item_parent 属性存储的是父菜单项的 ID。通过这个属性,我们可以确定菜单项之间的父子关系。

6. 排序:井然有序

在构建完菜单层级后,我们需要对菜单项进行排序,以确保它们按照正确的顺序显示。

    if ( isset( $sorted_menu_items ) ) {
        $menu_items = $sorted_menu_items;
        unset($sorted_menu_items);
    }

    return $menu_items;

这段代码将 $sorted_menu_items 数组赋值给 $menu_items 数组,并将 $sorted_menu_items 数组销毁。最终,函数返回 $menu_items 数组,其中包含了所有菜单项及其层级关系。

7. 代码分析:庖丁解牛

为了更深入地理解 wp_get_nav_menu_items() 函数的实现,我们可以将其分解为几个关键步骤,并使用表格进行总结。

步骤 描述 代码示例
1. 参数校验 检查 $menu 参数是否有效,并获取菜单 ID。 php $menu_object = wp_get_nav_menu_object( $menu ); if ( ! $menu_object ) { return false; } $menu_id = is_object( $menu_object ) ? $menu_object->term_id : (int) $menu_object;
2. 数据库查询 使用 WP_Query 类查询所有与指定菜单关联的菜单项。 php $args = wp_parse_args( $args, $defaults ); $args['menu'] = $menu_id; $posts = get_posts( $args );
3. 数据处理 将查询结果转换为对象,并存储到数组中。 php foreach ( (array) $posts as $post ) { $menu_item = $post; $menu_item->ID = (int) $menu_item->ID; $menu_items[] = $menu_item; $_menu_items[$menu_item->ID] = $menu_item; }
4. 构建层级 根据 menu_item_parent 属性将菜单项组织成树状结构。 php foreach ( (array) $menu_items as $menu_item ) { if ( 0 == $menu_item->menu_item_parent ) { $sorted_menu_items[$menu_item->ID] = $menu_item; } else { if ( isset( $_menu_items[$menu_item->menu_item_parent] ) ) { $_menu_items[$menu_item->menu_item_parent]->children[] = $menu_item; } } }
5. 排序 对菜单项进行排序,以确保它们按照正确的顺序显示。 php if ( isset( $sorted_menu_items ) ) { $menu_items = $sorted_menu_items; unset($sorted_menu_items); }
6. 返回结果 返回包含所有菜单项及其层级关系的数组。 php return $menu_items;

8. 举个栗子:实战演练

为了更好地理解 wp_get_nav_menu_items() 函数的使用,我们可以编写一个简单的代码示例,演示如何获取菜单项并显示它们。

<?php
$menu_name = 'main-menu'; // 替换为你的菜单名称
$menu_items = wp_get_nav_menu_items( $menu_name );

if ( $menu_items ) {
    echo '<ul>';
    foreach ( $menu_items as $menu_item ) {
        echo '<li><a href="' . esc_url( $menu_item->url ) . '">' . esc_html( $menu_item->title ) . '</a>';
        if ( isset( $menu_item->children ) && ! empty( $menu_item->children ) ) {
            echo '<ul>';
            foreach ( $menu_item->children as $child_item ) {
                echo '<li><a href="' . esc_url( $child_item->url ) . '">' . esc_html( $child_item->title ) . '</a></li>';
            }
            echo '</ul>';
        }
        echo '</li>';
    }
    echo '</ul>';
} else {
    echo '菜单未找到。';
}
?>

这段代码首先指定要获取的菜单名称,然后调用 wp_get_nav_menu_items() 函数获取菜单项。如果菜单项存在,则遍历菜单项,并使用 <ul><li> 标签将它们显示出来。如果菜单项有子菜单,则递归地显示子菜单。

9. 注意事项:避坑指南

在使用 wp_get_nav_menu_items() 函数时,需要注意以下几点:

  • 菜单名称/ID 必须正确: 如果你提供的菜单名称或 ID 不正确,函数将返回 false
  • 缓存: wp_get_nav_menu_items() 函数会缓存查询结果,以提高性能。如果你修改了菜单,需要清除缓存才能看到最新的结果。
  • 性能: 如果你的菜单非常大,查询所有菜单项可能会影响性能。你可以考虑使用分页或懒加载等技术来优化性能。
  • 安全: 在显示菜单项时,务必对 URL 和标题进行转义,以防止 XSS 攻击。使用 esc_url()esc_html() 函数可以有效地防止这些攻击。

10. 总结:举一反三

wp_get_nav_menu_items() 函数是 WordPress 中一个非常重要的函数,它负责从数据库中获取菜单项并构建其层级结构。通过深入分析该函数的源码,我们可以更好地理解 WordPress 的内部机制,并能够更好地使用该函数来构建自定义的菜单。

希望今天的讲座对你有所帮助。如果你有任何问题,欢迎随时提问。下次再见!

发表回复

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