各位观众,大家好! 今天咱们不讲段子,来点硬核的,深入扒一扒 WordPress 里面一个常用的函数:wp_get_object_terms()
。 这家伙看着不起眼,但在文章和分类之间牵线搭桥,作用可大了。 今天咱们就来拆解它,看看它到底是怎么把文章和分类术语(Terms)给“撮合”到一起的。
Part 1: 啥是 wp_get_object_terms()
?它要干嘛?
简单来说,wp_get_object_terms()
函数的作用就是:根据给定的对象(比如文章、自定义文章类型等)的 ID,获取与这个对象相关联的分类术语(比如文章的分类、标签、自定义分类法等等)。
你可以把它想象成一个媒婆,手里拿着对象的 ID,然后去数据库里查找,把所有跟这个对象“有关系”的分类术语都找出来,打包返回给你。
函数原型:
wp_get_object_terms(
int|int[] $object_ids,
string|string[] $taxonomies = 'post_tag',
array $args = array()
);
$object_ids
: 对象的 ID。可以是一个 ID,也可以是 ID 的数组。比如,你想知道文章 ID 为 1 的分类术语,这里就填 1。$taxonomies
: 分类法的名称。默认是 ‘post_tag’ (标签)。 比如,你想获取文章的分类,这里就填 ‘category’。$args
: 一些额外的参数,用于控制返回的结果。比如,你可以指定返回结果的排序方式、数量等等。
返回值:
- 成功:返回一个包含
WP_Term
对象的数组。每个WP_Term
对象代表一个分类术语。 - 失败:返回一个空数组或者
WP_Error
对象。
举个栗子:
$post_id = 123; // 假设文章 ID 是 123
$categories = wp_get_object_terms( $post_id, 'category' );
if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
echo '这篇文章属于以下分类:';
foreach ( $categories as $category ) {
echo $category->name . ', ';
}
} else {
echo '这篇文章没有分类。';
}
这个例子会获取文章 ID 为 123 的所有分类,然后把分类的名称打印出来。
Part 2: 源码剖析:wp_get_object_terms()
的内部世界
现在,咱们就来深入源码,看看 wp_get_object_terms()
函数是如何实现的。 (以下源码基于 WordPress 最新版本,可能会略有差异)
function wp_get_object_terms( $object_ids, $taxonomies = 'post_tag', $args = array() ) {
global $wpdb;
if ( empty( $object_ids ) ) {
return array();
}
$single = false;
if ( ! is_array( $object_ids ) ) {
$single = true;
$object_ids = array( (int) $object_ids );
$object_ids = array_map( 'absint', $object_ids ); //确保是整数
$object_id = $object_ids[0]; //缓存键用单个ID
} else {
$object_ids = array_map( 'absint', $object_ids );
$object_id = implode( ',', $object_ids ); //缓存键用逗号分隔的ID
}
if ( empty( $taxonomies ) ) {
return array();
}
if ( ! is_array( $taxonomies ) ) {
$taxonomies = array( $taxonomies );
}
$taxonomies = array_map( 'sanitize_key', $taxonomies );
$defaults = array(
'orderby' => 'name',
'order' => 'ASC',
'fields' => 'all',
'name' => '',
'slug' => '',
'count' => false,
'id_fields' => false,
'get' => '',
'number' => '',
'offset' => '',
'search' => '',
'cache_domain' => 'core',
'update_term_meta_cache' => true,
'hierarchical' => true,
'child_of' => 0,
'parent' => '',
'pad_counts' => false,
'hide_empty' => false,
);
$args = wp_parse_args( $args, $defaults );
$orderby = strtolower( $args['orderby'] );
$order = strtoupper( $args['order'] );
if ( ! in_array( $orderby, array( 'name', 'slug', 'term_group', 'term_id', 'id', 'description', 'count' ), true ) ) {
$orderby = 'name';
}
if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
$order = 'ASC';
}
$fields = $args['fields'];
if ( ! in_array( $fields, array( 'all', 'ids', 'names', 'slugs', 'tt_ids' ), true ) ) {
$fields = 'all';
}
$id_fields = (bool) $args['id_fields'];
$cache_key = md5( var_export( wp_array_slice_assoc( $args, array( 'orderby', 'order', 'fields', 'name', 'slug', 'count', 'id_fields', 'get', 'number', 'offset', 'search', 'hierarchical', 'child_of', 'parent', 'pad_counts', 'hide_empty' ) ), true ) . serialize( $object_ids ) . serialize( $taxonomies ) ); // 构建缓存键
$cache_domain = $args['cache_domain'];
$cache = wp_cache_get( $cache_key, "{$cache_domain}_term_relationships" ); // 尝试从缓存中获取
if ( false === $cache ) {
$terms = _wp_get_object_terms( $object_ids, $taxonomies, $args ); // 如果缓存没有,则调用 _wp_get_object_terms 获取
wp_cache_set( $cache_key, $terms, "{$cache_domain}_term_relationships" ); // 设置缓存
} else {
$terms = $cache;
}
if ( is_wp_error( $terms ) ) {
return $terms;
}
if ( empty( $terms ) ) {
return array();
}
if ( 'ids' === $fields ) {
$terms = wp_list_pluck( $terms, 'term_id' );
} elseif ( 'names' === $fields ) {
$terms = wp_list_pluck( $terms, 'name' );
} elseif ( 'slugs' === $fields ) {
$terms = wp_list_pluck( $terms, 'slug' );
} elseif ( 'tt_ids' === $fields ) {
$terms = wp_list_pluck( $terms, 'term_taxonomy_id' );
}
if ( $single ) {
return reset( $terms );
}
return $terms;
}
代码解读:
- 参数校验和处理:
- 首先,检查
$object_ids
和$taxonomies
是否为空。如果为空,直接返回空数组。 - 如果
$object_ids
不是数组,把它转换成数组。 - 如果
$taxonomies
不是数组,也把它转换成数组。 - 使用
array_map
和absint
函数确保$object_ids
数组中的元素都是整数。 - 使用
array_map
和sanitize_key
函数对$taxonomies
数组中的元素进行安全过滤。
- 首先,检查
- 参数合并和默认值:
- 定义一个
$defaults
数组,包含一些默认的参数值,比如排序方式、排序字段、返回字段等等。 - 使用
wp_parse_args
函数把用户传入的$args
参数和$defaults
数组合并,得到最终的参数。
- 定义一个
- 构建缓存键:
- 使用
md5
函数和var_export/serialize
函数构建一个唯一的缓存键。 这个缓存键包含了所有的参数信息,这样可以确保不同的参数组合对应不同的缓存。
- 使用
- 尝试从缓存中获取数据:
- 使用
wp_cache_get
函数尝试从 WordPress 的对象缓存中获取数据。 如果缓存命中,直接返回缓存中的数据。
- 使用
- 调用
_wp_get_object_terms
函数:- 如果缓存没有命中,调用
_wp_get_object_terms
函数从数据库中获取数据。 这个函数才是真正执行数据库查询的函数。
- 如果缓存没有命中,调用
- 设置缓存:
- 使用
wp_cache_set
函数把从数据库中获取到的数据设置到缓存中,以便下次使用。
- 使用
- 处理返回结果:
- 根据
$fields
参数的值,对返回结果进行处理。 如果$fields
是 ‘ids’,则只返回分类术语的 ID。 如果$fields
是 ‘names’,则只返回分类术语的名称。 以此类推。
- 根据
- 返回结果:
- 如果
$single
是true
,则只返回数组中的第一个元素。 否则,返回整个数组。
- 如果
Part 3: 深入 _wp_get_object_terms
函数:数据库查询的秘密
_wp_get_object_terms
函数才是真正执行数据库查询的函数。 咱们也来扒一扒它的源码。
function _wp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
global $wpdb;
$defaults = array(
'orderby' => 'name',
'order' => 'ASC',
'fields' => 'all',
'name' => '',
'slug' => '',
'count' => false,
'id_fields' => false,
'get' => '',
'number' => '',
'offset' => '',
'search' => '',
'hierarchical' => true,
'child_of' => 0,
'parent' => '',
'pad_counts' => false,
'hide_empty' => false,
);
$args = wp_parse_args( $args, $defaults );
$orderby = strtolower( $args['orderby'] );
$order = strtoupper( $args['order'] );
if ( ! in_array( $orderby, array( 'name', 'slug', 'term_group', 'term_id', 'id', 'description', 'count' ), true ) ) {
$orderby = 'name';
}
if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
$order = 'ASC';
}
$fields = $args['fields'];
if ( ! in_array( $fields, array( 'all', 'ids', 'names', 'slugs', 'tt_ids' ), true ) ) {
$fields = 'all';
}
$id_fields = (bool) $args['id_fields'];
$object_ids = array_map( 'intval', (array) $object_ids );
$object_ids = array_unique( $object_ids );
$taxonomies = array_map( 'sanitize_key', (array) $taxonomies );
$taxonomies = array_intersect( $taxonomies, get_taxonomies() );
if ( empty( $taxonomies ) ) {
return array();
}
$select_fields = '';
switch ( $fields ) {
case 'all':
$select_fields = "t.*, tt.*";
break;
case 'ids':
$select_fields = 't.term_id';
break;
case 'names':
$select_fields = 't.name';
break;
case 'slugs':
$select_fields = 't.slug';
break;
case 'tt_ids':
$select_fields = 'tt.term_taxonomy_id';
break;
}
$join = "INNER JOIN {$wpdb->term_relationships} AS tr ON (t.term_id = tr.term_taxonomy_id)";
$join .= "INNER JOIN {$wpdb->term_taxonomy} AS tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)";
$where = "WHERE tt.taxonomy IN ('" . implode( "', '", $taxonomies ) . "')";
$where .= "AND tr.object_id IN (" . implode( ',', $object_ids ) . ")";
if ( ! empty( $args['name'] ) ) {
$names = array_map( 'trim', explode( ',', $args['name'] ) );
$names = array_map( array( $wpdb, 'esc_like' ), $names );
$name_like = "AND t.name LIKE '%" . implode( "%' AND t.name LIKE '%", $names ) . "%'";
$where .= $name_like;
}
if ( ! empty( $args['slug'] ) ) {
$slugs = array_map( 'trim', explode( ',', $args['slug'] ) );
$slugs = array_map( array( $wpdb, 'esc_like' ), $slugs );
$slug_like = "AND t.slug LIKE '%" . implode( "%' AND t.slug LIKE '%", $slugs ) . "%'";
$where .= $slug_like;
}
if ( ! empty( $args['search'] ) ) {
$search = $wpdb->esc_like( $args['search'] );
$where .= "AND (t.name LIKE '%{$search}%' OR t.slug LIKE '%{$search}%')";
}
if ( ! empty( $args['parent'] ) ) {
$parent = (int) $args['parent'];
$where .= "AND tt.parent = {$parent}";
}
if ( is_numeric( $args['child_of'] ) && $args['child_of'] > 0 ) {
$child_of = (int) $args['child_of'];
$where .= "AND tt.parent = {$child_of}";
}
$orderby_clause = '';
switch ( $orderby ) {
case 'name':
$orderby_clause = 'ORDER BY t.name';
break;
case 'slug':
$orderby_clause = 'ORDER BY t.slug';
break;
case 'term_group':
$orderby_clause = 'ORDER BY t.term_group';
break;
case 'term_id':
case 'id':
$orderby_clause = 'ORDER BY t.term_id';
break;
case 'description':
$orderby_clause = 'ORDER BY tt.description';
break;
case 'count':
$orderby_clause = 'ORDER BY tt.count';
break;
}
$orderby_clause .= ' ' . $order;
if ( is_numeric( $args['number'] ) && $args['number'] > 0 ) {
$number = (int) $args['number'];
if ( is_numeric( $args['offset'] ) && $args['offset'] > 0 ) {
$offset = (int) $args['offset'];
$limit = "LIMIT {$offset}, {$number}";
} else {
$limit = "LIMIT {$number}";
}
} else {
$limit = '';
}
$query = "SELECT {$select_fields} FROM {$wpdb->terms} AS t {$join} {$where} {$orderby_clause} {$limit}";
$terms = $wpdb->get_results( $query );
if ( ! empty( $terms ) && $args['update_term_meta_cache'] ) {
_prime_term_caches( $terms, false );
}
if ( ! empty( $terms ) && 'all' === $fields ) {
$terms = array_map( 'get_term', $terms );
}
return $terms;
}
代码解读:
- 参数校验和处理:
- 跟
wp_get_object_terms
函数类似,首先进行参数校验和处理。 - 使用
array_intersect
函数确保$taxonomies
数组中的元素都是有效的分类法名称。
- 跟
- 构建 SQL 查询语句:
- 根据
$fields
参数的值,选择要查询的字段。 - 构建
JOIN
子句,连接wp_terms
、wp_term_relationships
和wp_term_taxonomy
表。 - 构建
WHERE
子句,根据$taxonomies
和$object_ids
参数的值,过滤查询结果。 - 根据
$args
数组中的其他参数,添加额外的过滤条件。 - 构建
ORDER BY
子句,指定排序方式。 - 构建
LIMIT
子句,限制返回结果的数量。
- 根据
- 执行 SQL 查询:
- 使用
$wpdb->get_results
函数执行 SQL 查询,获取查询结果。
- 使用
- 更新分类术语元数据缓存:
- 如果
$args['update_term_meta_cache']
是true
,则调用_prime_term_caches
函数更新分类术语元数据缓存。 这个函数可以提高分类术语元数据的访问速度。
- 如果
- 转换查询结果:
- 如果
$fields
是 ‘all’,则使用array_map
和get_term
函数把查询结果转换成WP_Term
对象。
- 如果
- 返回结果:
- 返回查询结果。
Part 4: 数据库表之间的关系
wp_get_object_terms()
函数的底层查询涉及三个关键的数据库表:
表名 | 作用 | 关键字段 |
---|---|---|
wp_terms |
存储分类术语的信息,例如名称、别名等。 | term_id (术语 ID), name (术语名称), slug (术语别名) |
wp_term_taxonomy |
存储分类术语的分类法信息,例如分类、标签等。 | term_taxonomy_id (术语分类 ID), term_id (术语 ID), taxonomy (分类法名称), parent (父级术语分类 ID), count (术语下的对象数量) |
wp_term_relationships |
存储对象和分类术语之间的关系,例如文章和分类之间的关系。 | object_id (对象 ID), term_taxonomy_id (术语分类 ID), term_order (术语顺序) |
这三个表之间的关系可以用下图来表示:
wp_terms (1) <-----> (1) wp_term_taxonomy (N) <-----> (N) wp_term_relationships (N)
wp_terms
表和wp_term_taxonomy
表之间是一对多的关系。 一个分类术语可以属于多个分类法。wp_term_taxonomy
表和wp_term_relationships
表之间是一对多的关系。 一个分类法可以关联多个对象。
wp_get_object_terms()
函数通过 JOIN
语句把这三个表连接起来,从而获取与指定对象相关联的分类术语。
Part 5: 缓存机制
wp_get_object_terms()
函数使用了 WordPress 的对象缓存机制来提高性能。 具体来说,它使用了 wp_cache_get
和 wp_cache_set
函数来从缓存中获取和设置数据。
缓存键的构建非常重要,它需要包含所有的参数信息,才能确保不同的参数组合对应不同的缓存。 wp_get_object_terms()
函数使用 md5
函数和 var_export/serialize
函数来构建缓存键,确保缓存键的唯一性。
通过使用缓存机制,wp_get_object_terms()
函数可以避免频繁地访问数据库,从而提高性能。
Part 6: 总结
wp_get_object_terms()
函数是一个非常重要的函数,它可以帮助我们获取与指定对象相关联的分类术语。 通过深入源码,我们了解了它的实现原理,包括参数校验、参数合并、构建 SQL 查询语句、执行 SQL 查询、更新分类术语元数据缓存、转换查询结果、返回结果等等。 我们也了解了它使用了 WordPress 的对象缓存机制来提高性能。
掌握 wp_get_object_terms()
函数的原理,可以帮助我们更好地理解 WordPress 的内部机制,从而更好地开发 WordPress 主题和插件。 希望今天的分享对大家有所帮助! 谢谢大家!