分析 `WP_Term_Meta_Query` 类的源码,它是如何处理分类术语元数据的查询条件的?

咳咳,各位观众老爷们,晚上好!今天咱们来聊聊 WordPress 里的一个“隐藏Boss”—— WP_Term_Meta_Query 类。这哥们儿专门负责处理分类术语元数据的查询,听起来是不是有点枯燥?别急,我保证用最接地气的方式,把这玩意儿扒个底朝天,让你们以后面对它的时候,就像见到老朋友一样亲切。

一、啥是分类术语元数据?

在深入源码之前,咱们先得搞清楚,啥是分类术语元数据。简单来说,就是给你的分类、标签这些玩意儿,加上一些额外的信息。

举个栗子:

  • 你有个分类叫“美食攻略”,你想给它加个元数据,说明“这个分类下的文章都是关于吃的”。
  • 你有个标签叫“旅行”,你想给它加个元数据,说明“这个标签下的文章适合喜欢冒险的人”。

这些额外的信息,就叫做分类术语元数据。 它们存储在 wp_termmeta 表里。

二、WP_Term_Meta_Query:元数据查询的幕后英雄

WP_Term_Meta_Query 类是 WordPress 用来处理 wp_termmeta 表查询的核心类。 它可以让你像操作文章元数据一样,使用灵活的查询条件来获取特定的分类术语元数据。

三、源码剖析:WP_Term_Meta_Query 的内部构造

咱们现在就来深入 WP_Term_Meta_Query 的源码,看看它到底是怎么工作的。

  1. 构造函数 __construct( $meta_query = false )

    这是类的“出生点”,负责初始化一些基本的参数。

    public function __construct( $meta_query = false ) {
        if ( is_array( $meta_query ) && isset( $meta_query['relation'] ) ) {
            $this->relation = strtoupper( $meta_query['relation'] );
            if ( ! in_array( $this->relation, array( 'AND', 'OR' ), true ) ) {
                $this->relation = 'AND';
            }
        } else {
            $this->relation = 'AND';
        }
    
        if ( isset( $meta_query['queries'] ) ) {
            $meta_query = $meta_query['queries'];
        }
    
        $this->queries = $this->normalize_query( $meta_query );
    }
    • $meta_query:这是传入的查询参数,可以是一个数组,用来定义各种查询条件。
    • $this->relation:定义多个查询条件之间的关系,可以是 ‘AND’ (所有条件都满足) 或 ‘OR’ (满足任意一个条件)。 默认是 ‘AND’。
    • $this->queries:经过 normalize_query() 函数处理后的标准化查询条件。

    代码解读:

    • 首先,它会检查 $meta_query 是否是一个数组,并且是否设置了 relation 键。 relation 决定了多个查询条件之间的逻辑关系。
    • 如果 relation 的值不是 ‘AND’ 或 ‘OR’, 那么它会默认设置为 ‘AND’。
    • 如果 $meta_query 中包含了 queries 键, 那么它会将 $meta_query 的值设置为 queries 的值。 这样做是为了兼容一些旧的查询格式。
    • 最后,它会调用 normalize_query() 函数来标准化查询条件。
  2. normalize_query( $query ):查询条件的标准化

    这个函数负责将传入的查询条件转换成统一的格式,方便后续的处理。

    protected function normalize_query( $query ) {
        $normalized = array();
    
        if ( ! is_array( $query ) ) {
            return $normalized;
        }
    
        foreach ( $query as $key => $value ) {
            if ( 'relation' === $key ) {
                continue;
            }
    
            if ( is_array( $value ) ) {
                $value = $this->normalize_query( $value );
                if ( ! empty( $value ) ) {
                    $normalized[] = $value;
                }
            } else {
                $normalized[] = $this->validate_query( $query );
                break;
            }
        }
    
        return $normalized;
    }

    代码解读:

    • 这个函数会递归地处理查询条件,确保所有的条件都是一个数组。
    • 如果查询条件中包含了 relation 键, 那么它会忽略这个键。
    • 如果查询条件是一个数组, 那么它会递归地调用 normalize_query() 函数来处理这个数组。
    • 如果查询条件不是一个数组, 那么它会调用 validate_query() 函数来验证这个条件。
  3. validate_query( $query ):查询条件的验证

    这个函数负责验证查询条件的合法性,确保没有错误的参数。

    protected function validate_query( $query ) {
        $defaults = array(
            'key'       => '',
            'value'     => '',
            'compare'   => '=',
            'type'      => 'CHAR',
        );
    
        $query = wp_parse_args( $query, $defaults );
    
        $query['key']   = trim( $query['key'] );
        $query['type']  = strtoupper( $query['type'] );
        $query['compare'] = strtoupper( $query['compare'] );
    
        if ( ! $query['key'] ) {
            return false;
        }
    
        if ( ! in_array( $query['type'], array( 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'TIME' ), true ) ) {
            $query['type'] = 'CHAR';
        }
    
        $valid_comparisons = array( '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', 'RLIKE' );
        if ( ! in_array( $query['compare'], $valid_comparisons, true ) ) {
            $query['compare'] = '=';
        }
    
        return $query;
    }

    代码解读:

    • 这个函数首先定义了一个默认的查询条件数组 $defaults
    • 然后,它使用 wp_parse_args() 函数将传入的查询条件和默认的查询条件合并起来。
    • 接下来,它会对查询条件中的 key, type, compare 进行验证,确保它们的值是合法的。
    • 如果 key 的值为空, 那么它会返回 false
    • 如果 type 的值不是 NUMERIC, BINARY, CHAR, DATE, DATETIME, TIME 中的一个, 那么它会将 type 的值设置为 CHAR
    • 如果 compare 的值不是 =', !=, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, REGEXP, NOT REGEXP, RLIKE 中的一个, 那么它会将 compare 的值设置为 =
    • 最后,它会返回验证后的查询条件。
  4. get_sql( $meta_table, $primary_table, $primary_id_column, $context = false ):生成 SQL 查询语句

    这是最关键的函数,负责根据查询条件生成实际的 SQL 查询语句。

    public function get_sql( $meta_table, $primary_table, $primary_id_column, $context = false ) {
        global $wpdb;
    
        $sql = $this->get_sql_clauses( $meta_table, $primary_table, $primary_id_column, $context );
    
        $sql['where'] = 'AND ' . $sql['where'];
    
        return array(
            'where'  => $sql['where'],
            'join'   => $sql['join'],
        );
    }

    代码解读:

    • 这个函数会调用 get_sql_clauses() 函数来获取 SQL 查询语句的各个部分, 包括 where 子句和 join 子句。
    • 然后,它会将 where 子句和 AND 关键字拼接起来。
    • 最后,它会返回一个包含 where 子句和 join 子句的数组。
  5. get_sql_clauses( $meta_table, $primary_table, $primary_id_column, $context = false ):构建 SQL 子句

    这个函数负责构建 SQL 查询语句的各个部分,包括 where 子句和 join 子句。

    public function get_sql_clauses( $meta_table, $primary_table, $primary_id_column, $context = false ) {
        global $wpdb;
    
        $sql = array(
            'where'  => '1=1',
            'join'   => '',
        );
    
        $clauses = $this->get_sql_for_query( $this->queries, $meta_table, $primary_table, $primary_id_column, $context );
    
        if ( ! empty( $clauses['where'] ) ) {
            $sql['where'] = '(' . implode( " ) {$this->relation} ( ", $clauses['where'] ) . ')';
        }
    
        if ( ! empty( $clauses['join'] ) ) {
            $sql['join'] = implode( ' ', $clauses['join'] );
        }
    
        return $sql;
    }

    代码解读:

    • 这个函数首先初始化一个包含 where 子句和 join 子句的数组 $sql
    • 然后,它会调用 get_sql_for_query() 函数来获取每个查询条件的 SQL 子句。
    • 接下来,它会将每个查询条件的 where 子句用 $this->relation 连接起来。
    • 最后,它会将每个查询条件的 join 子句拼接起来。
  6. get_sql_for_query( $query, $meta_table, $primary_table, $primary_id_column, $context = false ):递归生成 SQL 子句

    这个函数负责递归地生成每个查询条件的 SQL 子句。

    protected function get_sql_for_query( $query, $meta_table, $primary_table, $primary_id_column, $context = false ) {
        global $wpdb;
    
        $sql = array(
            'where'  => array(),
            'join'   => array(),
        );
    
        foreach ( $query as $clause ) {
            if ( is_array( $clause ) ) {
                $clause_sql = $this->get_sql_for_query( $clause, $meta_table, $primary_table, $primary_id_column, $context );
    
                $sql['where']  = array_merge( $sql['where'], $clause_sql['where'] );
                $sql['join']   = array_merge( $sql['join'], $clause_sql['join'] );
            } else {
                $where = $this->get_sql_for_clause( $clause, $meta_table, $primary_table, $primary_id_column, $context );
    
                if ( ! empty( $where ) ) {
                    $sql['where'][] = $where;
                }
            }
        }
    
        return $sql;
    }

    代码解读:

    • 这个函数会递归地处理查询条件,如果查询条件是一个数组, 那么它会递归地调用 get_sql_for_query() 函数来处理这个数组。
    • 如果查询条件不是一个数组, 那么它会调用 get_sql_for_clause() 函数来获取这个查询条件的 SQL 子句。
  7. get_sql_for_clause( $clause, $meta_table, $primary_table, $primary_id_column, $context = false ):生成单个查询条件的 SQL 子句

    这个函数负责生成单个查询条件的 SQL 子句。

    protected function get_sql_for_clause( $clause, $meta_table, $primary_table, $primary_id_column, $context = false ) {
        global $wpdb;
    
        $where = '';
    
        $meta_key = esc_sql( $clause['key'] );
        $meta_value = $clause['value'];
        $compare = $clause['compare'];
        $meta_type = $clause['type'];
    
        $column = 'meta_value';
    
        switch ( $meta_type ) {
            case 'NUMERIC':
                $column = 'meta_value+0';
                break;
            case 'BINARY':
                $column = 'BINARY meta_value';
                break;
        }
    
        switch ( $compare ) {
            case 'IN':
            case 'NOT IN':
                if ( ! is_array( $meta_value ) ) {
                    $meta_value = preg_split( '/[,s]+/', $meta_value );
                }
    
                $meta_value = array_map( 'esc_sql', $meta_value );
                $meta_value = "'" . implode( "', '", $meta_value ) . "'";
    
                $where = "$meta_table.meta_key = '$meta_key' AND $column " . $compare . " ($meta_value)";
                break;
            case 'BETWEEN':
            case 'NOT BETWEEN':
                if ( ! is_array( $meta_value ) || count( $meta_value ) != 2 ) {
                    return '';
                }
    
                $meta_value[0] = esc_sql( $meta_value[0] );
                $meta_value[1] = esc_sql( $meta_value[1] );
    
                $where = "$meta_table.meta_key = '$meta_key' AND $column " . $compare . " ('{$meta_value[0]}' AND '{$meta_value[1]}')";
                break;
            case 'LIKE':
            case 'NOT LIKE':
                $meta_value = esc_sql( like_escape( $meta_value ) );
                $where = "$meta_table.meta_key = '$meta_key' AND $column $compare '%$meta_value%'";
                break;
            case 'REGEXP':
            case 'NOT REGEXP':
            case 'RLIKE':
                $meta_value = esc_sql( $meta_value );
                $where = "$meta_table.meta_key = '$meta_key' AND $column $compare '$meta_value'";
                break;
            default:
                $meta_value = esc_sql( $meta_value );
                $where = "$meta_table.meta_key = '$meta_key' AND $column $compare '$meta_value'";
                break;
        }
    
        return $where;
    }

    代码解读:

    • 这个函数会根据查询条件中的 key, value, compare, type 来生成 SQL 子句。
    • 它会使用 esc_sql() 函数来转义查询条件中的值,防止 SQL 注入。
    • 它会根据 type 的值来选择不同的列名,例如 NUMERIC 类型会使用 meta_value+0BINARY 类型会使用 BINARY meta_value
    • 它会根据 compare 的值来生成不同的比较运算符,例如 IN, NOT IN, BETWEEN, NOT BETWEEN, LIKE, NOT LIKE, REGEXP, NOT REGEXP, RLIKE

四、使用示例:查询分类术语元数据

现在,咱们来演示一下如何使用 WP_Term_Meta_Query 类来查询分类术语元数据。

<?php
// 1. 构建查询参数
$meta_query = array(
    'relation' => 'AND', // 多个条件之间的关系:AND (默认), OR
    array(
        'key'     => 'color', // 元数据键名
        'value'   => 'red',   // 元数据值
        'compare' => '=',     // 比较运算符:=, !=, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN, BETWEEN, NOT BETWEEN, REGEXP, NOT REGEXP, RLIKE
        'type'    => 'CHAR',  // 元数据类型:NUMERIC, BINARY, CHAR (默认), DATE, DATETIME, TIME
    ),
    array(
        'key'     => 'size',
        'value'   => array( 10, 20 ),
        'compare' => 'BETWEEN',
        'type'    => 'NUMERIC',
    ),
);

// 2. 创建 WP_Term_Meta_Query 对象
$meta_query_obj = new WP_Term_Meta_Query( $meta_query );

// 3. 获取 SQL 查询语句
$sql = $meta_query_obj->get_sql( 'wp_termmeta', 'wp_terms', 'term_id' );

// 4. 构建完整的 SQL 查询语句
global $wpdb;
$term_ids = $wpdb->get_col( "
    SELECT tm.term_id
    FROM wp_termmeta tm
    {$sql['join']}
    WHERE 1=1 {$sql['where']}
" );

// 5. 处理查询结果
if ( ! empty( $term_ids ) ) {
    echo "符合条件的分类术语 ID:<br>";
    foreach ( $term_ids as $term_id ) {
        echo $term_id . "<br>";
    }
} else {
    echo "没有找到符合条件的分类术语。";
}
?>

代码解读:

  • 首先,我们构建了一个 $meta_query 数组,定义了两个查询条件。
    • 第一个条件是 color 元数据的值等于 red
    • 第二个条件是 size 元数据的值在 1020 之间。
  • 然后,我们创建了一个 WP_Term_Meta_Query 对象,并将 $meta_query 数组作为参数传递给它。
  • 接下来,我们调用 get_sql() 函数来获取 SQL 查询语句。
  • 最后,我们使用 $wpdb->get_col() 函数来执行 SQL 查询,并将查询结果输出到页面上。

五、WP_Term_Meta_Query 的优点

  • 灵活性: 允许你使用各种查询条件来过滤分类术语元数据。
  • 安全性: 自动转义查询条件中的值,防止 SQL 注入。
  • 可扩展性: 可以与其他 WordPress API 结合使用,实现更复杂的功能。

六、WP_Term_Meta_Query 的局限性

  • 性能: 对于大型数据集,复杂的查询可能会影响性能。
  • 学习曲线: 需要一定的 SQL 知识才能灵活使用。

七、总结

WP_Term_Meta_Query 类是 WordPress 中一个强大的工具,可以让你轻松地查询分类术语元数据。 掌握它的使用方法,可以让你更好地管理你的 WordPress 网站。

表格总结:

函数名 功能
__construct() 初始化查询参数,设置查询条件之间的关系。
normalize_query() 将查询条件转换为统一的格式,方便后续处理。
validate_query() 验证查询条件的合法性,确保没有错误的参数。
get_sql() 生成 SQL 查询语句,返回包含 where 子句和 join 子句的数组。
get_sql_clauses() 构建 SQL 查询语句的各个部分,包括 where 子句和 join 子句。
get_sql_for_query() 递归地生成每个查询条件的 SQL 子句。
get_sql_for_clause() 生成单个查询条件的 SQL 子句,包括 meta_key, meta_value, compare, meta_type 等。

好了,今天的讲座就到这里。希望大家以后看到 WP_Term_Meta_Query 这位老朋友,不会再感到陌生和恐惧了! 咱们下次再见!

发表回复

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