各位观众,晚上好!今天咱们来聊聊 WordPress 里的“评论侦探”—— WP_Comment_Query
类。这家伙专门负责从数据库里搜寻各种评论信息,就像福尔摩斯一样,根据你给的线索,给你找出你想要的评论。
我们今天要做的,就是解剖这位“侦探”,看看他是怎么接受线索(参数),又是怎么在茫茫评论数据中找到目标的(构建 SQL)。
第一幕:认识“侦探”——WP_Comment_Query
类
首先,咱们得对这位“侦探”有个基本的了解。WP_Comment_Query
类是 WordPress 提供的一个用于查询评论的类。它允许你通过各种参数来筛选评论,比如作者、日期、状态、关键词等等。
你可以像这样使用它:
$args = array(
'author_email' => '[email protected]',
'status' => 'approve', // 只获取已批准的评论
'number' => 10, // 最多获取 10 条评论
);
$comment_query = new WP_Comment_Query( $args );
$comments = $comment_query->comments;
if ( $comments ) {
foreach ( $comments as $comment ) {
echo '<p>' . $comment->comment_content . '</p>';
}
} else {
echo '没有找到符合条件的评论。';
}
这段代码创建了一个 WP_Comment_Query
对象,并传入了一个参数数组 $args
。然后,它调用 $comment_query->comments
获取查询结果,并遍历显示评论内容。
第二幕:解读“线索”——参数的秘密
WP_Comment_Query
接受的参数种类繁多,就像福尔摩斯面前摆满了各种线索。这些参数可以分为几个大类:
-
基本信息:
comment__in
:只获取指定 ID 的评论(数组)。comment__not_in
:排除指定 ID 的评论(数组)。number
:获取评论的最大数量。offset
:从第几条评论开始获取。paged
:分页参数,用于获取指定页码的评论。fields
:返回的字段,可选值包括'ids'
(只返回 ID) 和'all'
(返回所有字段)。
-
作者信息:
author_email
:指定作者的邮箱地址。author__in
:指定作者 ID 的评论(数组)。author__not_in
:排除指定作者 ID 的评论(数组)。
-
文章信息:
post_id
:指定文章 ID。post__in
:指定文章 ID 的评论(数组)。post__not_in
:排除指定文章 ID 的评论(数组)。post_author__in
:指定文章作者 ID 的评论(数组)。post_author__not_in
:排除指定文章作者 ID 的评论(数组)。post_type
:指定文章类型。post_status
:指定文章状态。
-
日期信息:
date_query
:使用WP_Date_Query
对象进行日期查询。year
:年份。month
:月份。monthnum
:月份(数字)。w
:周数。day
:天数。hour
:小时。minute
:分钟。second
:秒。
-
内容信息:
keyword
:关键词,用于搜索评论内容。search
:关键词,效果同keyword
。
-
状态信息:
status
:评论状态,可选值包括'approve'
(已批准)、'hold'
(待审核)、'spam'
(垃圾评论)、'trash'
(已删除) 和'all'
(所有状态)。type
:评论类型,比如'comment'
、'pingback'
、'trackback'
。parent
:父级评论 ID。parent__in
:父级评论 ID(数组)。parent__not_in
:排除父级评论 ID(数组)。
-
排序信息:
orderby
:排序字段,可选值包括'comment_ID'
、'comment_post_ID'
、'comment_author'
、'comment_author_email'
、'comment_date'
、'comment_content'
、'comment_karma'
和'comment_approved'
。order
:排序方式,可选值包括'ASC'
(升序) 和'DESC'
(降序)。
这只是冰山一角,还有很多其他参数可以使用。你可以参考 WordPress 官方文档来了解更多信息。
第三幕:现场还原——SQL 构建过程
现在,咱们来深入 WP_Comment_Query
的内部,看看它是如何将这些“线索”转化为 SQL 查询语句的。
-
初始化:
__construct()
方法当我们创建一个
WP_Comment_Query
对象时,__construct()
方法会被调用。这个方法主要做两件事:- 合并默认参数和用户传入的参数。
- 调用
prepare_query()
方法来构建 SQL 查询语句。
public function __construct( $query = '' ) { $this->query_vars = wp_parse_args( $query, $this->defaults ); $this->query_vars = $this->fill_query_vars( $this->query_vars ); /** * Fires before the comment query is run. * * @since 3.1.0 * * @param WP_Comment_Query &$this The WP_Comment_Query instance (passed by reference). */ do_action_ref_array( 'pre_get_comments', array( &$this ) ); $this->prepare_query(); }
wp_parse_args()
函数用于将用户传入的参数和默认参数合并。$this->defaults
是一个包含默认参数的数组。 -
准备查询:
prepare_query()
方法prepare_query()
方法是构建 SQL 查询语句的核心。它会根据query_vars
属性中的参数,逐步构建 SQL 语句的各个部分。public function prepare_query( $query_vars = '' ) { global $wpdb; if ( empty( $query_vars ) ) { $this->query_vars = $this->fill_query_vars( $this->query_vars ); $qv = &$this->query_vars; } else { $query_vars = $this->fill_query_vars( $query_vars ); $qv = &$query_vars; } $this->comment_fields = '*'; // Query parts. $this->query_from = "FROM {$wpdb->comments}"; $this->query_where = 'WHERE 1=1'; $this->query_orderby = ''; $this->query_limit = ''; $this->query_groupby = ''; $this->query_join = ''; // ID sanity check. if ( ! empty( $qv['comment__in'] ) ) { $qv['comment__in'] = wp_parse_id_list( $qv['comment__in'] ); } if ( ! empty( $qv['comment__not_in'] ) ) { $qv['comment__not_in'] = wp_parse_id_list( $qv['comment__not_in'] ); } $where = ''; // comment_id. if ( ! empty( $qv['comment__in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_ID IN ( ' . implode( ',', array_map( 'intval', $qv['comment__in'] ) ) . ' )'; } elseif ( ! empty( $qv['comment__not_in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_ID NOT IN ( ' . implode( ',', array_map( 'intval', $qv['comment__not_in'] ) ) . ' )'; } // Authors. if ( ! empty( $qv['author_email'] ) ) { $where .= $wpdb->prepare( ' AND ' . $wpdb->comments . '.comment_author_email = %s', $qv['author_email'] ); } if ( ! empty( $qv['author__in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_user_id IN ( ' . implode( ',', array_map( 'intval', $qv['author__in'] ) ) . ' )'; } elseif ( ! empty( $qv['author__not_in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_user_id NOT IN ( ' . implode( ',', array_map( 'intval', $qv['author__not_in'] ) ) . ' )'; } // Post ID. if ( ! empty( $qv['post_id'] ) ) { $where .= $wpdb->prepare( ' AND ' . $wpdb->comments . '.comment_post_ID = %d', $qv['post_id'] ); } if ( ! empty( $qv['post__in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_post_ID IN ( ' . implode( ',', array_map( 'intval', $qv['post__in'] ) ) . ' )'; } elseif ( ! empty( $qv['post__not_in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_post_ID NOT IN ( ' . implode( ',', array_map( 'intval', $qv['post__not_in'] ) ) . ' )'; } // Post Author ID. if ( ! empty( $qv['post_author__in'] ) ) { $where .= ' AND ' . $wpdb->posts . '.post_author IN ( ' . implode( ',', array_map( 'intval', $qv['post_author__in'] ) ) . ' )'; $this->query_from .= " INNER JOIN {$wpdb->posts} ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID"; } elseif ( ! empty( $qv['post_author__not_in'] ) ) { $where .= ' AND ' . $wpdb->posts . '.post_author NOT IN ( ' . implode( ',', array_map( 'intval', $qv['post_author__not_in'] ) ) . ' )'; $this->query_from .= " INNER JOIN {$wpdb->posts} ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID"; } // Post Type. if ( ! empty( $qv['post_type'] ) ) { $post_types = wp_parse_slug_list( $qv['post_type'] ); if ( $post_types ) { $post_types = array_map( array( $wpdb, 'escape' ), $post_types ); $where .= " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", $post_types ) . "')"; $this->query_from .= " INNER JOIN {$wpdb->posts} ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID"; } } // Post Status. if ( ! empty( $qv['post_status'] ) ) { $post_statuses = wp_parse_slug_list( $qv['post_status'] ); if ( $post_statuses ) { $post_statuses = array_map( array( $wpdb, 'escape' ), $post_statuses ); $where .= " AND {$wpdb->posts}.post_status IN ('" . implode( "', '", $post_statuses ) . "')"; $this->query_from .= " INNER JOIN {$wpdb->posts} ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID"; } } // Date queries. if ( ! empty( $qv['date_query'] ) && is_array( $qv['date_query'] ) ) { $date_query = new WP_Date_Query( $qv['date_query'], $wpdb->comments . '.comment_date' ); $where .= $date_query->get_sql(); } else { // Legacy date parameters. $date_query = new WP_Date_Query( $qv, $wpdb->comments . '.comment_date' ); $where .= $date_query->get_sql(); } // Keywords. if ( ! empty( $qv['keyword'] ) || ! empty( $qv['search'] ) ) { $search_term = ! empty( $qv['keyword'] ) ? $qv['keyword'] : $qv['search']; $search_term = esc_sql( $wpdb->esc_like( $search_term ) ); $where .= " AND {$wpdb->comments}.comment_content LIKE '%{$search_term}%'"; } // Comment status. if ( ! empty( $qv['status'] ) ) { $approved = wp_get_comment_status( $qv['status'] ); if ( is_array( $approved ) ) { $approved = array_map( 'esc_sql', $approved ); $where .= " AND {$wpdb->comments}.comment_approved IN ( '" . implode( "', '", $approved ) . "' )"; } else { $where .= $wpdb->prepare( " AND {$wpdb->comments}.comment_approved = %s", $approved ); } } // Comment type. if ( ! empty( $qv['type'] ) ) { $types = wp_parse_slug_list( $qv['type'] ); $types = array_map( 'esc_sql', $types ); $where .= " AND {$wpdb->comments}.comment_type IN ( '" . implode( "', '", $types ) . "' )"; } // Comment parent. if ( ! empty( $qv['parent'] ) ) { $where .= $wpdb->prepare( ' AND ' . $wpdb->comments . '.comment_parent = %d', $qv['parent'] ); } if ( ! empty( $qv['parent__in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_parent IN ( ' . implode( ',', array_map( 'intval', $qv['parent__in'] ) ) . ' )'; } elseif ( ! empty( $qv['parent__not_in'] ) ) { $where .= ' AND ' . $wpdb->comments . '.comment_parent NOT IN ( ' . implode( ',', array_map( 'intval', $qv['parent__not_in'] ) ) . ' )'; } $this->query_where .= $where; // Ordering. $orderby_array = array(); if ( ! empty( $qv['orderby'] ) ) { $orders = is_array( $qv['orderby'] ) ? $qv['orderby'] : preg_split( '/[,s]+/', $qv['orderby'] ); foreach ( $orders as $order ) { $order = strtolower( $order ); switch ( $order ) { case 'comment_id': $orderby_array[] = $wpdb->comments . '.comment_ID'; break; case 'comment_post_id': $orderby_array[] = $wpdb->comments . '.comment_post_ID'; break; case 'comment_author': $orderby_array[] = $wpdb->comments . '.comment_author'; break; case 'comment_author_email': $orderby_array[] = $wpdb->comments . '.comment_author_email'; break; case 'comment_date': $orderby_array[] = $wpdb->comments . '.comment_date'; break; case 'comment_content': $orderby_array[] = $wpdb->comments . '.comment_content'; break; case 'comment_karma': $orderby_array[] = $wpdb->comments . '.comment_karma'; break; case 'comment_approved': $orderby_array[] = $wpdb->comments . '.comment_approved'; break; default: $orderby_array[] = $wpdb->comments . '.comment_ID'; } } } if ( ! empty( $orderby_array ) ) { $this->query_orderby = 'ORDER BY ' . implode( ',', $orderby_array ); if ( isset( $qv['order'] ) ) { $this->query_orderby .= ' ' . strtoupper( $qv['order'] ); } } // Limit. if ( ! empty( $qv['number'] ) ) { if ( ! empty( $qv['offset'] ) ) { $this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $qv['offset'], $qv['number'] ); } else { $this->query_limit = $wpdb->prepare( 'LIMIT %d', $qv['number'] ); } } // Paged. if ( $qv['number'] && $qv['paged'] ) { $page = absint( $qv['paged'] ); $number = absint( $qv['number'] ); $offset = ( $page - 1 ) * $number; $this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $offset, $number ); } // 'fields' => 'ids' returns only the IDs. if ( 'ids' === $qv['fields'] ) { $this->comment_fields = "{$wpdb->comments}.comment_ID"; } $this->request = "SELECT {$this->comment_fields} {$this->query_from} {$this->query_where} {$this->query_groupby} {$this->query_orderby} {$this->query_limit}"; }
这个方法做了很多事情,咱们挑几个重点来说:
- 构建
WHERE
子句: 它会根据query_vars
中的各种参数,构建 SQL 语句的WHERE
子句。比如,如果指定了author_email
,就会添加AND comment_author_email = %s
这样的条件。注意,这里使用了$wpdb->prepare()
来防止 SQL 注入。 - 处理日期查询: 如果使用了
date_query
参数,它会创建一个WP_Date_Query
对象,并调用其get_sql()
方法来获取日期查询的 SQL 语句。 - 构建
ORDER BY
子句: 它会根据orderby
和order
参数,构建 SQL 语句的ORDER BY
子句。 - 构建
LIMIT
子句: 它会根据number
、offset
和paged
参数,构建 SQL 语句的LIMIT
子句,用于限制返回的评论数量。 - 构建完整的 SQL 语句: 最后,它会将各个部分组合起来,形成完整的 SQL 查询语句,并将其存储在
$this->request
属性中。
- 构建
-
执行查询:
get_comments()
方法get_comments()
方法负责执行 SQL 查询语句,并返回查询结果。public function get_comments() { global $wpdb; /** * Fires before the comment query executes. * * @since 3.1.0 * * @param WP_Comment_Query &$this The WP_Comment_Query instance (passed by reference). */ do_action_ref_array( 'pre_comment_query', array( &$this ) ); $comments = $wpdb->get_results( $this->request ); if ( 'ids' === $this->query_vars['fields'] ) { $this->comments = array_map( 'intval', $comments ); return $this->comments; } // Prime caches. if ( $comments ) { update_comment_cache( $comments ); $this->comments = array_map( 'get_comment', $comments ); } else { $this->comments = array(); } return $this->comments; }
这个方法主要做了以下几件事:
- 执行 SQL 查询: 它使用
$wpdb->get_results()
方法执行$this->request
中存储的 SQL 查询语句。 - 处理查询结果: 如果
fields
参数设置为'ids'
,它会将查询结果转换为整数数组,并返回。否则,它会调用update_comment_cache()
函数来更新评论缓存,并调用get_comment()
函数将查询结果转换为WP_Comment
对象数组,并返回。
- 执行 SQL 查询: 它使用
第四幕:案例分析——一个复杂的查询
为了更好地理解 WP_Comment_Query
的工作原理,咱们来看一个稍微复杂一点的例子。
假设我们需要查询以下评论:
- 作者邮箱地址为
[email protected]
。 - 评论状态为
'approve'
。 - 文章类型为
'post'
。 - 评论内容包含关键词
'WordPress'
。 - 按照评论日期降序排列。
- 只获取前 10 条评论。
我们可以这样构建查询参数:
$args = array(
'author_email' => '[email protected]',
'status' => 'approve',
'post_type' => 'post',
'keyword' => 'WordPress',
'orderby' => 'comment_date',
'order' => 'DESC',
'number' => 10,
);
$comment_query = new WP_Comment_Query( $args );
$comments = $comment_query->comments;
WP_Comment_Query
会将这些参数转化为以下 SQL 查询语句(简化版):
SELECT *
FROM wp_comments
INNER JOIN wp_posts ON wp_comments.comment_post_ID = wp_posts.ID
WHERE 1=1
AND wp_comments.comment_author_email = '[email protected]'
AND wp_comments.comment_approved = '1'
AND wp_posts.post_type = 'post'
AND wp_comments.comment_content LIKE '%WordPress%'
ORDER BY wp_comments.comment_date DESC
LIMIT 10
可以看到,WP_Comment_Query
根据我们提供的参数,构建了一个复杂的 SQL 查询语句,从而能够准确地找到我们想要的评论。
第五幕:总结与技巧
WP_Comment_Query
是一个功能强大的评论查询工具,掌握它可以帮助你更灵活地处理 WordPress 中的评论数据。
以下是一些使用 WP_Comment_Query
的技巧:
- 使用
wp_parse_args()
函数: 可以使用wp_parse_args()
函数来合并默认参数和用户传入的参数,从而简化代码。 - 使用
$wpdb->prepare()
函数: 一定要使用$wpdb->prepare()
函数来防止 SQL 注入。 - 善用
date_query
参数:date_query
参数可以让你更灵活地进行日期查询。 - 注意性能优化: 复杂的查询可能会影响性能,尽量避免不必要的查询条件,并合理使用缓存。
结语
希望今天的讲座能让你对 WP_Comment_Query
类有更深入的了解。 记住,熟练掌握 WP_Comment_Query
,你就能像一位真正的“评论侦探”一样,轻松驾驭 WordPress 中的评论数据! 谢谢大家!