嘿,大家好!今天咱们来聊聊 WordPress 里的一个“老朋友”—— get_terms()
函数。别看它名字简单,背后可藏着不少门道。今天咱们就扒开它的源码,看看它是怎么调兵遣将,最终把我们想要的分类术语给揪出来的。
咱们的目标是:搞清楚 get_terms()
是如何巧妙地利用 WP_Term_Query
类来实现分类术语查询的。
第一幕:get_terms()
函数闪亮登场
首先,我们得找到 get_terms()
函数的真身。它就藏在 wp-includes/taxonomy.php
文件里。
function get_terms( $args = '', $deprecated = '' ) {
global $wpdb, $_wp_term_hierarchy;
$defaults = array(
'taxonomy' => 'category',
'orderby' => 'name',
'order' => 'ASC',
'hide_empty' => true,
'exclude' => array(),
'exclude_tree' => array(),
'include' => array(),
'number' => '',
'fields' => 'all',
'slug' => '',
'parent' => '',
'hierarchical' => true,
'search' => '',
'name__like' => '',
'description__like' => '',
'pad_counts' => false,
'offset' => '',
'child_of' => 0,
'childless' => false,
'get' => '',
'name' => '',
'term_taxonomy_id' => '',
'update_term_meta_cache' => true,
'meta_query' => '',
'suppress_filter' => false,
);
if ( ! empty( $deprecated ) ) {
$args['taxonomy'] = $deprecated;
_deprecated_argument( __FUNCTION__, '3.0', sprintf(
/* translators: 1: Argument name, 2: get_terms() */
__( 'The %1$s argument of %2$s is deprecated.' ),
'<code>$deprecated</code>',
'<code>get_terms()</code>'
) );
}
$args = wp_parse_args( $args, $defaults );
$taxonomy = $args['taxonomy'];
if ( ! taxonomy_exists( $taxonomy ) ) {
return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
}
$get_terms_args = array(
'taxonomy' => $taxonomy,
'orderby' => $args['orderby'],
'order' => $args['order'],
'hide_empty' => $args['hide_empty'],
'exclude' => $args['exclude'],
'exclude_tree' => $args['exclude_tree'],
'include' => $args['include'],
'number' => $args['number'],
'fields' => $args['fields'],
'slug' => $args['slug'],
'parent' => $args['parent'],
'hierarchical' => $args['hierarchical'],
'search' => $args['search'],
'name__like' => $args['name__like'],
'description__like' => $args['description__like'],
'pad_counts' => $args['pad_counts'],
'offset' => $args['offset'],
'child_of' => $args['child_of'],
'childless' => $args['childless'],
'get' => $args['get'],
'name' => $args['name'],
'term_taxonomy_id' => $args['term_taxonomy_id'],
'update_term_meta_cache' => $args['update_term_meta_cache'],
'meta_query' => $args['meta_query'],
'suppress_filter' => $args['suppress_filter'],
);
$term_query = new WP_Term_Query( $get_terms_args );
return apply_filters( 'get_terms', $term_query->get_terms(), $taxonomy, $get_terms_args );
}
简单来说,get_terms()
函数的作用就是:
- 接收参数: 接收一个
$args
数组,里面包含了各种查询条件,比如要查询哪个分类法 (taxonomy),排序方式,是否隐藏空分类等等。 - 参数标准化:使用
wp_parse_args()
函数将传入的参数与默认参数合并,确保所有需要的参数都有值。 - 实例化
WP_Term_Query
: 创建一个WP_Term_Query
类的实例,并将处理后的参数传递给它。 - 调用
get_terms()
方法: 通过$term_query->get_terms()
获取查询结果。 - 应用过滤器:使用
apply_filters()
应用get_terms
过滤器,允许其他插件或主题修改查询结果。 - 返回结果:将最终的查询结果返回。
关键的一步是:$term_query = new WP_Term_Query( $get_terms_args );
它创建了一个 WP_Term_Query
类的实例。 这就像是给 get_terms()
函数配了一个专业的“搜索犬”,专门负责分类术语的搜索工作。
第二幕:WP_Term_Query
类登场
WP_Term_Query
类位于 wp-includes/class-wp-term-query.php
文件中。 这个类是整个查询的核心,它负责构建 SQL 查询语句,并从数据库中获取分类术语。
我们先来看看它的构造函数:
public function __construct( $query = '' ) {
if ( ! empty( $query ) ) {
$this->query( $query );
}
}
构造函数很简单,如果传入了查询参数 $query
,就调用 query()
方法来处理这些参数。
接下来,我们重点关注 query()
方法和 get_terms()
方法。
public function query( $query ) {
global $wpdb;
$this->query_vars = wp_parse_args( $query, $this->defaults );
$this->query_vars = sanitize_term_field( 'query_vars', $this->query_vars, 'db', 'query' );
/**
* Fires before taxonomy terms are retrieved.
*
* @since 4.6.0
*
* @param WP_Term_Query $this The WP_Term_Query instance (passed by reference).
*/
do_action_ref_array( 'pre_get_terms', array( &$this ) );
$this->sql = $this->get_sql( $this->query_vars );
if ( is_null( $this->sql ) ) {
return;
}
$this->terms = $wpdb->get_results( $this->sql );
if ( 'ids' === $this->query_vars['fields'] ) {
$this->terms = array_map( 'intval', $this->terms );
}
if ( $this->query_vars['update_term_meta_cache'] ) {
update_term_meta_cache( wp_list_pluck( $this->terms, 'term_id' ) );
}
/**
* Fires after taxonomy terms are retrieved.
*
* @since 4.6.0
*
* @param WP_Term_Query $this The WP_Term_Query instance (passed by reference).
*/
do_action_ref_array( 'get_terms', array( &$this ) );
}
query()
方法的流程如下:
- 参数合并与清理: 使用
wp_parse_args()
将传入的查询参数与默认参数合并,并使用sanitize_term_field()
对参数进行清理,防止 SQL 注入。 pre_get_terms
动作: 触发pre_get_terms
动作,允许其他插件或主题在查询之前进行一些操作。- 构建 SQL 查询语句: 调用
get_sql()
方法,根据查询参数构建 SQL 查询语句。 - 执行查询: 使用
$wpdb->get_results()
执行 SQL 查询,获取查询结果。 - 处理结果: 根据
fields
参数,对查询结果进行处理,比如只返回 ID。 - 更新术语元数据缓存: 如果
update_term_meta_cache
参数为 true,则更新术语元数据缓存,提高后续查询效率。 get_terms
动作: 触发get_terms
动作,允许其他插件或主题在查询之后进行一些操作。
接下来,我们重点看看 get_sql()
方法是如何构建 SQL 查询语句的:
protected function get_sql( $query_vars ) {
global $wpdb;
$defaults = $this->defaults;
$this->query_vars = wp_parse_args( $query_vars, $defaults );
// Cast integers.
$number = isset( $this->query_vars['number'] ) ? absint( $this->query_vars['number'] ) : 0;
$offset = isset( $this->query_vars['offset'] ) ? absint( $this->query_vars['offset'] ) : 0;
$child_of = isset( $this->query_vars['child_of'] ) ? absint( $this->query_vars['child_of'] ) : 0;
$parent = isset( $this->query_vars['parent'] ) ? absint( $this->query_vars['parent'] ) : '';
$hierarchical = isset( $this->query_vars['hierarchical'] ) ? (bool) $this->query_vars['hierarchical'] : true;
$pad_counts = isset( $this->query_vars['pad_counts'] ) ? (bool) $this->query_vars['pad_counts'] : false;
$hide_empty = isset( $this->query_vars['hide_empty'] ) ? (bool) $this->query_vars['hide_empty'] : true;
$childless = isset( $this->query_vars['childless'] ) ? (bool) $this->query_vars['childless'] : false;
$name__like = isset( $this->query_vars['name__like'] ) ? trim( $this->query_vars['name__like'] ) : '';
$description__like = isset( $this->query_vars['description__like'] ) ? trim( $this->query_vars['description__like'] ) : '';
$search = isset( $this->query_vars['search'] ) ? trim( $this->query_vars['search'] ) : '';
$name = isset( $this->query_vars['name'] ) ? $this->query_vars['name'] : '';
$slug = isset( $this->query_vars['slug'] ) ? $this->query_vars['slug'] : '';
$term_taxonomy_id = isset( $this->query_vars['term_taxonomy_id'] ) ? $this->query_vars['term_taxonomy_id'] : '';
$get = isset( $this->query_vars['get'] ) ? $this->query_vars['get'] : '';
$taxonomies = (array) $this->query_vars['taxonomy'];
$selects = array();
$from = "FROM {$wpdb->terms} AS t INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
$where = "WHERE tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
$orderby = '';
$limits = '';
$order = strtoupper( $this->query_vars['order'] );
if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
$order = 'ASC';
}
// Fields selects.
switch ( $this->query_vars['fields'] ) {
case 'count':
$selects = array( 'COUNT(*)' );
break;
case 'ids':
$selects = array( 't.term_id' );
break;
case 'names':
$selects = array( 't.name' );
break;
case 'all':
case 'all_with_object_id':
default:
$selects = array( 't.*', 'tt.*' );
break;
}
/**
* Filters the SELECT clause of the terms query.
*
* @since 4.6.0
*
* @param array $selects An array of the SELECT clause columns.
* @param WP_Term_Query $this The WP_Term_Query instance (passed by reference).
*/
$selects = apply_filters_ref_array( 'get_terms_fields', array( $selects, &$this ) );
$fields = implode( ', ', $selects );
// Exclude term IDs.
if ( ! empty( $this->query_vars['exclude'] ) ) {
$exclude = wp_parse_id_list( $this->query_vars['exclude'] );
$where .= ' AND t.term_id NOT IN (' . implode( ', ', array_map( 'intval', $exclude ) ) . ')';
}
// Exclude term IDs from an entire subtree.
if ( ! empty( $this->query_vars['exclude_tree'] ) ) {
$exclude_tree = wp_parse_id_list( $this->query_vars['exclude_tree'] );
$where .= ' AND t.term_id NOT IN (' . implode( ', ', array_map( 'intval', $exclude_tree ) ) . ')';
}
// Include term IDs.
if ( ! empty( $this->query_vars['include'] ) ) {
$include = wp_parse_id_list( $this->query_vars['include'] );
$where .= ' AND t.term_id IN (' . implode( ', ', array_map( 'intval', $include ) ) . ')';
}
// Checking 'number' string to catch falsey values.
if ( ! empty( $number ) ) {
if ( $offset ) {
$limits = 'LIMIT ' . $offset . ', ' . $number;
} else {
$limits = 'LIMIT ' . $number;
}
}
// Term name like.
if ( ! empty( $name__like ) ) {
$where .= $wpdb->prepare( ' AND t.name LIKE %s', '%' . $wpdb->esc_like( $name__like ) . '%' );
}
// Term description like.
if ( ! empty( $description__like ) ) {
$where .= $wpdb->prepare( ' AND tt.description LIKE %s', '%' . $wpdb->esc_like( $description__like ) . '%' );
}
// Term slug.
if ( ! empty( $slug ) ) {
$slug = sanitize_title_for_query( $slug );
$where .= $wpdb->prepare( ' AND t.slug = %s', $slug );
}
// Term name.
if ( ! empty( $name ) ) {
if ( is_array( $name ) ) {
$name = array_map( array( $this, 'sanitize_term_name_for_query' ), $name );
$where .= " AND t.name IN ('" . implode( "', '", array_map( 'esc_sql', $name ) ) . "')";
} else {
$name = $this->sanitize_term_name_for_query( $name );
$where .= $wpdb->prepare( ' AND t.name = %s', $name );
}
}
// Term taxonomy ID.
if ( ! empty( $term_taxonomy_id ) ) {
if ( is_array( $term_taxonomy_id ) ) {
$term_taxonomy_id = wp_parse_id_list( $term_taxonomy_id );
$where .= ' AND tt.term_taxonomy_id IN (' . implode( ', ', array_map( 'intval', $term_taxonomy_id ) ) . ')';
} else {
$where .= $wpdb->prepare( ' AND tt.term_taxonomy_id = %d', $term_taxonomy_id );
}
}
// Hide empty terms.
if ( $hide_empty && ! $hierarchical ) {
$join = "INNER JOIN {$wpdb->term_relationships} AS tr ON t.term_id = tr.term_taxonomy_id";
$from .= ' ' . $join;
$where .= ' AND tt.count > 0';
}
// Match term names.
if ( ! empty( $search ) ) {
$like = '%' . $wpdb->esc_like( $search ) . '%';
$where .= $wpdb->prepare( ' AND (t.name LIKE %s)', $like );
}
if ( is_numeric( $parent ) ) {
$where .= $wpdb->prepare( ' AND tt.parent = %d', $parent );
}
if ( is_numeric( $child_of ) ) {
$where .= $wpdb->prepare( ' AND tt.parent = %d', $child_of );
}
if ( $childless ) {
$where .= ' AND tt.parent = 0';
}
// 'get' filter.
if ( 'all' == $get ) {
$where .= ' AND tt.count > 0';
}
// Meta query clauses.
$meta_query = new WP_Meta_Query( $this->query_vars['meta_query'] );
$clauses = $meta_query->get_sql( 'term', 't', 'term_id' );
if ( $clauses ) {
$from .= $clauses['join'];
$where .= " AND {$clauses['where']}";
}
$orderby_inner = '';
switch ( $this->query_vars['orderby'] ) {
case 'none':
$orderby = '';
break;
case 'term_id':
$orderby = 'ORDER BY t.term_id ' . $order;
break;
case 'name':
$orderby = 'ORDER BY t.name ' . $order;
break;
case 'slug':
$orderby = 'ORDER BY t.slug ' . $order;
break;
case 'term_group':
$orderby = 'ORDER BY t.term_group ' . $order;
break;
case 'count':
$orderby = 'ORDER BY tt.count ' . $order;
break;
case 'parent':
$orderby = 'ORDER BY tt.parent ' . $order;
break;
case 'id':
$orderby = 'ORDER BY t.term_id ' . $order;
break;
case 'description':
$orderby = 'ORDER BY tt.description ' . $order;
break;
case 'include':
$include = wp_parse_id_list( $this->query_vars['include'] );
$orderby = 'ORDER BY FIELD(t.term_id, ' . implode( ',', array_map( 'intval', $include ) ) . ')';
break;
case 'meta_value':
if ( ! empty( $clauses['orderby'] ) ) {
$orderby = 'ORDER BY ' . $clauses['orderby'];
}
break;
case 'meta_value_num':
if ( ! empty( $clauses['orderby'] ) ) {
$orderby = 'ORDER BY ' . $clauses['orderby'];
}
break;
default:
/**
* Filters the taxonomy term query's orderby clause.
*
* @since 2.8.0
*
* @param string $orderby The ORDERBY clause of the query.
* @param array $query_vars An array of arguments for the query.
* @param string $taxonomy The taxonomy of the terms being queried.
*/
$orderby = apply_filters( 'get_terms_orderby', 'ORDER BY t.name ' . $order, $this->query_vars, $taxonomies[0] );
}
$orderby = trim( $orderby );
if ( ! empty( $orderby_inner ) ) {
$orderby = $orderby_inner . ( ! empty( $orderby ) ? ', ' . $orderby : '' );
}
$found_rows = '';
if ( ! empty( $number ) && $this->query_vars['hierarchical'] && $pad_counts ) {
$found_rows = 'SQL_CALC_FOUND_ROWS';
}
$this->sql_clauses['select'] = "SELECT {$found_rows} $fields";
$this->sql_clauses['from'] = $from;
$this->sql_clauses['where'] = $where;
$this->sql_clauses['orderby'] = $orderby;
$this->sql_clauses['limits'] = $limits;
$this->sql = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$this->sql_clauses['where']} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
/**
* Filters the terms query SQL statement.
*
* @since 4.6.0
*
* @param string $sql The complete SQL query.
* @param array $query_vars An array of arguments for the query.
* @param WP_Term_Query $this The WP_Term_Query instance (passed by reference).
*/
$this->sql = apply_filters_ref_array( 'terms_request', array( $this->sql, $this->query_vars, &$this ) );
if ( ! empty( $number ) && $this->query_vars['hierarchical'] && $pad_counts ) {
$this->sql_clauses['found_rows'] = "SELECT FOUND_ROWS()";
}
return $this->sql;
}
get_sql()
方法就像一个经验丰富的建筑师,它根据各种查询参数,一块一块地拼接 SQL 查询语句。
- 参数准备: 将查询参数进行类型转换和清理。
- SQL 骨架: 构建 SQL 查询语句的基本框架,包括
SELECT
、FROM
和WHERE
子句。 - 处理各种参数: 根据传入的参数,动态地修改
WHERE
子句,比如排除某些分类 ID,包含某些分类 ID,根据分类名称搜索等等。 - 处理元数据查询: 如果传入了
meta_query
参数,则使用WP_Meta_Query
类来构建元数据查询的 SQL 子句。 - 排序: 根据
orderby
和order
参数,构建ORDER BY
子句。 - 分页: 根据
number
和offset
参数,构建LIMIT
子句。 - 应用过滤器: 使用
apply_filters_ref_array()
应用terms_request
过滤器,允许其他插件或主题修改最终的 SQL 查询语句。 - 返回 SQL 语句: 将最终构建好的 SQL 查询语句返回。
get_terms()
方法,它负责返回查询到的术语:
public function get_terms() {
if ( ! isset( $this->terms ) ) {
$this->query();
}
return $this->terms;
}
这个方法很简单,它首先检查 $this->terms
属性是否已经设置,如果还没有设置,就调用 query()
方法执行查询,然后将查询结果返回。
第三幕:WP_Meta_Query
类助阵
在 WP_Term_Query
类中,如果传入了 meta_query
参数,会使用 WP_Meta_Query
类来构建元数据查询的 SQL 子句。 WP_Meta_Query
类位于 wp-includes/class-wp-meta-query.php
文件中。
WP_Meta_Query
类的作用是:将复杂的元数据查询条件转换成 SQL 查询语句,以便在数据库中进行搜索。 这就像是给 WP_Term_Query
配备了一个专业的“元数据翻译官”,专门负责将元数据查询条件翻译成数据库能够理解的语言。
由于篇幅限制,我们这里就不深入讲解 WP_Meta_Query
类的源码了,但是你需要知道,它是 WordPress 中处理元数据查询的核心类之一。
总结
现在,我们来总结一下 get_terms()
函数是如何使用 WP_Term_Query
类来查询分类术语的:
get_terms()
函数接收查询参数,并将这些参数传递给WP_Term_Query
类。WP_Term_Query
类根据查询参数,构建 SQL 查询语句。如果查询涉及到元数据,WP_Meta_Query
类会协助构建元数据查询的 SQL 子句。WP_Term_Query
类执行 SQL 查询,并将查询结果返回给get_terms()
函数。get_terms()
函数对查询结果进行处理,并应用过滤器,然后将最终的结果返回。
用表格来总结一下:
函数/类 | 职责 |
---|---|
get_terms() |
接收查询参数,创建 WP_Term_Query 实例,调用 WP_Term_Query->get_terms() ,应用过滤器,返回结果。 |
WP_Term_Query |
构建 SQL 查询语句,执行查询,处理查询结果。 |
WP_Meta_Query |
将元数据查询条件转换成 SQL 查询语句。 |
举个例子
假设我们要查询 category
分类法下,slug
为 news
的分类术语。我们可以这样调用 get_terms()
函数:
$args = array(
'taxonomy' => 'category',
'slug' => 'news',
);
$terms = get_terms( $args );
if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
foreach ( $terms as $term ) {
echo 'Term ID: ' . $term->term_id . '<br>';
echo 'Term Name: ' . $term->name . '<br>';
echo 'Term Slug: ' . $term->slug . '<br>';
}
}
在这个例子中,get_terms()
函数会创建一个 WP_Term_Query
实例,并将 taxonomy
和 slug
参数传递给它。WP_Term_Query
类会构建如下 SQL 查询语句:
SELECT t.*, tt.* FROM wp_terms AS t INNER JOIN wp_term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy IN ('category') AND t.slug = 'news'
然后,WP_Term_Query
类会执行这个 SQL 查询语句,并将查询结果返回给 get_terms()
函数。
最后的话
希望通过今天的讲解,你对 get_terms()
函数和 WP_Term_Query
类有了更深入的了解。 掌握了这些知识,你就可以更灵活地使用 WordPress 的分类术语查询功能,并根据自己的需求进行定制。
记住,源码是最好的老师。多看源码,多思考,你就能成为真正的 WordPress 大神!