大家好,欢迎来到今天的WordPress源码解剖课!今天我们要聊聊一个WordPress开发者经常会用到的函数——get_adjacent_post()
,它能帮助我们找到文章的前一篇和后一篇。别看它名字简单,里面的门道可不少,搞清楚了能让你对WordPress的查询机制理解更上一层楼。
一、get_adjacent_post()
:你是谁?从哪里来?要到哪里去?
首先,咱们得认识一下get_adjacent_post()
这个函数。简单来说,它的作用就是获取与当前文章相邻的文章对象(可以理解为包含了文章所有信息的数组)。相邻的定义可以是:
- 基于发布日期:这是最常见的用法,按照文章的发布时间先后顺序来确定前一篇和后一篇。
- 基于分类目录:只在同一个分类目录下的文章中寻找相邻文章。
这个函数藏身于wp-includes/link-template.php
文件中。
二、源码解剖:一层一层扒开它的心
我们先来看看get_adjacent_post()
函数的原型:
function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category', $previous = true, $excluded_categories = '' ) {
global $post;
if ( empty( $post ) ) {
return null;
}
$post = get_post( $post ); // Ensure $post is a WP_Post object.
return _get_adjacent_post( $in_same_term, $excluded_terms, $taxonomy, $post, $previous, $excluded_categories );
}
看起来挺唬人,但其实它只是个“代理”,真正干活的是_get_adjacent_post()
。咱们来看看这个“真身”:
function _get_adjacent_post( $in_same_term = false, $excluded_terms = '', $taxonomy = 'category', $post, $previous = true, $excluded_categories = '' ) {
global $wpdb;
if ( empty( $post ) ) {
return null;
}
$op = $previous ? '<' : '>';
$order = $previous ? 'DESC' : 'ASC';
$join = '';
$where = '';
if ( $in_same_term ) {
$term_object = get_the_terms( $post->ID, $taxonomy );
if ( is_array( $term_object ) ) {
$term_ids = wp_list_pluck( $term_object, 'term_id' );
$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 = '' . $taxonomy . '' AND tt.term_id IN (' . implode( ',', array_map( 'intval', $term_ids ) ) . ')';
}
}
if ( ! empty( $excluded_terms ) ) {
if ( ! is_array( $excluded_terms ) ) {
$excluded_terms = preg_split( '/[s,]+/', $excluded_terms );
}
$excluded_terms = array_map( 'intval', $excluded_terms );
$excluded_terms = array_filter( $excluded_terms );
if ( ! empty( $excluded_terms ) ) {
$join .= " INNER JOIN {$wpdb->term_relationships} AS tr2 ON p.ID = tr2.object_id INNER JOIN {$wpdb->term_taxonomy} AS tt2 ON tr2.term_taxonomy_id = tt2.term_taxonomy_id";
$where .= ' AND tt2.taxonomy = '' . $taxonomy . '' AND tt2.term_id NOT IN (' . implode( ',', $excluded_terms ) . ')';
}
}
if ( ! empty( $excluded_categories ) ) {
if ( ! is_array( $excluded_categories ) ) {
$excluded_categories = preg_split( '/[s,]+/', $excluded_categories );
}
$excluded_categories = array_map( 'intval', $excluded_categories );
$excluded_categories = array_filter( $excluded_categories );
if ( ! empty( $excluded_categories ) ) {
$join .= " INNER JOIN {$wpdb->term_relationships} AS tr3 ON p.ID = tr3.object_id INNER JOIN {$wpdb->term_taxonomy} AS tt3 ON tr3.term_taxonomy_id = tt3.term_taxonomy_id";
$where .= ' AND tt3.taxonomy = 'category' AND tt3.term_id NOT IN (' . implode( ',', $excluded_categories ) . ')';
}
}
$current_post_date = $post->post_date;
$where .= $wpdb->prepare( " AND p.post_date $op %s", $current_post_date );
$where .= " AND p.post_type = '$post->post_type'";
$where .= " AND p.post_status = 'publish'";
$sort = "ORDER BY p.post_date $order LIMIT 1";
$query = "SELECT p.* FROM {$wpdb->posts} AS p $join WHERE 1=1 $where $sort";
$result = $wpdb->get_row( $query );
if ( ! empty( $result ) ) {
$result = sanitize_post( $result );
}
return $result;
}
这段代码看着有点长,但主要逻辑是构建一个SQL查询语句,然后执行它,获取相邻文章的信息。我们把它拆解成几个部分:
-
初始化:
- 获取全局的
$wpdb
对象,用于执行数据库查询。 - 确定查询方向(前一篇还是后一篇),并设置相应的比较运算符(
$op
)和排序方式($order
)。 - 初始化
$join
和$where
变量,用于构建SQL查询语句的JOIN和WHERE子句。
- 获取全局的
-
处理分类目录相关参数:
$in_same_term
:如果设置为true,则只在相同分类目录下的文章中查找。会根据当前文章的分类目录构建JOIN和WHERE子句,确保只查询属于相同分类目录的文章。$excluded_terms
:排除某些分类目录。构建JOIN和WHERE子句,排除属于指定分类目录的文章。$excluded_categories
:排除某些分类目录(专门针对category
分类法)。构建JOIN和WHERE子句,排除属于指定分类目录的文章。
-
构建WHERE子句:
$where .= $wpdb->prepare( " AND p.post_date $op %s", $current_post_date );
:这是核心部分,根据发布日期进行比较。使用$wpdb->prepare()
函数来防止SQL注入。$where .= " AND p.post_type = '$post->post_type'";
:限制文章类型,确保只查询相同类型的文章。$where .= " AND p.post_status = 'publish'";
:只查询已发布的文章。
-
构建SQL查询语句:
$query = "SELECT p.* FROM {$wpdb->posts} AS p $join WHERE 1=1 $where $sort";
:将所有部分组合成一个完整的SQL查询语句。
-
执行查询并返回结果:
$result = $wpdb->get_row( $query );
:执行SQL查询,获取结果。$result = sanitize_post( $result );
:对结果进行清理,防止XSS攻击。- 返回结果。
三、性能分析:是骡子是马,拉出来溜溜
get_adjacent_post()
的性能主要取决于以下几个因素:
-
数据库查询的复杂度:
- 最简单的情况(只基于发布日期):只需要查询
wp_posts
表,速度很快。 - 复杂的情况(基于分类目录):需要进行JOIN操作,查询速度会慢一些。
- 排除分类目录会增加JOIN操作,性能也会下降。
- 最简单的情况(只基于发布日期):只需要查询
-
数据量:文章越多,查询时间越长。
-
索引:如果
wp_posts
表的post_date
字段没有索引,查询速度会很慢。 -
缓存:WordPress自带对象缓存,如果之前已经查询过相同的相邻文章,可以直接从缓存中获取,速度很快。
性能优化建议:
- 尽量避免使用
$in_same_term
参数:除非确实需要,否则尽量不要使用这个参数,因为它会增加查询的复杂度。 - 使用缓存:确保WordPress的对象缓存已经启用,可以大大提高性能。
- 检查索引:确保
wp_posts
表的post_date
字段已经创建索引。 - 自定义查询:如果
get_adjacent_post()
的性能无法满足需求,可以考虑自定义SQL查询,只获取需要的字段,减少数据传输量。 - 使用Transient API:可以将查询结果缓存到Transient中,设置一个过期时间,可以更灵活地控制缓存。
四、举个栗子:代码示例
假设我们要在文章页面显示前一篇和后一篇的链接,可以这样写:
<?php
$prev_post = get_adjacent_post( false, '', '', true ); // 获取前一篇
$next_post = get_adjacent_post( false, '', '', false ); // 获取后一篇
if ( ! empty( $prev_post ) ) {
echo '<a href="' . get_permalink( $prev_post->ID ) . '">« 上一篇:' . esc_html( $prev_post->post_title ) . '</a>';
}
if ( ! empty( $next_post ) ) {
echo '<a href="' . get_permalink( $next_post->ID ) . '">下一篇:' . esc_html( $next_post->post_title ) . ' »</a>';
}
?>
这段代码会获取前一篇和后一篇文章,然后显示链接。
五、高级用法:自定义SQL查询
如果get_adjacent_post()
的性能无法满足需求,我们可以自定义SQL查询。例如,我们可以只获取文章的ID和标题,减少数据传输量:
<?php
global $wpdb, $post;
$current_post_date = $post->post_date;
$query = $wpdb->prepare(
"SELECT ID, post_title FROM {$wpdb->posts}
WHERE post_type = %s
AND post_status = 'publish'
AND post_date < %s
ORDER BY post_date DESC
LIMIT 1",
$post->post_type,
$current_post_date
);
$prev_post = $wpdb->get_row( $query );
if ( ! empty( $prev_post ) ) {
echo '<a href="' . get_permalink( $prev_post->ID ) . '">« 上一篇:' . esc_html( $prev_post->post_title ) . '</a>';
}
?>
这段代码会直接执行SQL查询,获取前一篇文章的ID和标题。
六、参数详解:表格伺候
为了方便大家理解,我们用表格来总结一下get_adjacent_post()
的参数:
参数名 | 类型 | 默认值 | 描述 |
---|---|---|---|
$in_same_term |
boolean | false |
是否只在相同分类目录下的文章中查找。 |
$excluded_terms |
string | '' |
排除的分类目录ID,多个ID用逗号分隔。 |
$taxonomy |
string | 'category' |
分类法名称,默认为category 。 |
$previous |
boolean | true |
是否获取前一篇文章。如果为true ,则获取前一篇;如果为false ,则获取后一篇。 |
$excluded_categories |
string | '' |
排除的分类目录ID,多个ID用逗号分隔。注意:这个参数只针对category 分类法有效,如果$taxonomy 参数不是category ,则这个参数无效。 |
七、总结:学以致用,融会贯通
get_adjacent_post()
是一个非常实用的函数,可以帮助我们轻松获取文章的前一篇和后一篇。但是,在实际使用中,我们需要注意它的性能问题,并根据实际情况进行优化。
希望今天的课程能帮助大家更好地理解get_adjacent_post()
的源码和使用方法。记住,源码不是用来背的,而是用来理解的!理解了原理,才能灵活运用,写出更高效的代码。
今天的课程就到这里,下课!