解析 WordPress `get_adjacent_post()` 函数的源码:如何查询相邻文章的 ID,并解释其性能。

各位朋友,大家好!今天咱们来聊聊WordPress里一个挺实用但又容易被忽略的小可爱:get_adjacent_post()。 别看它名字平平无奇,用对了地方,能给你的WordPress站点带来不少便利。 咱们今天就扒开它的源码,看看它到底是怎么找到相邻的文章,以及它背后的性能考量。

一、初识get_adjacent_post():它是干啥的?

简单来说,get_adjacent_post()函数的作用就是找到当前文章的上一篇或下一篇文章。 想象一下,你在浏览一篇博客文章,文章末尾通常会有“上一篇”、“下一篇”的链接。 这个函数就是用来获取这些链接的目标文章信息的。

基本语法:

<?php
get_adjacent_post( bool $in_same_term = false, string $excluded_terms = '', bool $previous = true, string $taxonomy = 'category' ) : WP_Post|null;
?>
  • $in_same_term (bool, 可选): 是否限制在同一个分类/标签下查找相邻文章。 默认值: false
  • $excluded_terms (string, 可选): 排除的分类/标签 ID 列表,多个 ID 用逗号分隔。 默认值: ''
  • $previous (bool, 可选): true表示查找上一篇文章,false表示查找下一篇文章。 默认值: true
  • $taxonomy (string, 可选): 分类/标签的名称(slug)。 默认值: 'category'

返回值: 成功时返回WP_Post对象,失败时返回null

二、源码剖析:它怎么找到“邻居”的?

现在咱们来深入源码,看看get_adjacent_post()是如何实现的。 别担心,我会尽量用大白话解释,不会让你觉得枯燥。

首先,找到wp-includes/link-template.php文件,里面藏着get_adjacent_post()函数的真身。 (以下代码省略了部分注释和错误处理,只保留核心逻辑)

function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
    global $post;

    if ( empty( $post ) ) {
        return null;
    }

    $current_post_date = $post->post_date;

    $join = '';
    $where = '';
    $op = $previous ? '<' : '>';
    $order = $previous ? 'DESC' : 'ASC';

    if ( $in_same_term && ! empty( $taxonomy ) ) {
        if ( ! taxonomy_exists( $taxonomy ) ) {
            return null;
        }

        $term_object = get_term_by( 'slug', $excluded_terms, $taxonomy );
        if (is_wp_error($term_object))
        {
            $excluded_terms = '';
        }

        if (!empty($excluded_terms)){
            $excluded_terms = wp_parse_id_list($excluded_terms);

            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id ";
            $where .= " AND tt.taxonomy = '" . esc_sql($taxonomy) . "' AND tt.term_id NOT IN (" . implode(',', array_map('absint', $excluded_terms)) . ") ";
        }
        else {
            $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON p.ID = tr.object_id INNER JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id ";
            $where .= " AND tt.taxonomy = '" . esc_sql($taxonomy) . "' ";

            $term_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
            $term_ids = array_map( 'intval', $term_ids );

            $where .= " AND tt.term_id IN (" . implode( ',', $term_ids ) . ") ";
        }

    }

    $where .= " AND p.post_type = '" . esc_sql( $post->post_type ) . "'";
    $where .= " AND p.post_status = 'publish'";

    $where .= " AND p.post_date $op '" . esc_sql( $current_post_date ) . "'";

    $sort = "ORDER BY p.post_date $order LIMIT 1";

    $query = "SELECT p.* FROM {$wpdb->posts} AS p $join WHERE 1=1 $where $sort";

    $adjacent_post = $wpdb->get_row($query);

    if ( ! empty( $adjacent_post ) ) {
        $adjacent_post = sanitize_post( $adjacent_post );
        return $adjacent_post;
    }

    return null;
}

代码解读:

  1. 准备工作:

    • 首先,它会检查全局变量$post是否为空,如果为空,说明当前没有文章信息,直接返回null
    • 获取当前文章的发布时间$current_post_date,这个时间是后续查找相邻文章的关键。
    • 根据$previous的值,确定查找方向(上一篇还是下一篇),以及对应的比较运算符(<>)和排序方式(DESCASC)。
  2. 分类/标签限制($in_same_term):

    • 如果$in_same_termtrue,表示需要在同一个分类/标签下查找。
    • 它会根据$taxonomy的值,构建SQL查询的JOINWHERE子句,将文章与分类/标签关联起来。
    • 如果提供了$excluded_terms,会将这些分类/标签排除在外。
    • 如果没有提供 $excluded_terms,会获取当前文章所属的分类/标签,并将其作为查询条件。
  3. 构建SQL查询:

    • 它会构建一个完整的SQL查询语句,从wp_posts表中查询符合条件的文章。
    • WHERE子句中包含了以下条件:
      • 文章类型必须与当前文章相同(p.post_type = '" . esc_sql( $post->post_type ) . "')。
      • 文章状态必须是已发布(p.post_status = 'publish')。
      • 文章发布时间必须早于/晚于当前文章(p.post_date $op '" . esc_sql( $current_post_date ) . "')。
    • ORDER BY子句根据$previous的值,按照发布时间升序或降序排序。
    • LIMIT 1限制只返回一条结果,即相邻的文章。
  4. 执行查询并返回结果:

    • 使用$wpdb->get_row()执行SQL查询,获取查询结果。
    • 如果查询结果不为空,对结果进行安全过滤(sanitize_post()),然后返回WP_Post对象。
    • 如果查询结果为空,返回null

用表格总结一下:

参数 作用
$in_same_term 是否限制在同一分类/标签下查找。如果为true,会根据$taxonomy$excluded_terms构建更复杂的SQL查询,以确保只返回同一分类/标签下的文章。
$excluded_terms 排除的分类/标签 ID 列表。如果$in_same_termtrue,并且提供了$excluded_terms,会将其用于构建SQL查询的WHERE子句,排除指定的分类/标签。
$previous 查找方向。true表示查找上一篇文章,false表示查找下一篇文章。这会影响SQL查询中的比较运算符(<>)和排序方式(DESCASC)。
$taxonomy 分类/标签的名称。如果$in_same_termtrue,会根据$taxonomy的值构建SQL查询,将文章与指定的分类/标签关联起来。
SQL WHERE子句 用于过滤文章,确保只返回符合条件的文章。它包含了文章类型、文章状态、发布时间等条件,以及可能的分类/标签限制。
SQL ORDER BY子句 用于排序文章,确保返回的是最近的上一篇或下一篇文章。排序方式取决于$previous的值。
SQL LIMIT子句 用于限制返回结果的数量,确保只返回一条结果,即相邻的文章。

三、性能考量:它快不快?

get_adjacent_post()的性能取决于多个因素,包括:

  • 数据量: 文章数量越多,查询时间越长。
  • 分类/标签限制: 如果启用了$in_same_term,查询会更复杂,因为需要关联wp_term_relationshipswp_term_taxonomy表。
  • 索引: 数据库索引对查询性能至关重要。 确保wp_posts表的post_datepost_typepost_status等字段有索引。 如果使用了分类/标签限制,wp_term_relationships表的object_idwp_term_taxonomy表的term_id也应该有索引。

性能优化建议:

  1. 合理使用缓存:get_adjacent_post()的结果进行缓存,避免重复查询。 WordPress有很多缓存插件可以使用,比如WP Super CacheW3 Total Cache等。
  2. 优化数据库索引: 确保相关的数据库字段有索引。 可以使用SHOW INDEX FROM wp_posts;等命令查看索引情况。
  3. 避免过度使用分类/标签限制: 如果不需要,尽量不要启用$in_same_term,因为这会增加查询复杂度。
  4. 使用更高效的查询: 如果性能是关键,可以考虑自己编写SQL查询,针对特定场景进行优化。 例如,可以使用post_modified字段代替post_date,或者使用transient API手动缓存结果。

四、实际应用:怎么用它?

有了理论基础,咱们来看看get_adjacent_post()在实际开发中怎么用。

示例1:显示上一篇文章的链接

<?php
$prev_post = get_adjacent_post( false, '', true ); // 获取上一篇文章
if ( ! empty( $prev_post ) ) {
  echo '<a href="' . get_permalink( $prev_post->ID ) . '">' . esc_html( $prev_post->post_title ) . '</a>';
} else {
  echo '没有上一篇了';
}
?>

示例2:显示同一分类下的下一篇文章的链接

<?php
$next_post = get_adjacent_post( true, '', false ); // 获取同一分类下的下一篇文章
if ( ! empty( $next_post ) ) {
  echo '<a href="' . get_permalink( $next_post->ID ) . '">' . esc_html( $next_post->post_title ) . '</a>';
} else {
  echo '没有下一篇了';
}
?>

示例3:排除特定分类的上一篇文章链接
假设要排除ID为5的分类

<?php
$prev_post = get_adjacent_post( true, '5', true ); // 获取同一分类下的上一篇文章,排除ID为5的分类
if ( ! empty( $prev_post ) ) {
  echo '<a href="' . get_permalink( $prev_post->ID ) . '">' . esc_html( $prev_post->post_title ) . '</a>';
} else {
  echo '没有上一篇了';
}
?>

示例4:排除多个特定分类的下一篇文章链接
假设要排除ID为5和10的分类

<?php
$next_post = get_adjacent_post( true, '5,10', false ); // 获取同一分类下的下一篇文章,排除ID为5和10的分类
if ( ! empty( $next_post ) ) {
  echo '<a href="' . get_permalink( $next_post->ID ) . '">' . esc_html( $next_post->post_title ) . '</a>';
} else {
  echo '没有下一篇了';
}
?>

五、高级技巧:玩转get_adjacent_post()

  1. 自定义查询: 如果get_adjacent_post()的功能不能满足你的需求,可以自己编写SQL查询,实现更复杂的逻辑。 例如,可以根据自定义字段的值查找相邻文章。

  2. 使用get_previous_post_link()get_next_post_link() 这两个函数是get_adjacent_post()的封装,可以直接生成上一篇和下一篇文章的链接。 它们接受的参数与get_adjacent_post()类似。

  3. WP_Query结合: 可以将get_adjacent_post()的结果与WP_Query结合使用,实现更灵活的文章列表显示。

六、总结:

get_adjacent_post()是一个简单但实用的函数,可以方便地获取相邻文章的信息。 理解它的源码和性能特点,可以帮助你更好地使用它,并针对特定场景进行优化。 希望今天的讲解能让你对get_adjacent_post()有更深入的了解。 以后在WordPress开发中,就能更加得心应手啦!

谢谢大家! 下次有机会再和大家分享其他的WordPress技术。

发表回复

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