探讨 `wp_get_nav_menu_items()` 函数的源码,分析它如何查询数据库并构建出菜单项的层级结构。

各位观众老爷们,大家好!我是你们的导游,今天咱们要一起深入 WordPress 的腹地,扒一扒 wp_get_nav_menu_items() 这个函数的底裤,看看它到底是怎么把菜单项从数据库里捞出来,再像搭积木一样,构建出我们看到的层级结构的。

准备好了吗?系好安全带,发车!

第一站:wp_get_nav_menu_items() 的入口

首先,我们得找到入口。wp_get_nav_menu_items() 函数的定义在 wp-includes/nav-menu.php 文件里。 它的核心作用就是:根据给定的菜单 ID 或菜单对象,从数据库中获取菜单项,并返回一个包含所有菜单项的数组。

函数签名如下:

/**
 * Retrieve all menu items.
 *
 * @since 3.0.0
 *
 * @param mixed $menu          Menu ID, slug, name, or object.
 * @param array $args {
 *     Optional. Array of get_posts() arguments.
 *
 *     @type string $order      How to order navigation menu items. Accepts 'ASC', 'DESC'.
 *                              Default 'ASC'.
 *     @type string $orderby    Field to order navigation menu items by. Accepts 'id',
 *                              'menu_order', 'title'. Default 'menu_order'.
 *     @type bool   $output     Type of output. Accepts ARRAY_A, ARRAY_N, OBJECT, OBJECT_K.
 *                              Default OBJECT.
 *     @type string $output_key The key to use for the 'OBJECT_K' output format.
 *                              Default 'ID'.
 *     @type bool   $update_post_term_cache Whether to update the post term cache.
 *                              Default true.
 * }
 * @return WP_Post[]|false Array of menu items, false if menu not found.
 */
function wp_get_nav_menu_items( $menu, $args = array() ) {
  // ... 函数主体
}

简单来说,你需要给它提供一个菜单的标识(可以是 ID,slug,name 或者对象),它就会返回一个包含菜单项的 WP_Post 对象数组。

第二站:参数处理和安全检查

进入函数内部,首先会进行一些参数处理和安全检查,确保传入的 $menu 是有效的,并且参数 $args 也符合预期。

  $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;

  $defaults = array(
    'order'                  => 'ASC',
    'orderby'                => 'menu_order',
    'output'                 => OBJECT,
    'output_key'             => 'ID',
    'update_post_term_cache' => true,
  );

  $args = wp_parse_args( $args, $defaults );

这里先用 wp_get_nav_menu_object() 函数来获取菜单对象,如果获取失败,直接返回 false。 接着提取菜单的 ID。然后,使用 wp_parse_args() 函数将传入的 $args 参数与默认参数合并,保证所有需要的参数都有值。

第三站:构建查询语句

接下来,重头戏来了!我们要构建查询语句,从数据库中捞取菜单项。 wp_get_nav_menu_items() 函数的核心就在于构建正确的 WP_Query 参数,以获取所需的菜单项。

  $args['post_type'] = 'nav_menu_item';
  $args['posts_per_page'] = -1; // Retrieve all menu items.
  $args['tax_query'] = array(
    array(
      'taxonomy' => 'nav_menu',
      'field'    => 'term_id',
      'terms'    => $menu_id,
    ),
  );

  $q = new WP_Query( $args );
  $posts = $q->get_posts();

这里做了几件事:

  1. $args['post_type'] = 'nav_menu_item';: 指定要查询的 post 类型为 nav_menu_item,这是一种特殊的 post 类型,用于存储菜单项。
  2. $args['posts_per_page'] = -1;: 设置每页显示的 post 数量为 -1,表示获取所有菜单项。
  3. $args['tax_query']: 设置分类查询条件。nav_menu 是一个分类,term_id 是分类的 ID,$menu_id 就是我们之前获取的菜单 ID。通过这个分类查询,我们可以获取指定菜单下的所有菜单项。
  4. $q = new WP_Query( $args );: 使用 WP_Query 类创建一个新的查询对象,传入 $args 参数。
  5. $posts = $q->get_posts();: 执行查询,获取查询结果,返回一个包含 WP_Post 对象的数组。

第四站:处理查询结果

拿到菜单项的数组后,还需要进行一些处理,比如更新 post 的 term cache。

  if ( $args['update_post_term_cache'] ) {
    wp_update_post_term_cache( $posts );
  }

  return $posts;

wp_update_post_term_cache() 函数用于更新 post 的 term cache,提高后续访问 post 的 term 的效率。

到这里,wp_get_nav_menu_items() 函数就完成了它的使命,把菜单项从数据库里捞了出来,并返回了一个包含 WP_Post 对象的数组。

第五站:构建层级结构 (重点!)

虽然 wp_get_nav_menu_items() 函数返回了菜单项,但这些菜单项是扁平的,没有层级关系。 要构建层级结构,我们需要自己动手。 WordPress 提供了 wp_get_nav_menu_items() 函数返回的菜单项数组,然后开发者可以根据 menu_item_parent 字段来构建层级关系。 下面是一个示例函数,用于构建菜单项的层级结构:

/**
 * Build a hierarchical array of menu items.
 *
 * @param array $menu_items An array of menu items.
 * @return array A hierarchical array of menu items.
 */
function build_menu_tree( $menu_items ) {
  $parents = array();
  $children = array();

  // Separate menu items into parents and children.
  foreach ( $menu_items as $item ) {
    if ( 0 == $item->menu_item_parent ) {
      $parents[$item->ID] = $item;
    } else {
      $children[$item->menu_item_parent][] = $item;
    }
  }

  // Build the tree.
  foreach ( $parents as $parent_id => $parent ) {
    if ( isset( $children[$parent_id] ) ) {
      $parent->children = $children[$parent_id];
    } else {
      $parent->children = array();
    }
  }

  return $parents;
}

这个函数的核心思想是:

  1. 分离父节点和子节点: 遍历菜单项数组,将 menu_item_parent 为 0 的菜单项放入 $parents 数组,表示它们是顶级菜单项。 将 menu_item_parent 不为 0 的菜单项放入 $children 数组,表示它们是子菜单项。
  2. 构建树形结构: 遍历 $parents 数组,对于每个父节点,检查 $children 数组中是否存在以该父节点 ID 为键的子节点数组。 如果存在,则将该子节点数组赋值给父节点的 children 属性。 如果不存在,则将父节点的 children 属性赋值为空数组。

代码示例

// 获取菜单项
$menu_items = wp_get_nav_menu_items( 'main-menu' );

// 构建层级结构
$menu_tree = build_menu_tree( $menu_items );

// 输出菜单
function display_menu_tree( $menu_items ) {
  if ( ! empty( $menu_items ) ) {
    echo '<ul>';
    foreach ( $menu_items as $item ) {
      echo '<li><a href="' . esc_url( $item->url ) . '">' . esc_html( $item->title ) . '</a>';
      if ( ! empty( $item->children ) ) {
        display_menu_tree( $item->children );
      }
      echo '</li>';
    }
    echo '</ul>';
  }
}

display_menu_tree( $menu_tree );

这段代码首先使用 wp_get_nav_menu_items() 函数获取名为 "main-menu" 的菜单项。 然后使用 build_menu_tree() 函数构建菜单项的层级结构。 最后使用 display_menu_tree() 函数递归地输出菜单项,生成 HTML 代码。

WP_Post 对象的重要属性

在构建层级结构和输出菜单时,我们需要用到 WP_Post 对象的以下属性:

属性名 描述
ID 菜单项的 ID。
title 菜单项的标题。
url 菜单项的 URL。
menu_order 菜单项的排序顺序。
menu_item_parent 菜单项的父菜单项 ID。 如果为 0,则表示该菜单项是顶级菜单项。
object 菜单项指向的对象类型,例如 ‘page’、’post’、’category’ 等。
object_id 菜单项指向的对象的 ID。 例如,如果 object 为 ‘page’,则 object_id 为页面的 ID。
type 菜单项的类型,例如 ‘post_type’、’taxonomy’、’custom’ 等。
type_label 菜单项类型的标签,例如 ‘页面’、’文章’、’分类目录’ 等。
target 链接的目标属性,例如 ‘_blank’,用于在新窗口中打开链接。
attr_title 链接的 title 属性,用于鼠标悬停时显示提示信息。
classes 链接的 CSS 类名,可以添加自定义的 CSS 样式。
xfn 链接的 xfn 属性,用于描述链接的关系。

总结

wp_get_nav_menu_items() 函数是 WordPress 获取菜单项的核心函数。 它通过构建 WP_Query 查询语句,从数据库中获取菜单项,并返回一个包含 WP_Post 对象的数组。 要构建菜单项的层级结构,需要遍历菜单项数组,根据 menu_item_parent 属性将菜单项组织成树形结构。 然后,可以递归地输出菜单项,生成 HTML 代码。

额外提示

  • 缓存:WordPress 会缓存菜单项,以提高性能。 如果你修改了菜单,但页面没有立即更新,可以尝试清除 WordPress 的缓存。
  • 过滤器wp_get_nav_menu_items() 函数提供了多个过滤器,允许你自定义菜单项的查询和输出。 例如,可以使用 wp_nav_menu_objects 过滤器来修改菜单项的属性。

结束语

好了,今天的讲座就到这里。 希望通过今天的讲解,大家对 wp_get_nav_menu_items() 函数有了更深入的了解。 记住,理解底层原理,才能更好地驾驭 WordPress!

下次再见!

发表回复

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