深入理解 `WP_Comment_Query` 类的源码,它是如何构建复杂的评论查询条件的?

WordPress 评论查询:从 WP_Comment_Query 说起(讲座版)

大家好啊,各位观众老爷!今天咱们来聊聊 WordPress 里面一个相当重要的角色:WP_Comment_Query。 别看名字长,其实它就是个专门负责找评论的家伙。不过,这找评论的门道可深了,它能根据各种奇奇怪怪的条件,把你想找的评论给你揪出来。

咱们今天就扒一扒 WP_Comment_Query 的源码,看看它到底是怎么构建那些复杂的查询条件的。 咱们争取把它讲的透彻,让大家以后在定制评论查询的时候,也能得心应手,不再发愁。

1. WP_Comment_Query 是个啥?

简单来说,WP_Comment_Query 是 WordPress 提供的一个类,专门用来查询评论。它封装了复杂的 SQL 查询逻辑,让你不用直接写 SQL 也能方便地获取评论。 想象一下,你要找所有未审核的评论,或者某个用户的所有评论,再或者某个文章的所有评论,用 WP_Comment_Query 都能轻松搞定。

2. 从构造函数开始:__construct()

咱们先从 WP_Comment_Query 的构造函数 __construct() 入手,看看它是怎么接受各种参数的。

public function __construct( $query = '' ) {
  if ( ! empty( $query ) ) {
    $this->query( $query );
  }
}

这段代码很简单,就是判断一下有没有传入查询参数 $query。 如果有,就调用 query() 方法来处理这些参数。 $query 可以是数组,也可以是查询字符串。 举个例子:

$args = array(
  'number'  => 10, // 获取 10 条评论
  'status'  => 'approve', // 只获取审核通过的评论
  'post_id' => 123 // 只获取文章 ID 为 123 的评论
);

$comment_query = new WP_Comment_Query( $args );

或者,你也可以用查询字符串:

$comment_query = new WP_Comment_Query( 'number=10&status=approve&post_id=123' );

两种方式效果一样,看你喜欢哪个。

3. 核心方法:query()

query() 方法才是真正干活的。 它负责解析查询参数,构建 SQL 查询语句,然后执行查询。

public function query( $query ) {
  global $wpdb;

  $this->query_vars = wp_parse_args( $query, $this->query_vars_defaults );
  $this->query_vars = $this->sanitize_query( $this->query_vars );
  extract( $this->query_vars, EXTR_SKIP );

  $this->comment_fields = '*';

  if ( 'ids' == $fields ) {
    $this->comment_fields = 'comment_ID';
  }

  $this->sql_clauses = $this->get_sql_clauses();

  $this->comments = $wpdb->get_results( $this->sql_clauses['select'] . $this->sql_clauses['from'] . $this->sql_clauses['where'] . $this->sql_clauses['orderby'] . $this->sql_clauses['limits'] );

  if ( $this->comments ) {
    wp_cache_add_multiple( $this->comments, 'comments' );
    //...  (省略部分代码,关于 populate_comments 等)
  }

  return $this->comments;
}

咱们一步一步来分析:

  • wp_parse_args(): 这个函数把传入的查询参数 $query 和默认参数 $this->query_vars_defaults 合并。 默认参数定义了很多默认的查询选项,比如排序方式、评论状态等等。
  • sanitize_query(): 这个函数负责对查询参数进行安全过滤,防止 SQL 注入。
  • extract(): 这个函数把 $this->query_vars 数组里的键值对提取成变量。 比如,$this->query_vars['number'] 就会变成 $number 变量。
  • get_sql_clauses(): 这个方法是重点,它负责构建 SQL 查询语句的各个部分,包括 SELECTFROMWHEREORDER BYLIMIT 子句。
  • $wpdb->get_results(): 这个函数执行 SQL 查询,获取结果。
  • wp_cache_add_multiple(): 这个函数把查询结果缓存起来,下次再查同样的评论就不用再查数据库了,提高性能。

4. SQL 构建的关键:get_sql_clauses()

get_sql_clauses() 方法是 WP_Comment_Query 的灵魂。 它根据查询参数,动态地构建 SQL 查询语句的各个部分。 咱们来看看这个方法的简化版:

protected function get_sql_clauses() {
  global $wpdb;

  $sql = array();

  $where = "WHERE 1=1";  // 默认条件,总是成立

  // 处理 comment__in 参数
  if ( ! empty( $this->query_vars['comment__in'] ) ) {
    $ids = wp_parse_id_list( $this->query_vars['comment__in'] );
    $where .= " AND comment_ID IN (" . implode( ',', array_map( 'intval', $ids ) ) . ")";
  }

  // 处理 comment__not_in 参数
  if ( ! empty( $this->query_vars['comment__not_in'] ) ) {
    $ids = wp_parse_id_list( $this->query_vars['comment__not_in'] );
    $where .= " AND comment_ID NOT IN (" . implode( ',', array_map( 'intval', $ids ) ) . ")";
  }

  // 处理 post_id 参数
  if ( ! empty( $this->query_vars['post_id'] ) ) {
    $post_ids = wp_parse_id_list( $this->query_vars['post_id'] );
    $where .= " AND comment_post_ID IN (" . implode( ',', array_map( 'intval', $post_ids ) ) . ")";
  }

  // 处理 status 参数
  if ( ! empty( $this->query_vars['status'] ) ) {
    $statuses = $this->query_vars['status'];
    if ( is_string( $statuses ) ) {
      $statuses = explode( ',', $statuses );
    }

    $status_clauses = array();
    foreach ( $statuses as $status ) {
      $status = trim( $status );
      switch ( $status ) {
        case 'hold':
        case 'unapproved':
          $status_clauses[] = "comment_approved = '0'";
          break;
        case 'approve':
        case 'approved':
          $status_clauses[] = "comment_approved = '1'";
          break;
        case 'spam':
          $status_clauses[] = "comment_approved = 'spam'";
          break;
        case 'trash':
          $status_clauses[] = "comment_approved = 'trash'";
          break;
        default:
          $status_clauses[] = "comment_approved = '" . esc_sql( $status ) . "'";
          break;
      }
    }

    if ( ! empty( $status_clauses ) ) {
      $where .= " AND (" . implode( ' OR ', $status_clauses ) . ")";
    }
  }

  // 处理 number 和 offset 参数
  $number = absint( $this->query_vars['number'] );
  $offset = absint( $this->query_vars['offset'] );
  if ( $number ) {
    if ( $offset ) {
      $limits = 'LIMIT ' . $offset . ',' . $number;
    } else {
      $limits = 'LIMIT ' . $number;
    }
  } else {
    $limits = '';
  }

  $sql['select']  = "SELECT {$this->comment_fields}";
  $sql['from']    = " FROM {$wpdb->comments}";
  $sql['where']   = $where;
  $sql['orderby'] = $this->get_comment_order_by_clause();
  $sql['limits']  = $limits;

  return $sql;
}

这个方法的核心逻辑是,根据不同的查询参数,拼接 WHERE 子句。 比如,如果指定了 post_id,就拼接 AND comment_post_ID IN (...)。 如果指定了 status,就拼接 AND comment_approved = ...

get_sql_clauses 里面还有很多其他的参数处理,比如 date_query (日期查询), author__in, author__not_in 等等, 这里就不一一列举了, 核心思想都是一样的: 根据查询参数,拼接 SQL 语句。

5. 排序的处理:get_comment_order_by_clause()

get_comment_order_by_clause() 方法负责构建 ORDER BY 子句。 它可以根据 orderbyorder 参数,指定评论的排序方式。

protected function get_comment_order_by_clause() {
  global $wpdb;

  $orderby = $this->query_vars['orderby'];
  $order   = $this->query_vars['order'];

  $allowed_keys = array(
    'comment_ID',
    'comment_author',
    'comment_date',
    'comment_content',
    'comment_karma',
    'comment_approved',
    'comment_post_ID',
    'comment_parent',
    'comment_type',
    'comment__in',
  );

  if ( ! in_array( $orderby, $allowed_keys, true ) ) {
    $orderby = 'comment_date'; // 默认按日期排序
  }

  $order = strtoupper( $order );
  if ( 'DESC' !== $order && 'ASC' !== $order ) {
    $order = 'DESC'; // 默认降序
  }

  return "ORDER BY {$orderby} {$order}";
}

这个方法首先检查 orderby 参数是否合法,如果不合法,就使用默认的 comment_date 排序。 然后,它检查 order 参数是否是 ASCDESC,如果不是,就使用默认的 DESC 降序。 最后,它拼接 ORDER BY 子句。

6. 举个栗子:复杂查询

咱们来举个例子,看看怎么用 WP_Comment_Query 构建一个复杂的查询:

$args = array(
  'number'  => 20, // 获取 20 条评论
  'status'  => 'approve', // 只获取审核通过的评论
  'post_id' => array(123, 456), // 获取文章 ID 为 123 和 456 的评论
  'date_query' => array(
    array(
      'year'  => 2023,
      'month' => 10,
    ),
  ), // 只获取 2023 年 10 月的评论
  'orderby' => 'comment_date', // 按日期排序
  'order'   => 'ASC', // 升序
);

$comment_query = new WP_Comment_Query( $args );

$comments = $comment_query->get_comments();

if ( $comments ) {
  foreach ( $comments as $comment ) {
    echo '<p>' . $comment->comment_content . '</p>';
  }
} else {
  echo '没有找到评论';
}

这段代码会获取 2023 年 10 月,文章 ID 为 123 和 456 的,审核通过的 20 条评论,并按照日期升序排列。

7. 总结

WP_Comment_Query 是 WordPress 提供的一个强大的评论查询工具。 它通过封装复杂的 SQL 查询逻辑,让你不用直接写 SQL 也能方便地获取评论。 核心思想是:

  1. 接收查询参数。
  2. 对查询参数进行安全过滤。
  3. 根据查询参数,动态地构建 SQL 查询语句的各个部分,包括 SELECTFROMWHEREORDER BYLIMIT 子句。
  4. 执行 SQL 查询,获取结果。
  5. 缓存查询结果,提高性能。

通过理解 WP_Comment_Query 的源码,你可以更好地定制评论查询,满足各种奇奇怪怪的需求。 希望今天的讲座对大家有所帮助。 谢谢大家!

8. 常用参数列表

为了方便大家查阅,我整理了一个 WP_Comment_Query 常用参数的表格:

参数名 类型 描述
number int 要获取的评论数量。
offset int 偏移量,从第几条评论开始获取。
orderby string 排序字段,可选值包括 comment_ID, comment_author, comment_date, comment_content, comment_karma, comment_approved, comment_post_ID, comment_parent, comment_type 等。
order string 排序方式,可选值包括 ASC (升序) 和 DESC (降序)。
status string | array 评论状态,可选值包括 approve, hold, spam, trash。 可以是单个状态,也可以是状态数组。
post_id int | array 文章 ID,只获取指定文章的评论。 可以是单个 ID,也可以是 ID 数组。
post_author int | array 文章作者 ID,只获取指定作者的文章的评论。可以是单个 ID,也可以是 ID 数组。
post_type string | array 文章类型,只获取指定文章类型的评论。 可以是单个类型,也可以是类型数组。
post_status string | array 文章状态,只获取指定文章状态的评论。 可以是单个状态,也可以是状态数组。
comment__in array 只获取指定评论 ID 的评论。
comment__not_in array 排除指定评论 ID 的评论。
author_email string 评论作者邮箱。
author__in array 只获取指定评论作者 ID 的评论。
author__not_in array 排除指定评论作者 ID 的评论。
date_query array 日期查询,可以根据年份、月份、日期等条件查询评论。 具体用法参考 WordPress 官方文档。
search string 搜索评论内容。
fields string 返回字段,可选值包括 'all' (返回所有字段) 和 'ids' (只返回评论 ID)。
meta_query array 元数据查询,可以根据评论的元数据查询评论。 具体用法参考 WordPress 官方文档。

这个表格列出了 WP_Comment_Query 中常用的参数,希望对大家有所帮助。 在实际使用中,可以根据自己的需求选择合适的参数,构建出满足需求的评论查询。

发表回复

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