分析 WordPress `wp_update_term()` 函数源码:分类术语更新与计数更新的逻辑。

各位老铁,晚上好!我是你们的老朋友,今天咱们来聊聊 WordPress 里面一个挺重要的函数:wp_update_term()。这玩意儿,乍一听好像挺学术,但实际上,它就是负责更新分类术语信息的,比如你改个分类的名字,换个别名,都得靠它。而且,它还负责更新分类下的文章数量,也就是咱们常说的“计数更新”。

咱们今天就来扒一扒它的源码,看看它到底是怎么运作的,以及分类术语更新和计数更新的逻辑是什么。准备好了吗?Let’s go!

1. 初探 wp_update_term():这是个啥?

首先,咱们得搞清楚 wp_update_term() 这个函数是干嘛的。简单来说,它接受一些参数,然后去数据库里更新对应的分类术语信息。

它的基本用法是这样的:

$term_id = 123; // 要更新的分类术语 ID
$taxonomy = 'category'; // 分类法,比如 category(分类)、post_tag(标签)

$args = array(
    'name' => '新的分类名称',
    'slug' => 'new-category-slug',
    'description' => '新的分类描述',
    'parent' => 0 // 父级分类 ID,0 表示顶级分类
);

$result = wp_update_term( $term_id, $taxonomy, $args );

if ( is_wp_error( $result ) ) {
    // 更新失败
    echo '更新失败:' . $result->get_error_message();
} else {
    // 更新成功
    echo '更新成功!新的分类术语 ID 是:' . $result['term_id'];
}

看到了吧,wp_update_term() 接受三个参数:

  • $term_id: 要更新的分类术语 ID。
  • $taxonomy: 分类法(taxonomy),比如 category(分类)、post_tag(标签)。
  • $args: 一个数组,包含了要更新的字段和对应的值。

2. 源码剖析:一步一步看它怎么跑

接下来,咱们来深入源码,看看 wp_update_term() 到底是怎么实现的。为了方便阅读,我这里只截取了核心代码,并加上了注释。

function wp_update_term( $term_id, $taxonomy, $args = array() ) {
    global $wpdb;

    $term_id = (int) $term_id;

    if ( empty( $term_id ) || empty( $taxonomy ) ) {
        return new WP_Error( 'missing_term_id', __( '缺少分类术语 ID 或分类法。' ) );
    }

    $term = get_term( $term_id, $taxonomy );

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

    if ( empty( $term ) ) {
        return new WP_Error( 'invalid_term', __( '无效的分类术语。' ) );
    }

    $defaults = array(
        'name'        => $term->name,
        'slug'        => $term->slug,
        'description' => $term->description,
        'parent'      => $term->parent,
    );

    $args = wp_parse_args( $args, $defaults );

    $name        = trim( sanitize_term_field( 'name', $args['name'], $term_id, $taxonomy, 'db' ) );
    $slug        = sanitize_title( $args['slug'] );
    $description = trim( sanitize_term_field( 'description', $args['description'], $term_id, $taxonomy, 'db' ) );
    $parent      = (int) $args['parent'];

    // 检查名称是否为空
    if ( empty( $name ) ) {
        return new WP_Error( 'empty_term_name', __( '分类术语名称不能为空。' ) );
    }

    // 检查别名是否重复
    $slug_exists = term_exists( $slug, $taxonomy, $parent );

    if ( $slug_exists && $slug_exists['term_id'] != $term_id ) {
        return new WP_Error( 'term_exists', __( '该别名已存在。' ) );
    }

    $data = compact( 'name', 'slug', 'description', 'parent' );
    $where = array( 'term_id' => $term_id );

    /**
     * Fires immediately before a term is updated in the database.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     An array of term data.
     */
    do_action( 'pre_update_term', $term_id, $taxonomy, $args );

    $result = $wpdb->update( $wpdb->terms, $data, $where );

    if ( false === $result ) {
        return new WP_Error( 'db_update_error', __( '数据库更新失败。' ), $wpdb->last_error );
    }

    clean_term_cache( $term_id, $taxonomy, false );

    /**
     * Fires after a term is updated in the database.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param string $taxonomy Taxonomy slug.
     * @param array  $args     An array of term data.
     */
    do_action( 'edit_term', $term_id, $taxonomy, $args );
    do_action( "edit_{$taxonomy}", $term_id, $taxonomy, $args );
    do_action( 'edited_term', $term_id, $taxonomy, $args );
    do_action( "edited_{$taxonomy}", $term_id, $taxonomy, $args );

    wp_cache_delete( $term_id, $taxonomy . '_children' );

    /**
     * Fires after a term is updated in the database, but before the term object cache is cleaned.
     *
     * @since 4.4.0
     *
     * @param int    $term_id  Term ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action( 'saved_term', $term_id, $taxonomy );

    // 更新计数
    _update_term_count( $term_id, $taxonomy );

    /**
     * Fires after a term has been updated in the database and the term object cache has been cleaned.
     *
     * @since 2.3.0
     *
     * @param int    $term_id  Term ID.
     * @param string $taxonomy Taxonomy slug.
     */
    do_action( 'update_term', $term_id, $taxonomy );

    return array( 'term_id' => $term_id, 'term_taxonomy_id' => $term->term_taxonomy_id );
}

咱们来一步一步分析:

  1. 参数校验:
    • 首先,函数会检查 $term_id$taxonomy 是否为空,如果为空,就返回一个错误。
    • 然后,它会通过 get_term() 函数获取分类术语的信息,如果获取失败,也返回一个错误。
  2. 参数合并:
    • wp_parse_args() 函数会将传入的 $args 数组和默认值合并。这样,如果你只传入了要修改的字段,其他字段就会使用原来的值。
  3. 数据清洗:
    • sanitize_term_field() 函数会对 $name$description 进行安全过滤,防止 XSS 攻击。
    • sanitize_title() 函数会对 $slug 进行处理,生成一个合法的别名。
  4. 数据验证:
    • 函数会检查 $name 是否为空,如果为空,就返回一个错误。
    • term_exists() 函数会检查 $slug 是否重复,如果重复,也返回一个错误。注意,这里会排除掉当前要更新的分类术语,也就是说,如果只是修改了其他字段,而 $slug 没有改变,就不会报错。
  5. 数据库更新:
    • $wpdb->update() 函数会将数据更新到数据库中。
  6. 缓存清理:
    • clean_term_cache() 函数会清理分类术语的缓存,确保下次访问时能获取到最新的数据。
  7. 钩子(Actions):
    • 在更新前后,会触发一系列的钩子,允许开发者在这些关键点插入自定义的代码。
  8. 计数更新:
    • _update_term_count() 函数会更新分类术语下的文章数量。这个函数是咱们今天重点要讲的。
  9. 返回结果:
    • 函数会返回一个数组,包含了分类术语 ID 和分类法 ID。

3. 计数更新:_update_term_count() 的秘密

咱们再来看看 _update_term_count() 函数,它负责更新分类术语下的文章数量。这个函数稍微复杂一些,因为它需要考虑不同的文章状态(比如发布、草稿、私有等)和不同的分类法。

function _update_term_count( $terms, $taxonomy, $do_deferred = true ) {
    global $wpdb;

    $object_types = (array) get_object_taxonomies( $taxonomy, 'names' );

    if ( empty( $object_types ) ) {
        return;
    }

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

    $object_types = array_map( array( $wpdb, 'escape' ), $object_types );
    $terms = array_map( array( $wpdb, 'escape' ), $terms );

    $taxonomy = esc_sql( $taxonomy );

    foreach ( $object_types as $object_type ) {
        $count = $wpdb->get_results(
            "
            SELECT term_taxonomy_id, COUNT(*) AS count FROM {$wpdb->term_relationships} AS tr
            INNER JOIN {$wpdb->posts} AS p ON (p.ID = tr.object_id)
            WHERE tr.term_taxonomy_id IN (SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE term_id IN (" . implode( ',', $terms ) . ") AND taxonomy = '$taxonomy')
            AND p.post_type = '$object_type' AND p.post_status IN ('publish', 'future', 'private')
            GROUP BY tr.term_taxonomy_id
            "
        , ARRAY_A );

        foreach ( $count as $c ) {
            $wpdb->update( $wpdb->term_taxonomy, array( 'count' => $c['count'] ), array( 'term_taxonomy_id' => $c['term_taxonomy_id'] ) );
            wp_cache_delete( $c['term_taxonomy_id'], 'term_taxonomy' );
        }
    }

    wp_cache_delete( 'get_terms', $taxonomy );

    if ( $do_deferred ) {
        wp_defer_term_counting( false );
    }
}

咱们来解读一下:

  1. 获取对象类型:
    • get_object_taxonomies() 函数会获取与当前分类法关联的对象类型(object types),比如 post、page 等。
  2. 数据清洗:
    • 函数会对 $terms$object_types$taxonomy 进行安全过滤,防止 SQL 注入。
  3. 循环对象类型:
    • 函数会循环遍历每个对象类型,然后执行 SQL 查询。
  4. SQL 查询:
    • 这个 SQL 查询是核心,它会统计每个分类术语下,指定对象类型(比如 post)的文章数量,并且只统计 publishfutureprivate 状态的文章。
    • 查询语句比较复杂,简单来说,它做了以下几件事:
      • 连接 wp_term_relationships 表和 wp_posts 表,找到所有与指定分类术语关联的文章。
      • 根据文章类型和文章状态进行过滤。
      • 按照 term_taxonomy_id 进行分组,统计每个分类术语下的文章数量。
  5. 更新计数:
    • $wpdb->update() 函数会将统计结果更新到 wp_term_taxonomy 表中。
  6. 缓存清理:
    • wp_cache_delete() 函数会清理分类术语的缓存。
  7. 延迟计数:
    • wp_defer_term_counting() 函数用于延迟计数,避免在高并发情况下频繁更新计数,影响性能。

4. 分类术语更新与计数更新的逻辑

现在,咱们来总结一下分类术语更新和计数更新的逻辑:

步骤 分类术语更新 计数更新
1 接收参数:term_id, taxonomy, args 接收参数:term_id, taxonomy
2 参数校验:检查 term_idtaxonomy 是否为空,以及分类术语是否存在 获取对象类型:获取与分类法关联的对象类型(比如 post)
3 参数合并:将传入的 args 数组和默认值合并 数据清洗:对参数进行安全过滤
4 数据清洗:对 namedescription 进行安全过滤,对 slug 进行处理 循环对象类型:遍历每个对象类型
5 数据验证:检查 name 是否为空,检查 slug 是否重复 SQL 查询:统计每个分类术语下,指定对象类型的文章数量
6 数据库更新:将数据更新到 wp_terms 表中 更新计数:将统计结果更新到 wp_term_taxonomy 表中
7 缓存清理:清理分类术语的缓存 缓存清理:清理分类术语的缓存
8 触发钩子:在更新前后触发一系列的钩子 延迟计数:根据情况延迟计数
9 返回结果:返回分类术语 ID 和分类法 ID

可以看到,分类术语更新主要负责更新分类术语的基本信息,比如名称、别名、描述等。而计数更新则负责更新分类术语下的文章数量。

5. 注意事项

  • 性能问题: 频繁更新分类术语可能会影响性能,特别是当文章数量很多时。因此,应该尽量避免频繁更新。
  • 缓存问题: 在更新分类术语后,一定要清理缓存,确保下次访问时能获取到最新的数据。
  • 数据安全: 在处理用户输入的数据时,一定要进行安全过滤,防止 XSS 攻击和 SQL 注入。
  • 钩子: 可以利用 WordPress 提供的钩子,在更新前后插入自定义的代码,实现更灵活的功能。

6. 总结

今天,咱们深入剖析了 WordPress 的 wp_update_term() 函数,了解了它的实现原理,以及分类术语更新和计数更新的逻辑。希望通过今天的讲解,大家能够对 WordPress 的分类术语管理有更深入的理解。

好了,今天的讲座就到这里,谢谢大家!如果有什么问题,欢迎在评论区留言,咱们一起探讨。下次再见!

发表回复

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