分析 `wp_set_post_terms()` 函数的源码,它是如何为文章设置分类术语的?

各位好,今天咱们来聊聊 WordPress 里的一个相当重要的函数——wp_set_post_terms()。 简单来说,它就是个“分类术语搬运工”,专门负责给文章安排分类、标签等等。

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

在 WordPress 的世界里,文章(Post)就像一个个独立的个体,而分类术语(Taxonomy Terms)则像是它们的“标签”或“分类”。通过这些标签,我们能更好地组织内容,方便用户查找,也能让搜索引擎更容易理解文章的主题。 如果没有分类术语,你的博客文章就会像一堆散落的玩具,乱七八糟,没人知道该从哪里开始玩。

wp_set_post_terms() 函数:核心功能

wp_set_post_terms() 函数的功能非常明确: 为指定的文章设置指定的分类术语。

  • 文章: 就是你想给它贴标签的文章。
  • 分类术语:就是你想给文章贴上的标签,比如“科技”、“美食”、“旅行”等等。

源码剖析:一步一步揭秘

咱们直接上源码,来扒一扒 wp_set_post_terms() 的老底。为了方便理解,我们尽量简化一些不重要的细节。

function wp_set_post_terms( $post_id, $terms, $taxonomy = 'category', $append = false ) {
    $post_id = (int) $post_id;

    $taxonomy = sanitize_key( $taxonomy );

    $id_based = false; // 默认是按名称匹配

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

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

    $term_ids = array();

    if ( ! empty( $terms ) ) {
        foreach ( $terms as $term ) {
            $id = false;
            if ( is_int( $term ) ) {
                $id = $term;
                $id_based = true;
            } else {
                // 尝试通过名称获取术语 ID
                $term_info = term_exists( $term, $taxonomy );
                if ( $term_info ) {
                    $id = intval( $term_info['term_id'] );
                }
            }
            if ( $id ) {
                $term_ids[] = (int) $id;
            } else {
                // 如果术语不存在,则创建它
                $term_info = wp_insert_term( $term, $taxonomy );
                if ( ! is_wp_error( $term_info ) ) {
                    $term_ids[] = (int) $term_info['term_id'];
                }
            }
        }
    }

    $term_ids = array_unique( array_map( 'intval', $term_ids ) );

    if ( empty( $term_ids ) ) {
        return wp_delete_object_term_relationships( $post_id, $taxonomy );
    }

    $tt_ids = wp_set_object_terms( $post_id, $term_ids, $taxonomy, $append, $id_based );

    wp_cache_delete( $post_id, 'terms' ); // 清除缓存

    return $tt_ids;
}

源码解读:逐行剖析

  1. 参数准备阶段:

    • $post_id = (int) $post_id;: 确保文章 ID 是整数,类型转换是好习惯。
    • $taxonomy = sanitize_key( $taxonomy );: 对分类术语的名称进行清理,防止恶意代码注入。
    • if ( ! taxonomy_exists( $taxonomy ) ) { ... }: 检查分类术语是否存在,如果不存在,就返回一个错误。
  2. 术语处理阶段:

    • if ( ! is_array( $terms ) ) { $terms = array( $terms ); }: 确保 $terms 是一个数组,方便后续处理。
    • foreach ( $terms as $term ) { ... }: 循环处理每一个术语。
    • if ( is_int( $term ) ) { ... }: 如果术语是一个整数,那么就认为它是一个术语 ID。
    • else { ... }: 如果术语不是一个整数,那么就认为它是一个术语名称,尝试通过 term_exists() 函数获取术语 ID。 如果获取不到,就通过 wp_insert_term() 函数创建新的术语。
  3. 核心操作:

    • $tt_ids = wp_set_object_terms( $post_id, $term_ids, $taxonomy, $append, $id_based );: 调用 wp_set_object_terms() 函数,完成真正的术语设置操作。
  4. 清理缓存:

    • wp_cache_delete( $post_id, 'terms' );: 清除文章的术语缓存,确保下次获取时是最新的数据。

wp_set_object_terms() 函数:幕后英雄

wp_set_post_terms() 函数本身并没有直接操作数据库,它只是调用了另一个函数 wp_set_object_terms() 来完成真正的术语设置操作。 wp_set_object_terms() 函数才是真正的幕后英雄。

function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false, $id_based = false ) {
    global $wpdb;

    $object_id = (int) $object_id;

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

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

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

    $old_tt_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'tt_ids' ) );
    $old_tt_ids = array_map( 'intval', $old_tt_ids );
    $old_tt_ids = array_unique( $old_tt_ids );

    $tt_ids = array();
    if ( ! empty( $terms ) ) {
        $existing_terms = array();
        $new_terms = array();
        foreach ( $terms as $term_id ) {
            $term_exists = term_exists( $term_id, $taxonomy );
            if ( $term_exists ) {
                $existing_terms[] = intval( $term_exists['term_id'] );
            } else {
                // 处理不存在的术语(虽然理论上不应该发生)
                // 可以选择创建,也可以直接跳过
                // 这里选择跳过
            }
        }

        if ( ! empty( $existing_terms ) ) {
            $term_ids = array_unique( $existing_terms );

            $tt_ids = array();
            foreach ( $term_ids as $term_id ) {
                $tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE term_id = %d AND taxonomy = %s", $term_id, $taxonomy ) );
                if ( $tt_id ) {
                    $tt_ids[] = (int) $tt_id;
                }
            }
        }
    }

    if ( true === $append ) {
        $tt_ids = array_merge( $old_tt_ids, $tt_ids );
        $tt_ids = array_unique( $tt_ids );
    } else {
        $delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
        if ( ! empty( $delete_tt_ids ) ) {
            $delete_tt_ids = array_map( 'intval', $delete_tt_ids );
            $query = "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN (" . implode( ',', $delete_tt_ids ) . ")";
            $wpdb->query( $wpdb->prepare( $query, $object_id ) );

            wp_cache_delete( $object_id, 'terms' );
        }
    }

    $new_tt_ids = array_diff( $tt_ids, $old_tt_ids );
    if ( ! empty( $new_tt_ids ) ) {
        $query = "INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES ";
        $values = array();
        foreach ( $new_tt_ids as $tt_id ) {
            $values[] = $wpdb->prepare( "(%d, %d, 0)", $object_id, $tt_id );
        }
        $query .= implode( ',', $values );

        $wpdb->query( $query );

        wp_cache_delete( $object_id, 'terms' );
    }

    wp_cache_delete( $object_id, 'terms' );

    return $tt_ids;
}

wp_set_object_terms() 函数解读:

  1. 参数校验:

    • 确保参数 $object_id 是整数,并且 $taxonomy 是有效的分类法。
  2. 获取旧的关联:

    • 使用 wp_get_object_terms() 获取文章当前关联的术语 ID (term_taxonomy_id)。
  3. 处理新的术语:

    • 遍历 $terms 数组,确保每个术语都存在。
    • 通过 term_exists() 检查术语是否存在,如果不存在,可以选择创建或者跳过。
  4. 判断 append 参数:

    • $append = true:将新的术语追加到旧的术语列表中。
    • $append = false:替换旧的术语列表。
  5. 删除旧的关联:

    • 如果 $appendfalse,则需要删除文章与旧术语之间的关联。
    • 使用 array_diff() 找出需要删除的术语 ID,并使用 DELETE 语句从 $wpdb->term_relationships 表中删除相应的记录。
  6. 添加新的关联:

    • 使用 array_diff() 找出需要添加的术语 ID,并使用 INSERT 语句将文章与新术语之间的关联添加到 $wpdb->term_relationships 表中。
  7. 清除缓存:

    • 清除文章的术语缓存。

数据库结构:幕后存储

要理解 wp_set_post_terms() 的工作原理,还需要了解 WordPress 的数据库结构。

表名 作用
wp_terms 存储术语的信息,比如术语 ID、术语名称、术语别名等。
wp_term_taxonomy 存储术语的分类信息,比如分类术语 ID、术语 ID、分类法名称、描述、文章数量等。
wp_term_relationships 存储文章和术语之间的关联关系,比如文章 ID、分类术语 ID、排序等。

wp_term_relationships 表是关键,它将文章(object_id)和分类术语(term_taxonomy_id)联系起来。 wp_set_object_terms() 函数的核心操作就是在这个表中插入或删除记录。

实战演练:代码示例

<?php
// 获取文章 ID
$post_id = 123;

// 设置分类(category)
$categories = array( '科技', '互联网' );
wp_set_post_terms( $post_id, $categories, 'category' );

// 设置标签(post_tag)
$tags = array( 'WordPress', '函数', '源码' );
wp_set_post_terms( $post_id, $tags, 'post_tag', true ); // 追加标签,而不是替换

// 设置自定义分类法(my_custom_taxonomy)
$custom_terms = array( 4, 5, 6 ); // 使用术语 ID
wp_set_post_terms( $post_id, $custom_terms, 'my_custom_taxonomy' );
?>

注意事项:小技巧和陷阱

  • 性能优化: 如果你需要批量设置大量文章的分类术语,建议使用 wp_insert_term()wp_set_object_terms() 函数的底层方法,并尽量减少数据库查询次数。
  • 错误处理: 在使用 wp_insert_term() 函数创建新术语时,要检查返回值是否为 WP_Error 对象,以便及时处理错误。
  • 术语 ID vs 术语名称: wp_set_post_terms() 函数既可以接受术语 ID,也可以接受术语名称。 如果使用术语名称,函数会自动查找或创建相应的术语。
  • $append 参数: 要注意 $append 参数的作用,它决定了是追加术语还是替换术语。

总结:分类术语的艺术

wp_set_post_terms() 函数是 WordPress 中一个非常重要的函数,它负责为文章设置分类术语。 理解它的工作原理,可以帮助我们更好地组织内容,优化网站性能,并为用户提供更好的浏览体验。 下次再有文章需要贴标签,你就知道该找谁了! 别忘了,好的分类就像好的导游,能让你的读者轻松找到他们想看的东西。

发表回复

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