各位观众老爷,早上好!今天咱们来聊聊 WordPress 里一个非常重要的类——WP_Tax_Query
。这玩意儿就像个魔法师,能把咱们定义的分类、标签查询条件,变成数据库能理解的 SQL 代码,从而筛选出我们想要的文章。
一、 啥是 WP_Tax_Query
?为什么要研究它?
简单来说,WP_Tax_Query
就是 WordPress 用来处理分类法(Taxonomy)查询的类。当你用 WP_Query
查询文章,并且需要根据分类、标签、自定义分类法进行筛选时,WP_Tax_Query
就在背后默默工作。
研究它干啥?
- 定制化查询: 深入理解
WP_Tax_Query
,你就能写出更复杂、更精准的分类法查询,满足各种奇葩需求。 - 性能优化: 了解它如何生成 SQL,你可以避免写出低效的查询条件,提升网站速度。
- 调试问题: 当你的分类法查询出现问题时,理解
WP_Tax_Query
能帮你更快地找到问题根源。
二、 WP_Tax_Query
的基本结构
WP_Tax_Query
接收一个参数 $tax_query
,这个参数是一个数组,用来描述你的分类法查询条件。 比如:
$args = array(
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'news', 'featured' ),
'operator' => 'IN',
),
),
);
$query = new WP_Query( $args );
上面的代码表示:查询属于 category
分类下 slug
为 news
或 featured
的文章。
$tax_query
数组可以包含多个子数组,每个子数组代表一个分类法查询条件。 还可以有关联关系,表示多个分类法条件之间的 AND
,OR
关系。
三、 WP_Tax_Query
的源码剖析
好,现在咱们开始扒 WP_Tax_Query
的源码,看看它到底是怎么工作的。
首先,找到 wp-includes/class-wp-tax-query.php
文件。
-
构造函数
__construct( $tax_query )
构造函数主要负责初始化
WP_Tax_Query
对象,并解析传入的$tax_query
参数。public function __construct( $tax_query ) { if ( isset( $tax_query['relation'] ) && in_array( strtoupper( $tax_query['relation'] ), array( 'AND', 'OR' ), true ) ) { $this->relation = strtoupper( $tax_query['relation'] ); } else { $this->relation = 'AND'; } if ( isset( $tax_query[0] ) && is_array( $tax_query[0] ) ) { $this->queries = $this->normalize_queries( $tax_query ); } else { $this->queries = array( $tax_query ); } $this->queries = $this->fill_query_defaults( $this->queries ); }
$this->relation
: 记录查询条件之间的关系,默认为AND
。 就像SQL语句中的AND
和OR
。$this->queries
: 存储解析后的查询条件数组。normalize_queries()
: 标准化查询条件数组,确保每个条件都是一个数组。fill_query_defaults()
: 为每个查询条件填充默认值,比如默认的operator
是IN
。
-
normalize_queries( $queries )
这个函数负责将
$tax_query
参数标准化,确保每个查询条件都是一个数组。 它的作用是处理嵌套的$tax_query
数组。private function normalize_queries( $queries ) { $normalized = array(); foreach ( $queries as $key => $query ) { if ( 'relation' === $key ) { $normalized['relation'] = strtoupper( $query ); continue; } if ( ! is_array( $query ) ) { continue; } $keys = array_keys( $query ); sort( $keys ); if ( $keys === array( 'field', 'taxonomy', 'terms' ) || $keys === array( 'field', 'operator', 'taxonomy', 'terms' ) ) { $normalized[] = $query; } elseif ( isset( $query[0] ) && is_array( $query[0] ) ) { // It's an embedded array, parse each query. $normalized[] = $this->normalize_queries( $query ); } } return $normalized; }
-
fill_query_defaults( $queries )
这个函数为每个查询条件填充默认值。 如果用户没有指定
operator
,就默认为IN
。private function fill_query_defaults( $queries ) { $defaults = array( 'taxonomy' => '', 'field' => 'term_id', 'terms' => array(), 'operator' => 'IN', ); foreach ( $queries as $key => $query ) { if ( isset( $query['relation'] ) ) { continue; } $queries[ $key ] = array_merge( $defaults, $query ); } return $queries; }
-
get_sql( $table_prefix, $query, $type = 'AND' )
这个函数是
WP_Tax_Query
的核心,它负责生成 SQL 的JOIN
和WHERE
子句。public function get_sql( $table_prefix, $query, $type = 'AND' ) { // 省略一部分代码,关注核心逻辑 $sql = $this->get_sql_clauses( $table_prefix, $query ); $sql['where'] = 'AND ' . $sql['where']; return $sql; }
get_sql()
函数调用了get_sql_clauses()
函数来生成 SQL 子句。 -
get_sql_clauses( $table_prefix, $query )
这个函数递归地处理查询条件,生成 SQL 的
JOIN
和WHERE
子句。public function get_sql_clauses( $table_prefix, $query ) { $sql = array( 'where' => 'AND 1=1', 'join' => '', ); $sql_chunks = array( 'relation' => 'AND', 'where' => array(), 'join' => array(), ); $taxonomies = array(); $i = 0; foreach ( $this->queries as $tax_query ) { $i++; if ( ! is_array( $tax_query ) ) { continue; } if ( isset( $tax_query['relation'] ) ) { $sql_chunks['relation'] = $tax_query['relation']; continue; } $taxonomy = $tax_query['taxonomy']; $field = $tax_query['field']; $terms = $tax_query['terms']; $operator = $tax_query['operator']; if ( ! taxonomy_exists( $taxonomy ) ) { continue; } if ( empty( $terms ) ) { continue; } $taxonomies[] = $taxonomy; $base_table = "{$table_prefix}term_relationships"; $alias = 'tr' . $i; // 别名 $terms_table = "{$table_prefix}terms"; $term_taxonomy_table = "{$table_prefix}term_taxonomy"; $sql_chunks['join'][] = "INNER JOIN {$base_table} AS {$alias} ON ($query.ID = {$alias}.object_id)"; $sql_chunks['join'][] = "INNER JOIN {$term_taxonomy_table} AS tt{$i} ON ({$alias}.term_taxonomy_id = tt{$i}.term_taxonomy_id)"; $sql_chunks['join'][] = "INNER JOIN {$terms_table} AS t{$i} ON (tt{$i}.term_id = t{$i}.term_id)"; switch ( $field ) { case 'term_id': $terms_placeholder = implode( ',', array_map( 'intval', $terms ) ); break; case 'name': case 'slug': $terms_placeholder = "'" . implode( "', '", array_map( 'esc_sql', $terms ) ) . "'"; break; case 'term_taxonomy_id': $terms_placeholder = implode( ',', array_map( 'intval', $terms ) ); break; default: continue 2; // Skip this query. } switch ( $operator ) { case 'IN': $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy' AND t{$i}.{$field} IN ($terms_placeholder)"; break; case 'NOT IN': $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy' AND t{$i}.{$field} NOT IN ($terms_placeholder)"; break; case 'AND': $terms_placeholder = "'" . implode( "' AND t{$i}.{$field} = '", array_map( 'esc_sql', $terms ) ) . "'"; $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy' AND t{$i}.{$field} = $terms_placeholder"; break; case 'EXISTS': $sql_chunks['where'][] = "tt{$i}.taxonomy = '$taxonomy'"; break; case 'NOT EXISTS': $sql_chunks['where'][] = "tt{$i}.taxonomy <> '$taxonomy'"; break; default: continue 2; // Skip this query. } } $sql['join'] = array_unique( $sql_chunks['join'] ); $sql['join'] = implode( "n", $sql['join'] ); $sql['where'] = implode( " {$sql_chunks['relation']} ", $sql_chunks['where'] ); return $sql; }
这个函数做了以下几件事:
- 循环处理每个查询条件: 遍历
$this->queries
数组,处理每个分类法查询条件。 - 生成
JOIN
子句: 根据分类法和文章的关系,生成JOIN
子句,连接wp_term_relationships
,wp_term_taxonomy
,wp_terms
表。 关键在于给每个JOIN的表定义别名,例如tr1
、tt1
、t1
, 以便在WHERE子句中引用。 - 生成
WHERE
子句: 根据field
、terms
和operator
,生成WHERE
子句,筛选出符合条件的文章。 这里会根据不同的field
和operator
生成不同的SQL语句。 - 处理
AND
和OR
关系: 如果$tax_query
中有relation
字段,则根据AND
或OR
关系,将多个WHERE
子句连接起来。
- 循环处理每个查询条件: 遍历
四、 举例说明
咱们还是用最开始的例子:
$args = array(
'tax_query' => array(
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'news', 'featured' ),
'operator' => 'IN',
),
),
);
$query = new WP_Query( $args );
经过 WP_Tax_Query
的处理,最终生成的 SQL 如下(简化版):
SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_term_relationships AS tr1 ON (wp_posts.ID = tr1.object_id)
INNER JOIN wp_term_taxonomy AS tt1 ON (tr1.term_taxonomy_id = tt1.term_taxonomy_id)
INNER JOIN wp_terms AS t1 ON (tt1.term_id = t1.term_id)
WHERE 1=1
AND tt1.taxonomy = 'category'
AND t1.slug IN ('news', 'featured')
AND wp_posts.post_type = 'post'
AND ((wp_posts.post_status = 'publish'))
ORDER BY wp_posts.post_date DESC
可以看到,WP_Tax_Query
成功地将分类法查询条件转换成了 SQL 的 JOIN
和 WHERE
子句。
五、 复杂查询示例
假设我们要查询同时属于 category
分类下的 news
和 featured
,并且属于 post_tag
标签下的 important
的文章。
$args = array(
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'news', 'featured' ),
'operator' => 'AND',
),
array(
'taxonomy' => 'post_tag',
'field' => 'slug',
'terms' => array( 'important' ),
'operator' => 'IN',
),
),
);
$query = new WP_Query( $args );
最终生成的 SQL 如下(简化版):
SELECT wp_posts.*
FROM wp_posts
INNER JOIN wp_term_relationships AS tr1 ON (wp_posts.ID = tr1.object_id)
INNER JOIN wp_term_taxonomy AS tt1 ON (tr1.term_taxonomy_id = tt1.term_taxonomy_id)
INNER JOIN wp_terms AS t1 ON (tt1.term_id = t1.term_id)
INNER JOIN wp_term_relationships AS tr2 ON (wp_posts.ID = tr2.object_id)
INNER JOIN wp_term_taxonomy AS tt2 ON (tr2.term_taxonomy_id = tt2.term_taxonomy_id)
INNER JOIN wp_terms AS t2 ON (tt2.term_id = t2.term_id)
WHERE 1=1
AND ( tt1.taxonomy = 'category' AND t1.slug = 'news' AND t1.slug = 'featured' )
AND tt2.taxonomy = 'post_tag' AND t2.slug IN ('important')
AND wp_posts.post_type = 'post'
AND ((wp_posts.post_status = 'publish'))
ORDER BY wp_posts.post_date DESC
注意:
'relation' => 'AND'
指定了多个分类法条件之间的关系。category
的operator
为AND
,表示文章必须同时属于news
和featured
这两个分类。post_tag
的operator
为IN
,表示文章属于important
这个标签。
六、 WP_Tax_Query
的高级用法
-
使用
EXISTS
和NOT EXISTS
EXISTS
和NOT EXISTS
可以用来判断文章是否属于某个分类法。$args = array( 'tax_query' => array( array( 'taxonomy' => 'category', 'operator' => 'EXISTS', // 查询属于 category 分类的文章 ), ), ); $query = new WP_Query( $args );
-
使用嵌套的
tax_query
WP_Tax_Query
支持嵌套的tax_query
,可以实现更复杂的查询逻辑。$args = array( 'tax_query' => array( 'relation' => 'OR', array( 'taxonomy' => 'category', 'field' => 'slug', 'terms' => array( 'news' ), ), array( 'relation' => 'AND', array( 'taxonomy' => 'post_tag', 'field' => 'slug', 'terms' => array( 'important' ), ), array( 'taxonomy' => 'custom_taxonomy', 'field' => 'term_id', 'terms' => array( 123 ), ), ), ), ); $query = new WP_Query( $args );
上面的代码表示:查询属于
category
分类下的news
,或者同时属于post_tag
标签下的important
并且属于custom_taxonomy
分类下的 term_id 为 123 的文章。
七、 性能优化建议
- 避免使用
name
作为field
:name
字段没有索引,查询效率较低。 尽量使用term_id
或slug
。 - 尽量使用
IN
操作符:IN
操作符的效率通常比多个OR
操作符要高。 - 避免过度复杂的查询: 复杂的查询会导致 SQL 语句过于庞大,影响性能。 可以考虑将复杂的查询拆分成多个简单的查询。
- 利用缓存: 对查询结果进行缓存,可以减少数据库的访问次数,提高网站速度。
八、 总结
WP_Tax_Query
是 WordPress 中一个非常强大的类,它能让我们灵活地控制分类法查询。 理解 WP_Tax_Query
的工作原理,可以帮助我们写出更高效、更精准的查询代码。
记住,掌握了 WP_Tax_Query
,就像掌握了一门魔法,能让你的 WordPress 网站更加强大! 今天就到这里,希望大家有所收获! 下课!