详解 WordPress `wp_set_post_terms()` 函数源码:如何设置文章的分类术语并更新计数。

各位观众老爷们,晚上好!我是你们的老朋友,今天咱们来聊聊 WordPress 里的一个“重量级”选手:wp_set_post_terms() 函数。别被它长长的名字吓到,其实理解了它的核心逻辑,你会发现它也没那么可怕。

今天咱们就来扒一扒它的源码,看看它是如何给文章设置分类术语,又是如何更新那些让人头疼的计数器的。准备好了吗?咱们开始!

开场白:术语分类的重要性

在WordPress的世界里,文章(Post)就像是咱们辛辛苦苦码出来的文字结晶,而分类术语(Taxonomy Terms),比如分类(Category)和标签(Tag),则是用来组织和管理这些文章的利器。想象一下,如果没有分类,所有的文章都堆在一起,那岂不是乱成一锅粥?

wp_set_post_terms() 函数就是负责把文章和这些分类术语联系起来的关键。它能让咱们轻松地给文章添加、删除或更新分类和标签。

源码解析:wp_set_post_terms() 的真面目

好,废话不多说,直接上代码!不过,为了方便理解,咱们会把它拆解成几个小模块来讲解。

function wp_set_post_terms( $post_id, $terms, $taxonomy = 'post_tag', $append = false ) {
    global $wpdb;

    $post_id = (int) $post_id;

    if ( empty( $taxonomy ) ) {
        return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    }

    $taxonomy = sanitize_key( $taxonomy );

    $term_taxonomy_ids = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'tt_ids' ) );

    if ( is_wp_error( $term_taxonomy_ids ) ) {
        return $term_taxonomy_ids;
    }

    $term_taxonomy_ids = array_map( 'intval', $term_taxonomy_ids );

    if ( ! is_array( $terms ) ) {
        $terms = array( $terms );
    }

    $terms = array_filter( $terms );

    if ( empty( $terms ) && ! empty( $term_taxonomy_ids ) ) {
        return wp_remove_object_terms( $post_id, $term_taxonomy_ids, $taxonomy );
    }

    if ( ! empty( $terms ) ) {
        $new_tt_ids = wp_create_term_taxonomies( $terms, $taxonomy );

        if ( is_wp_error( $new_tt_ids ) ) {
            return $new_tt_ids;
        }

        if ( ! is_array( $new_tt_ids ) ) {
            return new WP_Error( 'unexpected_output', __( 'Unexpected output.' ) );
        }

        $tt_ids = array_values( $new_tt_ids );
    } else {
        $tt_ids = array();
    }

    if ( $append ) {
        $tt_ids = array_merge( $term_taxonomy_ids, $tt_ids );
    }

    $tt_ids = array_map( 'intval', $tt_ids );
    $tt_ids = array_unique( $tt_ids );
    $tt_ids = array_diff( $tt_ids, $term_taxonomy_ids );

    $tt_ids = array_map( 'intval', $tt_ids );
    $term_taxonomy_ids = array_map( 'intval', $term_taxonomy_ids );

    $insertions = array();
    $deleteions = array();

    foreach ( (array) $tt_ids as $tt_id ) {
        $insertions[] = $wpdb->prepare( "(%d, %d, %s)", $post_id, $tt_id, $taxonomy );
    }

    if ( ! empty( $insertions ) ) {
        $query = "INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id, term_order) VALUES " . implode( ',', $insertions );
        $wpdb->query( $query );
    }

    $deleteions = array_diff( $term_taxonomy_ids, array_map( 'intval', $tt_ids ) );

    if ( ! empty( $deleteions ) ) {
        $delete_tt_ids = implode( ',', $deleteions );
        $query = "DELETE FROM {$wpdb->term_relationships} WHERE object_id = %d AND term_taxonomy_id IN ($delete_tt_ids)";
        $wpdb->query( $wpdb->prepare( $query, $post_id ) );
    }

    wp_cache_delete( $post_id, $taxonomy . '_relationships' );

    do_action( 'set_object_terms', $post_id, $terms, $taxonomy, $append, $old_tt_ids );

    wp_update_term_count_now( array_merge( (array) $deleteions, (array) $tt_ids ), $taxonomy );

    do_action( 'edited_' . $taxonomy, $post_id );

    return array_map( 'intval', array_values( $tt_ids ) );
}

哇,代码真多!别慌,咱们一步一步来。

1. 参数校验和准备工作

    $post_id = (int) $post_id;

    if ( empty( $taxonomy ) ) {
        return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    }

    $taxonomy = sanitize_key( $taxonomy );

这一段主要是做一些参数的校验,确保传入的参数是有效的。

  • $post_id = (int) $post_id;:将 $post_id 强制转换为整数,确保它是文章的 ID。
  • if ( empty( $taxonomy ) ) { ... }:检查 $taxonomy 是否为空,如果为空,则返回一个 WP_Error 对象,提示 taxonomy 无效。
  • $taxonomy = sanitize_key( $taxonomy );:使用 sanitize_key() 函数对 $taxonomy 进行清理,确保它是一个安全的键名。

2. 获取现有的 Term Taxonomy IDs

    $term_taxonomy_ids = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'tt_ids' ) );

    if ( is_wp_error( $term_taxonomy_ids ) ) {
        return $term_taxonomy_ids;
    }

    $term_taxonomy_ids = array_map( 'intval', $term_taxonomy_ids );

这一步是为了获取文章当前已经关联的 term taxonomy IDs。

  • wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'tt_ids' ) );:调用 wp_get_object_terms() 函数,传入 $post_id$taxonomy 和一个选项数组,'fields' => 'tt_ids' 表示只获取 term taxonomy IDs。
  • if ( is_wp_error( $term_taxonomy_ids ) ) { ... }:检查 wp_get_object_terms() 的返回值是否是一个 WP_Error 对象,如果是,则直接返回该错误。
  • $term_taxonomy_ids = array_map( 'intval', $term_taxonomy_ids );:使用 array_map() 函数将 $term_taxonomy_ids 数组中的所有元素转换为整数。

3. 处理传入的 Terms

    if ( ! is_array( $terms ) ) {
        $terms = array( $terms );
    }

    $terms = array_filter( $terms );

这里处理传入的 $terms 参数,确保它是一个数组,并且数组中的元素都是有效的。

  • if ( ! is_array( $terms ) ) { ... }:如果 $terms 不是一个数组,则将其转换为一个包含 $terms 的数组。
  • $terms = array_filter( $terms );:使用 array_filter() 函数过滤 $terms 数组,移除所有空值(例如 nullfalse''0 等)。

4. 如果没有 Terms 且文章有现有的 Term Taxonomy IDs,则删除它们

    if ( empty( $terms ) && ! empty( $term_taxonomy_ids ) ) {
        return wp_remove_object_terms( $post_id, $term_taxonomy_ids, $taxonomy );
    }

如果传入的 $terms 为空,并且文章已经关联了一些 term taxonomy IDs,则调用 wp_remove_object_terms() 函数删除这些关联。

5. 创建或获取 Term Taxonomy IDs

    if ( ! empty( $terms ) ) {
        $new_tt_ids = wp_create_term_taxonomies( $terms, $taxonomy );

        if ( is_wp_error( $new_tt_ids ) ) {
            return $new_tt_ids;
        }

        if ( ! is_array( $new_tt_ids ) ) {
            return new WP_Error( 'unexpected_output', __( 'Unexpected output.' ) );
        }

        $tt_ids = array_values( $new_tt_ids );
    } else {
        $tt_ids = array();
    }

如果传入的 $terms 不为空,则需要创建或获取对应的 term taxonomy IDs。

  • $new_tt_ids = wp_create_term_taxonomies( $terms, $taxonomy );:调用 wp_create_term_taxonomies() 函数,传入 $terms$taxonomy,该函数会创建或获取对应的 term taxonomy IDs。
  • if ( is_wp_error( $new_tt_ids ) ) { ... }:检查 wp_create_term_taxonomies() 的返回值是否是一个 WP_Error 对象,如果是,则直接返回该错误。
  • if ( ! is_array( $new_tt_ids ) ) { ... }:检查 wp_create_term_taxonomies() 的返回值是否是一个数组,如果不是,则返回一个 WP_Error 对象。
  • $tt_ids = array_values( $new_tt_ids );:使用 array_values() 函数获取 $new_tt_ids 数组的值,并将其赋值给 $tt_ids

6. 合并 Term Taxonomy IDs (如果 $append 为 true)

    if ( $append ) {
        $tt_ids = array_merge( $term_taxonomy_ids, $tt_ids );
    }

如果 $append 参数为 true,则将现有的 term taxonomy IDs 和新的 term taxonomy IDs 合并。

7. 清理和准备 Term Taxonomy IDs

    $tt_ids = array_map( 'intval', $tt_ids );
    $tt_ids = array_unique( $tt_ids );
    $tt_ids = array_diff( $tt_ids, $term_taxonomy_ids );

    $tt_ids = array_map( 'intval', $tt_ids );
    $term_taxonomy_ids = array_map( 'intval', $term_taxonomy_ids );

这一步对 $tt_ids 进行清理,确保其中包含的是唯一的、新增的 term taxonomy IDs。

  • $tt_ids = array_map( 'intval', $tt_ids );:将 $tt_ids 数组中的所有元素转换为整数。
  • $tt_ids = array_unique( $tt_ids );:使用 array_unique() 函数移除 $tt_ids 数组中的重复元素。
  • $tt_ids = array_diff( $tt_ids, $term_taxonomy_ids );:使用 array_diff() 函数移除 $tt_ids 数组中已经存在的 term taxonomy IDs,只保留新增的 IDs。
  • 再次使用array_map$tt_ids$term_taxonomy_ids数组中的所有元素转换为整数。

8. 插入新的 Term Relationships

    $insertions = array();
    $deleteions = array();

    foreach ( (array) $tt_ids as $tt_id ) {
        $insertions[] = $wpdb->prepare( "(%d, %d, %s)", $post_id, $tt_id, $taxonomy );
    }

    if ( ! empty( $insertions ) ) {
        $query = "INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id, term_order) VALUES " . implode( ',', $insertions );
        $wpdb->query( $query );
    }

这一步将新的 term taxonomy IDs 插入到 wp_term_relationships 表中,建立文章和 term taxonomy 之间的关联。

  • $insertions = array();:初始化一个空数组 $insertions,用于存储要插入的 SQL 语句片段。
  • foreach ( (array) $tt_ids as $tt_id ) { ... }:遍历 $tt_ids 数组,为每个 term taxonomy ID 构建一个 SQL 语句片段。
  • $wpdb->prepare( "(%d, %d, %s)", $post_id, $tt_id, $taxonomy );:使用 $wpdb->prepare() 函数构建一个安全的 SQL 语句片段,防止 SQL 注入。
  • if ( ! empty( $insertions ) ) { ... }:如果 $insertions 数组不为空,则执行插入操作。
  • $query = "INSERT INTO {$wpdb->term_relationships} (object_id, term_taxonomy_id, term_order) VALUES " . implode( ',', $insertions );:将 $insertions 数组中的所有 SQL 语句片段连接成一个完整的 INSERT 语句。
  • $wpdb->query( $query );:执行 SQL 查询,将新的 term relationships 插入到 wp_term_relationships 表中。

9. 删除不再需要的 Term Relationships

    $deleteions = array_diff( $term_taxonomy_ids, array_map( 'intval', $tt_ids ) );

    if ( ! empty( $deleteions ) ) {
        $delete_tt_ids = implode( ',', $deleteions );
        $query = "DELETE FROM {$wpdb->term_relationships} WHERE object_id = %d AND term_taxonomy_id IN ($delete_tt_ids)";
        $wpdb->query( $wpdb->prepare( $query, $post_id ) );
    }

这一步删除文章不再需要的 term relationships。

  • $deleteions = array_diff( $term_taxonomy_ids, array_map( 'intval', $tt_ids ) );:使用 array_diff() 函数计算需要删除的 term taxonomy IDs,即存在于 $term_taxonomy_ids 中,但不存在于 $tt_ids 中的 IDs。
  • if ( ! empty( $deleteions ) ) { ... }:如果 $deleteions 数组不为空,则执行删除操作。
  • $delete_tt_ids = implode( ',', $deleteions );:将 $deleteions 数组中的所有 term taxonomy IDs 连接成一个字符串,用逗号分隔。
  • $query = "DELETE FROM {$wpdb->term_relationships} WHERE object_id = %d AND term_taxonomy_id IN ($delete_tt_ids)";:构建一个 DELETE 语句,删除文章不再需要的 term relationships。
  • $wpdb->query( $wpdb->prepare( $query, $post_id ) );:执行 SQL 查询,删除不再需要的 term relationships。

10. 清理缓存

    wp_cache_delete( $post_id, $taxonomy . '_relationships' );

删除与文章和 taxonomy 相关的缓存,确保数据是最新的。

11. 触发 Actions

    do_action( 'set_object_terms', $post_id, $terms, $taxonomy, $append, $old_tt_ids );

    wp_update_term_count_now( array_merge( (array) $deleteions, (array) $tt_ids ), $taxonomy );

    do_action( 'edited_' . $taxonomy, $post_id );

触发一些 actions,允许其他插件或主题对文章的 term relationships 进行自定义操作。

  • do_action( 'set_object_terms', $post_id, $terms, $taxonomy, $append, $old_tt_ids );:触发 set_object_terms action,传递文章 ID、terms、taxonomy、append 和旧的 term taxonomy IDs。
  • wp_update_term_count_now( array_merge( (array) $deleteions, (array) $tt_ids ), $taxonomy );:调用 wp_update_term_count_now() 函数,更新 taxonomy 的计数器。这个函数可是非常重要,它负责更新每个分类术语下文章的数量。
  • do_action( 'edited_' . $taxonomy, $post_id );:触发 edited_{$taxonomy} action,传递文章 ID。

12. 返回结果

    return array_map( 'intval', array_values( $tt_ids ) );

返回新的 term taxonomy IDs 数组。

wp_update_term_count_now():幕后英雄

咱们再来重点聊聊 wp_update_term_count_now() 这个函数,它可是维护分类术语计数的关键。

function wp_update_term_count_now( $terms, $taxonomy, $do_deferred = false ) {
    global $wpdb;

    $terms = array_map( 'intval', (array) $terms );

    $taxonomy = sanitize_key( $taxonomy );

    foreach ( (array) $terms as $term ) {
        $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->term_relationships} WHERE term_taxonomy_id = %d", $term ) );
        $wpdb->update( $wpdb->term_taxonomy, array( 'count' => $count ), array( 'term_taxonomy_id' => $term ) );
        wp_cache_delete( $term, $taxonomy . '_term_taxonomy' );
    }

    wp_cache_delete( 'all_terms', $taxonomy );

    do_action( 'edited_term_taxonomy', $term, $taxonomy );
}

这个函数接收一个 term taxonomy IDs 数组和一个 taxonomy 名称,然后遍历每个 term taxonomy ID,统计与其关联的文章数量,并将结果更新到 wp_term_taxonomy 表中。

代码分解:

  • 函数wp_update_term_count_now接收三个参数:$terms(要更新计数的术语ID数组),$taxonomy(术语的分类法名称),以及$do_deferred(一个布尔值,决定是否延迟计数更新,默认false)。

  • $terms = array_map( 'intval', (array) $terms );:确保$terms是一个整数数组。

  • $taxonomy = sanitize_key( $taxonomy );:确保$taxonomy是一个有效的键。

  • 循环遍历$terms数组:

    • $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->term_relationships} WHERE term_taxonomy_id = %d", $term ) );:使用SQL查询从wp_term_relationships表中获取与当前术语ID相关的文章数量。$wpdb->prepare用于安全地构建SQL查询,防止SQL注入。

    • $wpdb->update( $wpdb->term_taxonomy, array( 'count' => $count ), array( 'term_taxonomy_id' => $term ) );:使用SQL更新wp_term_taxonomy表中对应术语ID的count列。

    • wp_cache_delete( $term, $taxonomy . '_term_taxonomy' );:删除与当前术语相关的缓存,以确保下次访问时可以获取最新的数据。

  • wp_cache_delete( 'all_terms', $taxonomy );:删除与指定分类法相关的所有术语的缓存。

  • do_action( 'edited_term_taxonomy', $term, $taxonomy );:触发一个动作钩子edited_term_taxonomy,允许其他插件或主题在术语分类法被编辑后执行自定义操作。

表格总结:wp_set_post_terms() 的核心步骤

为了让大家更清晰地理解 wp_set_post_terms() 函数的工作流程,我整理了一个表格:

步骤 描述 涉及的关键函数
1. 参数校验 验证 $post_id$taxonomy 参数的有效性。 sanitize_key()
2. 获取现有 Term IDs 获取文章当前已经关联的 term taxonomy IDs。 wp_get_object_terms()
3. 处理传入的 Terms 确保传入的 $terms 参数是一个数组,并且数组中的元素都是有效的。 array_filter()
4. 删除旧的 Terms 如果 $terms 为空,且文章已经关联了一些 term taxonomy IDs,则删除这些关联。 wp_remove_object_terms()
5. 创建/获取 Term IDs 如果 $terms 不为空,则创建或获取对应的 term taxonomy IDs。 wp_create_term_taxonomies()
6. 合并 Term IDs (append) 如果 $append 为 true,则将现有的 term taxonomy IDs 和新的 term taxonomy IDs 合并。 array_merge()
7. 清理 Term IDs 对 term taxonomy IDs 进行清理,确保其中包含的是唯一的、新增的 IDs。 array_unique(), array_diff()
8. 插入新的 Relationships 将新的 term taxonomy IDs 插入到 wp_term_relationships 表中,建立文章和 term taxonomy 之间的关联。 $wpdb->prepare(), $wpdb->query()
9. 删除旧的 Relationships 删除文章不再需要的 term relationships。 array_diff(), $wpdb->prepare(), $wpdb->query()
10. 清理缓存 清理与文章和 taxonomy 相关的缓存。 wp_cache_delete()
11. 触发 Actions 触发一些 actions,允许其他插件或主题对文章的 term relationships 进行自定义操作。 do_action()
12. 更新计数 更新 taxonomy 的计数器。 wp_update_term_count_now()
13. 返回结果 返回新的 term taxonomy IDs 数组。 array_map(), array_values()

使用示例:给文章设置分类

$post_id = 123; // 文章ID
$categories = array( '新闻', '科技' ); // 要设置的分类名称
$taxonomy = 'category'; // 分类法名称

$result = wp_set_post_terms( $post_id, $categories, $taxonomy );

if ( is_wp_error( $result ) ) {
    echo 'Error: ' . $result->get_error_message();
} else {
    echo 'Categories set successfully!';
    print_r( $result ); // 输出 Term Taxonomy IDs
}

这段代码演示了如何使用 wp_set_post_terms() 函数给 ID 为 123 的文章设置 "新闻" 和 "科技" 两个分类。

总结:wp_set_post_terms(),WordPress 的分类大师

wp_set_post_terms() 函数是 WordPress 中一个非常重要的函数,它负责管理文章和分类术语之间的关系。理解它的源码,可以帮助咱们更好地理解 WordPress 的内部机制,也能让咱们在开发 WordPress 主题和插件时更加得心应手。

希望今天的讲解对大家有所帮助!下次有机会,咱们再聊聊 WordPress 的其他有趣的功能。谢谢大家!

发表回复

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