各位观众老爷,晚上好!今天咱们来聊聊 WordPress 里一个可能大家不太常用,但其实挺有意思的类:WP_Term_Meta_Query
。这玩意儿专门负责处理分类术语(Term)的元数据查询,说白了,就是给你提供一个更灵活的方式,按照分类术语的自定义字段来检索你想要的分类。
咱们先来个开胃小菜,简单介绍一下它的作用,然后深入源码,看看它是怎么运作的,最后再来点实际例子,保证大家听完之后,下次再遇到分类术语元数据查询的需求,心里有数,手上有招。
1. WP_Term_Meta_Query
是个啥?
在 WordPress 里,分类术语(比如文章的分类、标签)可以拥有自己的元数据(也就是自定义字段)。 假设你有一个“书籍分类”,你想给每个分类添加一个“封面颜色”的自定义字段。 那么,你就可以用 WP_Term_Meta_Query
来查找所有“封面颜色”是“红色”的分类。
听起来有点绕? 没关系,我们用表格来整理一下:
概念 | 解释 |
---|---|
分类术语 (Term) | WordPress里的分类、标签等,用于组织文章的内容。 |
元数据 (Meta) | 附加在分类术语上的额外信息,可以理解为自定义字段。比如,分类的“封面颜色”、“推荐指数”等等。 |
WP_Term_Meta_Query |
一个类,专门用来构建复杂的分类术语元数据查询条件。你可以用它来查找满足特定元数据条件的分类术语。 |
所以,WP_Term_Meta_Query
的作用就是:让你可以更精确地筛选分类术语,基于它们的元数据!
2. 源码剖析:WP_Term_Meta_Query
的内部世界
好了,废话不多说,咱们直接跳进源码,看看 WP_Term_Meta_Query
到底是怎么实现的。 源码位置在 wp-includes/class-wp-term-meta-query.php
。
(1) 构造函数 __construct()
首先,我们看看构造函数,它负责初始化查询的一些基本参数:
public function __construct( $meta_query = array(), $table_prefix = '' ) {
$this->table_prefix = $table_prefix;
if ( ! empty( $table_prefix ) ) {
$this->meta_table = $this->table_prefix . 'termmeta';
} else {
$this->meta_table = _wp_get_term_meta_table(); // 获得 termmeta 表名
}
if ( isset( $meta_query['relation'] ) && in_array( strtoupper( $meta_query['relation'] ), array( 'AND', 'OR' ), true ) ) {
$this->relation = strtoupper( $meta_query['relation'] );
} else {
$this->relation = 'AND'; // 默认关系是 AND
}
$this->queries = $this->sanitize_query( $meta_query ); // 清理查询参数
}
$meta_query
: 这是最重要的参数,一个数组,定义了你要查询的元数据条件。 稍后我们会详细讲解这个数组的结构。$table_prefix
: 数据库表前缀,一般情况下可以忽略,WordPress 会自动处理。$this->relation
: 定义了多个元数据条件之间的关系,可以是AND
(必须同时满足) 或者OR
(满足其中一个即可)。 默认是AND
。$this->queries
: 经过sanitize_query()
函数处理后的查询条件,目的是为了安全起见,清理一些不必要的参数。
(2) sanitize_query()
: 清理查询参数
这个函数负责对 $meta_query
数组进行清理和格式化:
protected function sanitize_query( $queries ) {
$cleaned_queries = array();
if ( ! is_array( $queries ) ) {
return $cleaned_queries;
}
foreach ( $queries as $key => $query ) {
if ( 'relation' === $key ) {
continue;
}
if ( is_array( $query ) ) {
$cleaned_queries[] = $this->sanitize_query( $query ); // 递归调用,处理嵌套的查询
continue;
}
if ( ! is_array( $query ) || ! isset( $query['key'] ) || ! isset( $query['value'] ) ) {
continue; // 忽略不合法的查询条件
}
$cleaned_queries[] = $query; // 添加合法的查询条件
}
return $cleaned_queries;
}
这个函数的主要作用是:
- 递归处理嵌套的查询条件。 也就是说,
$meta_query
数组可以包含其他的$meta_query
数组,形成一个树状结构。 - 验证查询条件是否合法。 一个合法的查询条件必须包含
key
(元数据键名) 和value
(元数据值)。
(3) get_sql()
: 生成 SQL 查询语句
这是 WP_Term_Meta_Query
最核心的函数,它负责根据 $this->queries
里的查询条件,生成最终的 SQL 查询语句。
public function get_sql( $primary_table, $primary_id_column, $context = '' ) {
global $wpdb;
$sql = $this->get_sql_clauses( $primary_table, $primary_id_column, $context );
if ( ! empty( $sql['where'] ) ) {
$sql = array(
'where' => ' AND ' . $sql['where'],
'join' => $sql['join'],
);
}
return $sql;
}
get_sql()
函数调用了 get_sql_clauses()
函数来生成 SQL 的 WHERE
和 JOIN
子句,然后将它们组合起来。
(4) get_sql_clauses()
: 生成 SQL 子句
public function get_sql_clauses( $primary_table, $primary_id_column, $context = '' ) {
global $wpdb;
$sql = array(
'where' => '1=1',
'join' => '',
);
$sql_chunks = $this->get_sql_for_query( $this->queries, $primary_table, $primary_id_column, $context );
if ( empty( $sql_chunks ) ) {
return $sql;
}
$sql['where'] = implode( ' ' . $this->relation . ' ', $sql_chunks );
$join = array();
foreach ( $this->query_clauses as $query ) {
if ( ! empty( $query['join'] ) ) {
$join[] = $query['join'];
}
}
if ( ! empty( $join ) ) {
$sql['join'] = implode( ' ', $join );
}
return $sql;
}
这个函数做了以下事情:
- 初始化 SQL 的
WHERE
和JOIN
子句。 - 调用
get_sql_for_query()
函数,为每个查询条件生成 SQL 片段。 - 将所有的 SQL 片段用
$this->relation
(AND 或者 OR) 连接起来,形成最终的WHERE
子句。 - 将所有查询条件需要的
JOIN
子句组合起来。
(5) get_sql_for_query()
: 为每个查询条件生成 SQL
protected function get_sql_for_query( $query, $primary_table, $primary_id_column, $context = '' ) {
global $wpdb;
$sql = array();
if ( ! is_array( $query ) ) {
return $sql;
}
foreach ( $query as $key => $clause ) {
if ( is_array( $clause ) ) {
$sql_chunks = $this->get_sql_for_query( $clause, $primary_table, $primary_id_column, $context );
if ( empty( $sql_chunks ) ) {
continue;
}
$sql[] = '(' . implode( ' ' . $this->relation . ' ', $sql_chunks ) . ')';
continue;
}
$this->query_clauses[ $this->query_id ] = $this->get_sql_clause( $clause, $primary_table, $primary_id_column, $context );
$sql[] = $this->query_clauses[ $this->query_id ]['where'];
$this->query_id++;
}
return $sql;
}
这个函数也是一个递归函数,负责处理嵌套的查询条件。 它会调用 get_sql_clause()
函数来为每个具体的查询条件生成 SQL 片段。
(6) get_sql_clause()
: 生成单个查询条件的 SQL
public function get_sql_clause( $query, $primary_table, $primary_id_column, $context = '' ) {
global $wpdb;
$sql = array(
'where' => '1=1',
'join' => '',
);
$meta_key = $query['key'];
$meta_value = $query['value'];
$compare = isset( $query['compare'] ) ? strtoupper( $query['compare'] ) : '='; // 默认比较运算符是 =
$type = isset( $query['type'] ) ? strtoupper( $query['type'] ) : 'CHAR'; // 默认数据类型是 CHAR
$meta_type = isset( $query['meta_type'] ) ? $query['meta_type'] : '';
$alias = 'mt' . $this->query_id;
$sql['join'] = " LEFT JOIN {$this->meta_table} AS {$alias} ON {$primary_table}.{$primary_id_column} = {$alias}.term_id";
$where = "$alias.meta_key = %s";
if($meta_type){
$where .= " AND {$alias}.meta_type = %s";
}
switch ( $compare ) {
case '=':
$where .= " AND {$alias}.meta_value = %s";
$args = array( $meta_key, $meta_value );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
case '!=':
$where .= " AND {$alias}.meta_value != %s";
$args = array( $meta_key, $meta_value );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
case '>':
case '>=':
case '<':
case '<=':
$where .= " AND {$alias}.meta_value {$compare} %s";
$args = array( $meta_key, $meta_value );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
case 'LIKE':
$where .= " AND {$alias}.meta_value LIKE %s";
$args = array( $meta_key, '%' . $wpdb->esc_like( $meta_value ) . '%' );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
case 'NOT LIKE':
$where .= " AND {$alias}.meta_value NOT LIKE %s";
$args = array( $meta_key, '%' . $wpdb->esc_like( $meta_value ) . '%' );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
case 'IN':
if ( ! is_array( $meta_value ) ) {
$meta_value = array( $meta_value );
}
$placeholders = array_fill( 0, count( $meta_value ), '%s' );
$where .= " AND {$alias}.meta_value IN (" . implode( ',', $placeholders ) . ")";
$args = array_merge( array( $meta_key ), $meta_value );
if($meta_type){
$args = array_merge(array($meta_key, $meta_type), $meta_value);
}
break;
case 'NOT IN':
if ( ! is_array( $meta_value ) ) {
$meta_value = array( $meta_value );
}
$placeholders = array_fill( 0, count( $meta_value ), '%s' );
$where .= " AND {$alias}.meta_value NOT IN (" . implode( ',', $placeholders ) . ")";
$args = array_merge( array( $meta_key ), $meta_value );
if($meta_type){
$args = array_merge(array($meta_key, $meta_type), $meta_value);
}
break;
case 'BETWEEN':
if ( ! is_array( $meta_value ) || count( $meta_value ) !== 2 ) {
break; // Invalid values
}
$where .= " AND {$alias}.meta_value BETWEEN %s AND %s";
$args = array( $meta_key, $meta_value[0], $meta_value[1] );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
case 'NOT BETWEEN':
if ( ! is_array( $meta_value ) || count( $meta_value ) !== 2 ) {
break; // Invalid values
}
$where .= " AND {$alias}.meta_value NOT BETWEEN %s AND %s";
$args = array( $meta_key, $meta_value[0], $meta_value[1] );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
case 'EXISTS':
$where = "{$alias}.meta_key = %s";
$args = array( $meta_key );
if($meta_type){
$where .= " AND {$alias}.meta_type = %s";
$args = array( $meta_key, $meta_type );
}
break;
case 'NOT EXISTS':
$sql['join'] = " LEFT JOIN {$this->meta_table} AS {$alias} ON {$primary_table}.{$primary_id_column} = {$alias}.term_id AND {$alias}.meta_key = %s";
if($meta_type){
$sql['join'] .= " AND {$alias}.meta_type = %s";
$args = array( $meta_key, $meta_type );
$where = "{$alias}.term_id IS NULL";
} else {
$args = array( $meta_key );
$where = "{$alias}.term_id IS NULL";
}
break;
default:
$where .= " AND {$alias}.meta_value = %s"; // 默认使用 =
$args = array( $meta_key, $meta_value );
if($meta_type){
array_splice($args, 1, 0, $meta_type);
}
break;
}
$sql['where'] = $wpdb->prepare( $where, $args );
$this->query_clauses[ $this->query_id ] = $sql;
return $sql;
}
这个函数是整个 WP_Term_Meta_Query
的灵魂所在! 它做了以下关键的事情:
- 构建
JOIN
子句: 将termmeta
表 (存储分类术语元数据的表) 和主表 (比如terms
表) 连接起来。 - 构建
WHERE
子句: 根据key
、value
、compare
和type
参数,生成具体的查询条件。 支持多种比较运算符 (=
,!=
,>
,<
,LIKE
,IN
,BETWEEN
等)。 - 使用
$wpdb->prepare()
函数,对 SQL 语句进行安全处理,防止 SQL 注入。
总结:WP_Term_Meta_Query
的工作流程
- 接收查询参数: 通过构造函数接收
$meta_query
数组,定义查询条件。 - 清理参数: 使用
sanitize_query()
函数,对查询参数进行清理和格式化。 - 生成 SQL: 调用
get_sql()
函数,生成最终的 SQL 查询语句。 - 构建 SQL 子句:
get_sql()
又调用get_sql_clauses()
函数,将查询条件分解成 SQL 的WHERE
和JOIN
子句。 - 处理每个查询条件:
get_sql_clauses()
调用get_sql_for_query()
函数,递归处理嵌套的查询条件。 - 生成单个条件的 SQL:
get_sql_for_query()
调用get_sql_clause()
函数,为每个具体的查询条件生成 SQL 片段,包括JOIN
和WHERE
子句。 - 安全处理:
get_sql_clause()
使用$wpdb->prepare()
函数,对 SQL 语句进行安全处理。
3. 实战演练:如何使用 WP_Term_Meta_Query
光说不练假把式,咱们来几个实际的例子,看看怎么用 WP_Term_Meta_Query
。
(1) 查找 "封面颜色" 是 "红色" 的分类
$args = array(
'taxonomy' => 'category', // 指定分类法
'meta_query' => array(
array(
'key' => 'cover_color', // 元数据键名
'value' => 'red', // 元数据值
'compare' => '=', // 比较运算符,默认是 =
),
),
);
$terms = get_terms( $args );
if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
echo '<ul>';
foreach ( $terms as $term ) {
echo '<li>' . esc_html( $term->name ) . '</li>';
}
echo '</ul>';
} else {
echo '没有找到符合条件的分类。';
}
在这个例子中,我们使用了 get_terms()
函数,并传入了 meta_query
参数。 meta_query
是一个数组,包含一个子数组,定义了我们要查询的元数据条件:
key
:cover_color
,表示我们要查询的元数据键名是 "cover_color"。value
:red
,表示我们要查询的元数据值是 "red"。compare
:=
,表示我们要查找 "cover_color" 等于 "red" 的分类。
(2) 查找 "推荐指数" 大于 8 的分类
$args = array(
'taxonomy' => 'category',
'meta_query' => array(
array(
'key' => 'recommendation_index',
'value' => 8,
'compare' => '>', // 比较运算符,大于
'type' => 'NUMERIC', // 数据类型,数值型
),
),
);
$terms = get_terms( $args );
// ... (后续代码同上)
这个例子和上一个例子类似,但是使用了不同的比较运算符 >
(大于),并且指定了 type
为 NUMERIC
,表示我们要比较的是数值型数据。 如果不指定 type
,WordPress 默认会把元数据值当做字符串来比较,可能会导致错误的结果。
(3) 查找 "作者" 是 "张三" 或者 "李四" 的分类
$args = array(
'taxonomy' => 'category',
'meta_query' => array(
'relation' => 'OR', // 多个条件之间的关系是 OR
array(
'key' => 'author',
'value' => '张三',
'compare' => '=',
),
array(
'key' => 'author',
'value' => '李四',
'compare' => '=',
),
),
);
$terms = get_terms( $args );
// ... (后续代码同上)
在这个例子中,我们使用了 relation
参数,指定多个元数据条件之间的关系是 OR
,表示只要满足其中一个条件,就返回该分类。
(4) 查找 "封面颜色" 是 "红色" 并且 "推荐指数" 大于 8 的分类
$args = array(
'taxonomy' => 'category',
'meta_query' => array(
'relation' => 'AND', // 多个条件之间的关系是 AND,默认是 AND
array(
'key' => 'cover_color',
'value' => 'red',
'compare' => '=',
),
array(
'key' => 'recommendation_index',
'value' => 8,
'compare' => '>',
'type' => 'NUMERIC',
),
),
);
$terms = get_terms( $args );
// ... (后续代码同上)
这个例子和上一个例子类似,但是使用了 relation
参数,指定多个元数据条件之间的关系是 AND
,表示必须同时满足所有条件,才返回该分类。
(5) 嵌套查询:查找 "系列" 是 "哈利波特" 并且 ("封面颜色" 是 "红色" 或者 "封面材质" 是 "皮革") 的分类
$args = array(
'taxonomy' => 'category',
'meta_query' => array(
'relation' => 'AND',
array(
'key' => 'series',
'value' => '哈利波特',
'compare' => '=',
),
array(
'relation' => 'OR',
array(
'key' => 'cover_color',
'value' => 'red',
'compare' => '=',
),
array(
'key' => 'cover_material',
'value' => '皮革',
'compare' => '=',
),
),
),
);
$terms = get_terms( $args );
// ... (后续代码同上)
这个例子展示了如何使用嵌套的 meta_query
数组,构建更复杂的查询条件。 meta_query
数组可以包含其他的 meta_query
数组,形成一个树状结构,从而实现更灵活的查询。
(6) 使用 EXISTS 和 NOT EXISTS: 查找 存在 "封面颜色" 元数据键的分类 或者 不存在 "封面颜色" 元数据键的分类
//查找存在"封面颜色"元数据键的分类
$args = array(
'taxonomy' => 'category',
'meta_query' => array(
array(
'key' => 'cover_color',
'compare' => 'EXISTS',
),
),
);
$terms = get_terms( $args );
//查找不存在"封面颜色"元数据键的分类
$args = array(
'taxonomy' => 'category',
'meta_query' => array(
array(
'key' => 'cover_color',
'compare' => 'NOT EXISTS',
),
),
);
$terms = get_terms( $args );
在这个例子中,我们使用了 EXISTS
和 NOT EXISTS
比较运算符来查找是否存在某个元数据键。 这在某些情况下非常有用,比如你想查找所有设置了 "封面颜色" 的分类,或者所有没有设置 "封面颜色" 的分类。
4. 注意事项
- 性能问题: 复杂的
meta_query
查询可能会影响性能,特别是当数据量很大时。 尽量避免使用过于复杂的查询条件,并考虑使用缓存来提高性能。 - 数据类型: 确保
type
参数设置正确,特别是当比较数值型或日期型数据时。 - 安全问题: 使用
$wpdb->prepare()
函数对 SQL 语句进行安全处理,防止 SQL 注入。 - 调试: 可以使用
echo $wpdb->last_query;
来查看 WordPress 生成的 SQL 查询语句,方便调试。
5. 总结
WP_Term_Meta_Query
是一个强大的工具,可以让你更灵活地查询分类术语的元数据。 掌握了它的使用方法,你就可以构建更复杂的分类筛选条件,从而更好地组织和管理你的 WordPress 内容。
希望今天的讲座对大家有所帮助! 以后遇到分类术语元数据查询的问题,不要慌,想想 WP_Term_Meta_Query
,它会是你的得力助手。
散会! 各位观众老爷,下次再见!