各位观众,欢迎来到今天的 "WordPress源码刨析" 讲座!今天我们要聊的是一个在WordPress开发中经常用到的函数:wp_set_post_terms()
。 别看名字平平无奇,它可是控制文章分类的关键人物,相当于文章的"户口登记员",负责把文章归到不同的"社区" (也就是分类术语)。
咱们今天就来扒一扒它的源码,看看这个"户口登记员"是怎么工作的,以及它背后的那些数据更新操作。
开场白: 认识一下我们的主角:wp_set_post_terms()
wp_set_post_terms()
函数的主要作用是:为指定的文章(post)设置指定分类法(taxonomy)下的分类术语(terms)。 简单来说,就是告诉WordPress,这篇文章属于哪个或哪些分类。
/**
* Sets the terms for a post.
*
* Relates the post to the term(s) and taxonomy. Works much like
* wp_set_object_terms() except it applies to post objects.
*
* @since 2.3.0
*
* @param int $post_id Post ID.
* @param int|string|array $terms Single term ID, single term slug, or array of term IDs or slugs.
* @param string $taxonomy Taxonomy name.
* @param bool $append Optional. Whether to append new terms to the old terms. Default false.
* @return array|WP_Error Array of term taxonomy IDs on success, WP_Error on failure.
*/
function wp_set_post_terms( $post_id = 0, $terms = array(), $taxonomy = 'category', $append = false ) {
return wp_set_object_terms( $post_id, $terms, $taxonomy, $append );
}
看到了吧? wp_set_post_terms()
本身只是一个wrapper函数, 它实际上调用了 wp_set_object_terms()
函数来完成任务。 这样做的好处是, wp_set_object_terms()
可以更通用地处理不同类型的对象(例如文章、用户等)与分类术语之间的关系。
第一幕: wp_set_object_terms()
源码解析
好了,既然主角是 wp_set_object_terms()
,我们就直接跳到它身上。 准备好,源码之旅开始了!
/**
* Sets the terms for an object.
*
* Relates the object to the term(s) and taxonomy. Creates term relationships if they don't already exist.
* Handles taxonomy counts.
*
* @since 2.3.0
* @since 4.3.0 Added 'id=>taxonomy' support for `$terms` parameter.
* @since 4.5.0 Added `$args` parameter.
*
* @param int $object_id Object ID.
* @param int|string|array $terms Single term ID, single term slug, array of term IDs, array of term slugs,
* or an associative array of term IDs to taxonomies.
* @param string|array $taxonomy Taxonomy name or array of taxonomy names.
* @param bool $append Optional. Whether to append new terms to the old terms. Default false.
* @param array $args {
* Optional. Array of arguments for setting object terms.
*
* @type bool $use_cache Whether to update the cached term counts. Default true.
* }
* @return array|WP_Error Array of term taxonomy IDs on success, WP_Error on failure.
*/
function wp_set_object_terms( $object_id = 0, $terms = array(), $taxonomy = 'category', $append = false, $args = array() ) {
global $wpdb;
$object_id = (int) $object_id;
if ( empty( $object_id ) ) {
return new WP_Error( 'invalid_object_id', __( 'Invalid object ID.' ) );
}
$defaults = array(
'use_cache' => true,
);
$args = wp_parse_args( $args, $defaults );
if ( ! is_array( $taxonomy ) ) {
$taxonomy = array( $taxonomy );
}
$taxonomies = array();
foreach ( $taxonomy as $tax ) {
$tax_obj = get_taxonomy( $tax );
if ( ! $tax_obj ) {
return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
}
$taxonomies[] = $tax_obj->name;
}
$term_ids = array();
if ( ! is_array( $terms ) ) {
$terms = array( $terms );
}
if ( empty( $terms ) ) {
$cleared = _wp_remove_object_terms( $object_id, $taxonomies );
if ( is_wp_error( $cleared ) ) {
return $cleared;
}
wp_cache_delete( $object_id, 'terms' );
return array();
}
$id_taxonomy = false;
foreach ( (array) $terms as $key => $value ) {
if ( is_string( $key ) && is_string( $value ) ) {
$id_taxonomy = true;
break;
}
}
if ( $id_taxonomy ) {
$new_taxonomies = array();
foreach ( (array) $terms as $term_id => $taxonomy ) {
$term_id = (int) $term_id;
if ( ! $term_id ) {
continue;
}
if ( ! taxonomy_exists( $taxonomy ) ) {
continue;
}
if ( ! in_array( $taxonomy, $new_taxonomies, true ) ) {
$new_taxonomies[] = $taxonomy;
}
if ( isset( $term_ids[ $taxonomy ] ) ) {
$term_ids[ $taxonomy ][] = $term_id;
} else {
$term_ids[ $taxonomy ] = array( $term_id );
}
}
$taxonomies = $new_taxonomies;
unset( $new_taxonomies );
} else {
foreach ( $taxonomies as $taxonomy ) {
$term_ids[ $taxonomy ] = array();
}
foreach ( (array) $terms as $term ) {
$term_info = term_exists( $term, $taxonomies[0] ); // Check if the term exists.
if ( is_array( $term_info ) ) {
$term_ids[ $taxonomies[0] ][] = (int) $term_info['term_id'];
} else {
// Skip if no matching term ID was found.
continue;
}
}
}
// Get the existing terms.
$old_term_ids = array();
foreach ( $taxonomies as $taxonomy ) {
$old_term_ids[ $taxonomy ] = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids', 'orderby' => 'none' ) );
}
// Only delete existing terms if we're not appending.
if ( ! $append ) {
$cleared = _wp_remove_object_terms( $object_id, $taxonomies );
if ( is_wp_error( $cleared ) ) {
return $cleared;
}
}
$tt_ids = array();
foreach ( $taxonomies as $taxonomy ) {
if ( isset( $term_ids[ $taxonomy ] ) ) {
$term_ids[ $taxonomy ] = array_map( 'intval', $term_ids[ $taxonomy ] );
$term_ids[ $taxonomy ] = array_unique( $term_ids[ $taxonomy ] );
// Remove already-assigned terms.
$new_ids = array_diff( $term_ids[ $taxonomy ], $old_term_ids[ $taxonomy ] );
if ( empty( $new_ids ) ) {
continue;
}
$tt_ids = array_merge( $tt_ids, wp_add_object_terms( $object_id, $new_ids, $taxonomy ) );
}
}
if ( $args['use_cache'] ) {
wp_cache_delete( $object_id, 'terms' );
}
wp_update_term_count( $tt_ids, $taxonomies );
/**
* Fires after the terms are set for an object.
*
* @since 2.9.0
*
* @param int $object_id Object ID.
* @param array $terms An array of object term IDs.
* @param array $tt_ids Array of term taxonomy IDs.
* @param array $taxonomy Array of taxonomy names.
* @param bool $append Whether to append the terms.
* @param array $old_term_ids Array of pre-existing term IDs.
*/
do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_term_ids );
return $tt_ids;
}
第二幕: 源码解读,庖丁解牛
看起来代码很长,但别慌,我们一步一步来。
-
参数验证和准备
- 首先,函数会把
$object_id
转换为整数,并检查是否为空。 如果为空,直接返回一个WP_Error
对象,提示 ID 无效。 - 然后,它会设置一个默认参数
$args
,其中'use_cache'
默认为true
,表示要更新缓存。 - 接下来,它会确保
$taxonomy
是一个数组,方便后续处理。
$object_id = (int) $object_id; if ( empty( $object_id ) ) { return new WP_Error( 'invalid_object_id', __( 'Invalid object ID.' ) ); } $defaults = array( 'use_cache' => true, ); $args = wp_parse_args( $args, $defaults ); if ( ! is_array( $taxonomy ) ) { $taxonomy = array( $taxonomy ); }
- 首先,函数会把
-
验证 Taxonomy 是否存在
- 遍历
$taxonomy
数组,使用get_taxonomy()
函数检查每个 taxonomy 是否存在。 如果有不存在的,也会返回一个WP_Error
对象。 - 把验证通过的 taxonomy name 保存在
$taxonomies
数组中。
$taxonomies = array(); foreach ( $taxonomy as $tax ) { $tax_obj = get_taxonomy( $tax ); if ( ! $tax_obj ) { return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) ); } $taxonomies[] = $tax_obj->name; }
- 遍历
-
处理
$terms
参数$terms
参数可以有多种形式:单个 term ID、单个 term slug、term ID 数组、term slug 数组,甚至是一个关联数组,其中 key 是 term ID,value 是 taxonomy。 函数会根据$terms
的类型进行不同的处理。- 如果
$terms
为空,表示要移除该对象的所有分类术语。 此时,会调用_wp_remove_object_terms()
函数来删除关联关系,并清除缓存。
$term_ids = array(); if ( ! is_array( $terms ) ) { $terms = array( $terms ); } if ( empty( $terms ) ) { $cleared = _wp_remove_object_terms( $object_id, $taxonomies ); if ( is_wp_error( $cleared ) ) { return $cleared; } wp_cache_delete( $object_id, 'terms' ); return array(); } $id_taxonomy = false; foreach ( (array) $terms as $key => $value ) { if ( is_string( $key ) && is_string( $value ) ) { $id_taxonomy = true; break; } } if ( $id_taxonomy ) { // 处理 `$terms` 是一个关联数组,其中 key 是 term ID,value 是 taxonomy 的情况 $new_taxonomies = array(); foreach ( (array) $terms as $term_id => $taxonomy ) { $term_id = (int) $term_id; if ( ! $term_id ) { continue; } if ( ! taxonomy_exists( $taxonomy ) ) { continue; } if ( ! in_array( $taxonomy, $new_taxonomies, true ) ) { $new_taxonomies[] = $taxonomy; } if ( isset( $term_ids[ $taxonomy ] ) ) { $term_ids[ $taxonomy ][] = $term_id; } else { $term_ids[ $taxonomy ] = array( $term_id ); } } $taxonomies = $new_taxonomies; unset( $new_taxonomies ); } else { // 处理 `$terms` 是 term ID 或 term slug 数组的情况 foreach ( $taxonomies as $taxonomy ) { $term_ids[ $taxonomy ] = array(); } foreach ( (array) $terms as $term ) { $term_info = term_exists( $term, $taxonomies[0] ); // Check if the term exists. if ( is_array( $term_info ) ) { $term_ids[ $taxonomies[0] ][] = (int) $term_info['term_id']; } else { // Skip if no matching term ID was found. continue; } } }
-
获取已存在的分类术语
- 使用
wp_get_object_terms()
函数获取该对象已存在的分类术语 ID。
$old_term_ids = array(); foreach ( $taxonomies as $taxonomy ) { $old_term_ids[ $taxonomy ] = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids', 'orderby' => 'none' ) ); }
- 使用
-
删除旧的分类术语(如果不是追加模式)
- 如果
$append
参数为false
(默认值),表示要替换现有的分类术语。 此时,会调用_wp_remove_object_terms()
函数删除旧的关联关系。
if ( ! $append ) { $cleared = _wp_remove_object_terms( $object_id, $taxonomies ); if ( is_wp_error( $cleared ) ) { return $cleared; } }
- 如果
-
添加新的分类术语
- 遍历
$taxonomies
数组,对于每个 taxonomy,将$term_ids
数组中的 term ID 转换为整数,并去除重复的 ID。 - 使用
array_diff()
函数找出需要添加的新 term ID (即不在$old_term_ids
中的 ID)。 - 调用
wp_add_object_terms()
函数添加新的关联关系。
$tt_ids = array(); foreach ( $taxonomies as $taxonomy ) { if ( isset( $term_ids[ $taxonomy ] ) ) { $term_ids[ $taxonomy ] = array_map( 'intval', $term_ids[ $taxonomy ] ); $term_ids[ $taxonomy ] = array_unique( $term_ids[ $taxonomy ] ); // Remove already-assigned terms. $new_ids = array_diff( $term_ids[ $taxonomy ], $old_term_ids[ $taxonomy ] ); if ( empty( $new_ids ) ) { continue; } $tt_ids = array_merge( $tt_ids, wp_add_object_terms( $object_id, $new_ids, $taxonomy ) ); } }
- 遍历
-
更新缓存
- 如果
$args['use_cache']
为true
,则清除缓存。
if ( $args['use_cache'] ) { wp_cache_delete( $object_id, 'terms' ); }
- 如果
-
更新分类术语计数
- 调用
wp_update_term_count()
函数更新分类术语的计数。 这是非常重要的一步,因为它确保了分类术语的计数与实际关联的文章数量保持一致。
wp_update_term_count( $tt_ids, $taxonomies );
- 调用
-
触发 Action Hook
- 触发
set_object_terms
action hook,允许其他插件或主题执行自定义操作。
do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_term_ids );
- 触发
-
返回结果
- 函数返回一个数组,包含所有关联的 term taxonomy ID (tt_id)。
return $tt_ids;
第三幕: 幕后英雄: wp_add_object_terms()
和 wp_update_term_count()
wp_set_object_terms()
函数调用了两个重要的助手函数:wp_add_object_terms()
和 wp_update_term_count()
。 让我们简单了解一下它们。
-
wp_add_object_terms()
:添加对象与分类术语的关联关系这个函数负责在
wp_term_relationships
表中插入数据,建立对象与分类术语之间的关联。/** * Adds term(s) to an object. * * Creates term relationships if they don't already exist. * * @since 2.3.0 * * @param int $object_id Object ID. * @param int|string|array $terms Single term ID, single term slug, or array of term IDs or slugs. * @param string $taxonomy Taxonomy name. * @return array Array of term taxonomy IDs. */ function wp_add_object_terms( $object_id, $terms, $taxonomy ) { global $wpdb; $object_id = (int) $object_id; $term_ids = array(); $taxonomy_obj = get_taxonomy( $taxonomy ); if ( ! $taxonomy_obj ) { return array(); } if ( ! is_array( $terms ) ) { $terms = array( $terms ); } foreach ( $terms as $term ) { $term_info = term_exists( $term, $taxonomy ); if ( is_array( $term_info ) ) { $term_id = $term_info['term_id']; } else { $term_id = term_exists( $term, $taxonomy ); if ( ! is_int( $term_id ) ) { continue; } } $term_ids[] = (int) $term_id; } $term_ids = array_map( 'intval', $term_ids ); $term_ids = array_unique( $term_ids ); $tt_ids = array(); if ( ! empty( $term_ids ) ) { $tt_ids = _wp_add_object_terms( $object_id, $term_ids, $taxonomy ); } return $tt_ids; } /** * Adds term(s) to an object. * * @access private * @since 4.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $object_id Object ID. * @param array $term_ids Array of term IDs. * @param array $taxonomy Taxonomy name. * @return array Array of term taxonomy IDs. */ function _wp_add_object_terms( $object_id, $term_ids, $taxonomy ) { global $wpdb; $object_id = (int) $object_id; $taxonomy_obj = get_taxonomy( $taxonomy ); if ( ! $taxonomy_obj ) { return array(); } $tt_ids = array(); $term_ids = array_map( 'intval', $term_ids ); $term_ids = array_unique( $term_ids ); $check = sanitize_sql_in( $term_ids, false ); if ( ! $check ) { return array(); } $exists = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id IN ($check) AND taxonomy = %s", $taxonomy ) ); $term_ids = array_intersect( $term_ids, $exists ); if ( empty( $term_ids ) ) { return array(); } $query = "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id IN ($check) AND taxonomy = %s"; $term_taxonomy_ids = $wpdb->get_results( $wpdb->prepare( $query, $taxonomy ), OBJECT_K ); $values = array(); $placeholders = array(); foreach ( $term_ids as $term_id ) { $tt_id = $term_taxonomy_ids[ $term_id ]->term_taxonomy_id; $values[] = $wpdb->prepare( "(%d, %d, %s)", $tt_id, $object_id, 0 ); $placeholders[] = '%d, %d, %s'; $tt_ids[] = $tt_id; } if ( ! empty( $values ) ) { $query = "INSERT IGNORE INTO $wpdb->term_relationships (term_taxonomy_id, object_id, term_order) VALUES "; $query .= implode( ', ', $values ); $wpdb->query( $query ); } wp_cache_delete( $object_id, 'terms' ); return $tt_ids; }
-
wp_update_term_count()
:更新分类术语的计数这个函数负责更新
wp_term_taxonomy
表中每个分类术语的count
字段,确保它反映了实际关联的文章数量。 这个函数非常重要,因为它影响着分类列表的显示和相关功能的运作。/** * Updates Term Count. * * Recalculates term counts, using a taxonomy query. * * @since 2.3.0 * * @param int|array $terms Single term ID or array of term IDs. * @param string|array $taxonomies Single taxonomy name or array of taxonomy names. */ function wp_update_term_count( $terms, $taxonomies ) { global $wpdb; $object_types = array_unique( (array) apply_filters( 'wp_term_taxonomy_object_types', array( 'post' ), $taxonomies ) ); foreach ( (array) $taxonomies as $taxonomy ) { $taxonomy_obj = get_taxonomy( $taxonomy ); if ( empty( $taxonomy_obj->object_type ) ) { continue; } $_terms = (array) $terms; if ( ! empty( $_terms ) ) { $terms = "'" . implode( "', '", array_map( 'esc_sql', $_terms ) ) . "'"; } // 'post' is the only type that is supported by default. if ( isset( $taxonomy_obj->object_type ) && is_array( $taxonomy_obj->object_type ) ) { $object_types = array_intersect( $object_types, $taxonomy_obj->object_type ); } $object_types = array_map( 'esc_sql', $object_types ); /** * Filters the object types used in term count queries. * * @since 4.9.0 * * @param array $object_types Array of object types. * @param string $taxonomy The taxonomy being updated. */ $object_types = apply_filters( 'wp_update_term_count_object_types', $object_types, $taxonomy ); $object_types = "'" . implode( "', '", $object_types ) . "'"; $query = "UPDATE $wpdb->term_taxonomy SET count = ( SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id AND $wpdb->posts.ID = $wpdb->term_relationships.object_id AND $wpdb->posts.post_status IN ('publish', 'future', 'draft', 'pending', 'private') AND $wpdb->posts.post_type IN ($object_types) ) WHERE taxonomy = '$taxonomy'"; if ( ! empty( $_terms ) ) { $query .= " AND term_id IN ($terms)"; } $wpdb->query( $query ); } wp_cache_flush(); }
第四幕: 总结
wp_set_post_terms()
函数,以及它调用的 wp_set_object_terms()
、wp_add_object_terms()
和 wp_update_term_count()
函数,共同完成了为文章设置分类术语的任务。 它们负责验证参数、建立关联关系、更新分类术语计数,并确保数据的完整性和一致性。
函数名 | 主要作用 |
---|---|
wp_set_post_terms() |
为文章设置分类术语,是 wp_set_object_terms() 的一个 wrapper 函数。 |
wp_set_object_terms() |
为指定对象(例如文章)设置分类术语,负责参数验证、删除旧关联关系、添加新关联关系、更新缓存和触发 action hook。 |
wp_add_object_terms() |
在 wp_term_relationships 表中插入数据,建立对象与分类术语之间的关联关系。 |
wp_update_term_count() |
更新 wp_term_taxonomy 表中每个分类术语的 count 字段,确保它反映了实际关联的文章数量。 |
理解这些函数的运作方式,可以帮助我们更好地进行WordPress开发,并能更好地理解WordPress是如何管理文章分类的。
剧终: 感谢观看!
希望今天的讲座能帮助大家更好地理解 wp_set_post_terms()
函数及其背后的机制。 掌握了这些知识,你就可以更加自信地驾驭WordPress,打造出更加强大的网站! 感谢各位的观看,我们下期再见!