剖析 `WP_Term_Query` 类的源码,解释它如何通过 “ 参数构建 SQL 语句来查询分类术语。

咳咳,各位同学们,老司机要开车了,今天咱们来聊聊 WordPress 里面的 WP_Term_Query 这个类,尤其是它怎么通过 taxonomy 参数来构建 SQL 语句查询分类术语的。这玩意儿看起来高深莫测,其实扒开皮儿,你会发现它也就那么回事儿。准备好了吗?咱们开始!

一、WP_Term_Query 是个啥?

简单来说,WP_Term_Query 是 WordPress 提供的一个专门用来查询分类术语(也就是 taxonomy terms,例如分类目录、标签等等)的类。它允许你使用各种各样的参数来过滤、排序、分页你的术语。你想按名称查,按描述查,按 parent 查,按 slug 查,甚至按 term_id 查,它都能满足你。

二、taxonomy 参数:核心中的核心

taxonomy 参数,顾名思义,指定你要查询哪个或哪些分类法。这是 WP_Term_Query 必须处理的核心参数之一。 如果没有这个参数,WP_Term_Query 就不知道你要查什么类型的术语,查询就无从谈起。

三、源码解剖:一步一步看它怎么干的

咱们直接上代码,从 WP_Term_Query 的构造函数开始,看看它如何处理 taxonomy 参数并构建 SQL 语句。

<?php
// 假设这是简化版的 WP_Term_Query 类 (为了方便讲解,省略了一些不重要的部分)

class My_WP_Term_Query {

    public $query_vars = array(); //存储查询参数
    public $terms; // 查询结果
    public $term_count; // 查询到的术语数量
    public $sql_clauses = array(
        'select'  => '',
        'from'    => '',
        'where'   => '',
        'orderby' => '',
        'limits'  => '',
    );

    public function __construct( $query = '' ) {

        $this->query_vars = wp_parse_args( $query, $this->get_default_query() );

        // 处理 taxonomy 参数
        $this->parse_taxonomies();

        // 构建 SQL
        $this->get_terms();
    }

    protected function get_default_query() {
        return array(
            'taxonomy'   => 'category', // 默认分类法
            'object_ids' => null,
            'number'     => '',
            'offset'     => '',
            'orderby'    => 'name',
            'order'      => 'ASC',
            'hide_empty' => false,
            'include'    => array(),
            'exclude'    => array(),
            'search'     => '',
            'cache_domain' => 'core',
        );
    }

    protected function parse_taxonomies() {
        $taxonomies = $this->query_vars['taxonomy'];

        if ( ! is_array( $taxonomies ) ) {
            $taxonomies = preg_split( '/[,s]+/', $taxonomies );
        }
        $this->query_vars['taxonomy'] = $taxonomies;
    }

    public function get_terms() {
        global $wpdb;

        $this->sql_clauses['select']  = 'SELECT t.*, tt.* ';
        $this->sql_clauses['from']    = "FROM {$wpdb->terms} AS t INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id ";
        $this->sql_clauses['where']   = 'WHERE 1=1 ';
        $this->sql_clauses['orderby'] = 'ORDER BY t.name ASC';
        $this->sql_clauses['limits']  = '';

        // 添加 taxonomy 条件
        $this->sql_clauses['where'] .= $this->get_taxonomy_sql();

        // 构建完整的 SQL 语句
        $sql = $this->sql_clauses['select'] .
               $this->sql_clauses['from'] .
               $this->sql_clauses['where'] .
               $this->sql_clauses['orderby'] .
               $this->sql_clauses['limits'];

        // 查询数据库
        $this->terms = $wpdb->get_results( $sql );
        $this->term_count = count($this->terms);

        return $this->terms;
    }

    protected function get_taxonomy_sql() {
        global $wpdb;
        $taxonomies = $this->query_vars['taxonomy'];

        $taxonomies = array_map( 'esc_sql', $taxonomies );
        $taxonomy_list = "'" . implode( "', '", $taxonomies ) . "'";

        $sql = "AND tt.taxonomy IN ( {$taxonomy_list} )";

        return $sql;
    }
}

// 使用示例
$args = array(
    'taxonomy' => array( 'category', 'post_tag' ),
    'hide_empty' => true,
    'orderby' => 'count',
    'order' => 'DESC'
);

$term_query = new My_WP_Term_Query( $args );

if ( $term_query->terms ) {
    echo "找到 " . $term_query->term_count . " 个术语n";
    foreach ( $term_query->terms as $term ) {
        echo "Term: " . $term->name . ", Taxonomy: " . $term->taxonomy . "n";
    }
} else {
    echo "没有找到任何术语n";
}

?>

代码解读:

  1. 构造函数 __construct()

    • 接收一个查询参数数组 $query
    • 使用 wp_parse_args() 函数将传入的参数与默认参数合并。 这样,即使你只传入了 taxonomy 参数,其他参数也会有默认值。
    • 调用 parse_taxonomies() 方法来确保 taxonomy 参数是数组形式。 如果传入的是字符串(例如 'category'),则将其转换为数组。
    • 调用 get_terms() 执行查询
  2. get_default_query():

    • 返回一个包含默认查询参数的数组。 关键的是 'taxonomy' => 'category',这意味着如果你不指定 taxonomy,默认会查询 category(分类目录)。
  3. parse_taxonomies()

    • 处理 taxonomy 参数,确保它是一个数组。 这样,即使你传入的是用逗号分隔的字符串(例如 'category, post_tag'),也会被转换为数组 array('category', 'post_tag')
  4. get_terms()

    • 构建 SQL 查询语句的核心方法。
    • 首先定义基本的 SQL 语句框架,包括 SELECTFROMWHEREORDER BYLIMIT 子句。 注意这里用到了 $wpdb 全局对象,它是 WordPress 数据库操作的核心。
    • 调用 get_taxonomy_sql() 方法来生成 WHERE 子句中关于 taxonomy 的条件。
    • 将各个 SQL 子句拼接成完整的 SQL 语句。
    • 使用 $wpdb->get_results() 函数执行 SQL 查询,并将结果存储在 $this->terms 属性中。
  5. get_taxonomy_sql()

    • 根据 taxonomy 参数生成 SQL 的 WHERE 子句。
    • taxonomy 数组中的每个元素使用 esc_sql() 函数进行转义,防止 SQL 注入。
    • 将转义后的 taxonomy 列表用逗号连接,并用单引号括起来。
    • 构建 AND tt.taxonomy IN ( {$taxonomy_list} ) 这样的 SQL 片段,用于筛选指定的分类法。

四、SQL 语句示例

假设我们使用以下参数:

$args = array(
    'taxonomy' => array( 'category', 'post_tag' ),
    'hide_empty' => true,
    'orderby' => 'count',
    'order' => 'DESC'
);

那么,最终生成的 SQL 语句可能是这样的(简化版):

SELECT t.*, tt.*
FROM wp_terms AS t
INNER JOIN wp_term_taxonomy AS tt ON t.term_id = tt.term_id
WHERE 1=1
AND tt.taxonomy IN ( 'category', 'post_tag' )
ORDER BY t.name ASC

解释:

  • SELECT t.*, tt.*:选择 wp_terms 表和 wp_term_taxonomy 表的所有字段。
  • FROM wp_terms AS t INNER JOIN wp_term_taxonomy AS tt ON t.term_id = tt.term_id:从 wp_terms 表和 wp_term_taxonomy 表进行内连接,连接条件是 term_id 相等。
  • WHERE 1=1 AND tt.taxonomy IN ( 'category', 'post_tag' )WHERE 子句,1=1 是一个常用的技巧,方便后续添加条件。 tt.taxonomy IN ( 'category', 'post_tag' ) 筛选出 taxonomy'category''post_tag' 的术语。
  • ORDER BY t.name ASC: 根据 t.name 也就是term的name排序,升序

五、深入理解:WP_Term_Query 的设计思想

  • 灵活性: WP_Term_Query 的设计非常灵活,允许你通过各种参数来定制你的查询。
  • 可扩展性: 你可以通过 filter 钩子来修改 SQL 语句,从而实现更复杂的查询需求。 例如,你可以添加自定义的 WHERE 子句,或者修改 ORDER BY 子句。
  • 安全性: WP_Term_Query 使用 esc_sql() 函数来转义 SQL 语句中的参数,防止 SQL 注入。

六、WP_Term_Query 的其他重要参数

除了 taxonomy 之外,WP_Term_Query 还有很多其他重要的参数,它们共同作用,构建出强大的查询能力。 下面是一些常用的参数:

参数 描述 示例
object_ids 根据文章ID筛选关联的术语。 $args['object_ids'] = array(1, 2, 3); // 获取与文章ID 1, 2, 3 关联的术语
number 返回术语的最大数量。 $args['number'] = 5; // 只返回5个术语
offset 从查询结果的哪个位置开始返回术语。 $args['offset'] = 10; // 跳过前10个术语,从第11个开始返回
orderby 排序字段。 可以是 ‘name’(默认)、’count’、’term_id’、’slug’ 等。 $args['orderby'] = 'count'; // 按术语计数排序
order 排序方式。 可以是 ‘ASC’(默认,升序)或 ‘DESC’(降序)。 $args['order'] = 'DESC'; // 降序排列
hide_empty 是否隐藏没有文章的术语。 默认值为 false $args['hide_empty'] = true; // 隐藏没有文章的术语
include 只返回指定的术语 ID。 $args['include'] = array(1, 2, 3); // 只返回 ID 为 1, 2, 3 的术语
exclude 排除指定的术语 ID。 $args['exclude'] = array(4, 5, 6); // 排除 ID 为 4, 5, 6 的术语
slug 根据术语的 slug 进行筛选。 可以是单个 slug,也可以是 slug 数组。 $args['slug'] = 'my-term'; // 只返回 slug 为 ‘my-term’ 的术语 $args['slug'] = array('my-term', 'another-term'); // 返回 slug 为 ‘my-term’ 或 ‘another-term’ 的术语
search 搜索术语的名称或描述。 $args['search'] = 'keyword'; // 搜索名称或描述包含 ‘keyword’ 的术语
name__like 匹配类似特定名称的术语。 $args['name__like'] = 'key'; // 搜索名称类似 ‘key’ 的术语
description__like 匹配类似特定描述的术语。 $args['description__like'] = 'key'; // 搜索描述类似 ‘key’ 的术语
term_id 根据term_id进行筛选,可以是单个term_id,也可以是term_id数组 $args['term_id'] = 1; //只返回ID为1的术语 $args['term_id'] = array(1, 2, 3); // 只返回 ID 为 1, 2, 3 的术语
parent 只返回指定父级术语ID的子术语 $args['parent'] = 10; //只返回父级term_id为10的术语
hierarchical 是否以层级结构返回术语。默认值为 true $args['hierarchical'] = false; //不以层级结构返回术语

七、实战案例:复杂查询

假设我们需要查询 categorypost_tag 这两种分类法下,名称包含 "WordPress",并且至少关联了 5 篇文章的术语,按照文章数量降序排列,并且排除 ID 为 10 的术语。

$args = array(
    'taxonomy' => array( 'category', 'post_tag' ),
    'search' => 'WordPress',
    'hide_empty' => true, // 确保至少关联了一篇文章
    'orderby' => 'count',
    'order' => 'DESC',
    'exclude' => array( 10 ),
    'number' => 10, // 最多返回10个
    'hierarchical' => false //不以层级结构返回
);

$term_query = new My_WP_Term_Query( $args );

if ( $term_query->terms ) {
    foreach ( $term_query->terms as $term ) {
        echo "Term: " . $term->name . ", Count: " . $term->count . "n";
    }
} else {
    echo "没有找到任何术语n";
}

八、总结:WP_Term_Query 的力量

WP_Term_Query 是 WordPress 中一个非常强大的类,它可以让你轻松地查询和管理分类术语。 掌握 WP_Term_Query,你就可以构建出更加灵活和强大的 WordPress 应用。

今天的课就到这里,希望大家对 WP_Term_Query 有了更深入的了解。 下课!

发表回复

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