阐述 WordPress `wp_update_post_count_cache()` 函数的源码:如何高效地更新分类术语的文章计数。

各位朋友,大家好!我是你们的老朋友,今天咱们来聊聊 WordPress 里的一个“低调英雄”—— wp_update_post_count_cache() 函数。这个函数平时可能不太引人注意,但它默默地守护着你的分类、标签等术语的帖子数量,确保你在后台看到的数字都是准确的。如果这个函数出了问题,那你的网站数据可能就一团糟了,所以理解它的工作原理非常重要。

缘起:为什么需要这个函数?

在 WordPress 中,分类、标签等都属于术语(Terms)。每个术语都关联着若干篇文章(Posts)。为了方便显示每个分类下有多少篇文章,WordPress 需要维护一个计数。

最直接的方式是每次访问分类页面时都进行实时统计。但想想看,如果你的网站有很多文章,每次都统计那得多慢啊!这就像每次你想知道你家有多少本书都要重新数一遍,累不累?

所以,WordPress 采用了一种更高效的方式:缓存。 它会预先计算好每个术语的文章数量,并将结果存储起来。 这样,下次访问分类页面时,直接从缓存中读取,速度就快多了。

wp_update_post_count_cache() 函数的作用就是更新这个缓存,确保缓存中的数据与实际数据保持同步。

源码剖析:wp_update_post_count_cache() 的内部世界

现在,让我们深入 wp-includes/taxonomy.php 文件,一探 wp_update_post_count_cache() 函数的真面目。

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

    $term_ids = array();
    foreach ( (array) $terms as $term ) {
        if ( is_object( $term ) ) {
            $term_ids[] = intval( $term->term_id );
        } else {
            $term_ids[] = intval( $term );
        }
    }

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

    if ( ! $term_ids ) {
        return false;
    }

    $taxonomies = array();
    $update_terms = array();
    $_taxonomies = get_object_taxonomies( 'post', 'names' );

    if ( $_taxonomies ) {
        $taxonomies = $_taxonomies;
    }

    if ( ! $taxonomies ) {
        return false;
    }

    $taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";

    $query = "SELECT term_taxonomy_id, COUNT(*) AS count FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (" . implode( ',', $term_ids ) . ") AND object_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish') GROUP BY term_taxonomy_id";

    $counts = $wpdb->get_results( $query, OBJECT_K );

    if ( is_array( $counts ) ) {
        foreach ( $term_ids as $id ) {
            $count = isset( $counts[ $id ] ) ? intval( $counts[ $id ]->count ) : 0;

            $update_terms[ $id ] = $count;

            wp_cache_set( "term_count_$id", $count, 'terms' );
        }
    }

    if ( $do_deferred ) {
        wp_defer_term_counting( $update_terms );
    } else {
        _update_term_count_now( $update_terms );
    }

    return $terms;
}

逐行解读:代码背后的逻辑

  1. 参数处理:

    • $terms:要更新文章数量的术语,可以是术语对象数组,也可以是术语 ID 数组。
    • $do_deferred:一个布尔值,决定是否延迟更新文章计数。 默认值为 true,表示延迟更新。
  2. 提取术语 ID:

    $term_ids = array();
    foreach ( (array) $terms as $term ) {
        if ( is_object( $term ) ) {
            $term_ids[] = intval( $term->term_id );
        } else {
            $term_ids[] = intval( $term );
        }
    }

    这段代码将传入的 $terms 参数转换为一个包含术语 ID 的数组 $term_ids。它会遍历 $terms 数组,如果元素是一个术语对象,则提取其 term_id 属性;如果元素已经是术语 ID,则直接使用。

  3. 获取所有可用的分类法:

     $taxonomies = array();
    $_taxonomies = get_object_taxonomies( 'post', 'names' );
    
    if ( $_taxonomies ) {
        $taxonomies = $_taxonomies;
    }
    
    if ( ! $taxonomies ) {
        return false;
    }
    
    $taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";

    这段代码获取所有与 ‘post’ 类型相关的分类法。get_object_taxonomies() 函数返回与指定文章类型关联的分类法名称数组。然后将这个数组转换为一个用单引号引起来并用逗号分隔的字符串,以便在 SQL 查询中使用。

  4. 构建 SQL 查询:

    $query = "SELECT term_taxonomy_id, COUNT(*) AS count FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN (" . implode( ',', $term_ids ) . ") AND object_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish') GROUP BY term_taxonomy_id";

    这是整个函数的核心部分。这段 SQL 查询语句的作用是:统计每个术语(在 $term_ids 数组中)关联的已发布文章的数量。

    • {$wpdb->term_relationships} 表存储了文章与术语之间的关联关系。
    • {$wpdb->posts} 表存储了文章的信息。
    • WHERE term_taxonomy_id IN (" . implode( ',', $term_ids ) . ") 限制了只统计指定术语的文章数量。
    • AND object_id IN (SELECT ID FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish') 限制了只统计已发布的文章。
    • GROUP BY term_taxonomy_id 按照术语 ID 进行分组,以便统计每个术语的文章数量。
  5. 执行查询并更新缓存:

    $counts = $wpdb->get_results( $query, OBJECT_K );
    
    if ( is_array( $counts ) ) {
        foreach ( $term_ids as $id ) {
            $count = isset( $counts[ $id ] ) ? intval( $counts[ $id ]->count ) : 0;
    
            $update_terms[ $id ] = $count;
    
            wp_cache_set( "term_count_$id", $count, 'terms' );
        }
    }

    这段代码执行 SQL 查询,并将结果存储在 $counts 数组中。然后,它遍历 $term_ids 数组,对于每个术语 ID,从 $counts 数组中获取对应的文章数量,并使用 wp_cache_set() 函数更新缓存。

    • wp_cache_set( "term_count_$id", $count, 'terms' ) 将文章数量存储在 WordPress 的对象缓存中。缓存键的格式为 term_count_$id,其中 $id 是术语 ID。缓存组为 'terms'
  6. 延迟更新与立即更新:

    if ( $do_deferred ) {
        wp_defer_term_counting( $update_terms );
    } else {
        _update_term_count_now( $update_terms );
    }

    这段代码根据 $do_deferred 参数的值,决定是延迟更新还是立即更新文章计数。

    • 如果 $do_deferredtrue(默认值),则调用 wp_defer_term_counting() 函数,将更新任务添加到延迟队列中。这意味着文章计数不会立即更新,而是在稍后的某个时间点进行更新。这可以提高性能,特别是在批量更新文章时。
    • 如果 $do_deferredfalse,则调用 _update_term_count_now() 函数,立即更新文章计数。

延迟更新:wp_defer_term_counting()_update_term_count_now()

让我们再深入一点,看看 wp_defer_term_counting()_update_term_count_now() 这两个函数是如何工作的。

  • wp_defer_term_counting( $terms )

    这个函数的作用是将要更新的术语计数存储在一个全局变量中,以便稍后批量更新。

    function wp_defer_term_counting( $terms ) {
        global $_deferred_term_counting;
    
        if ( ! isset( $_deferred_term_counting ) ) {
            $_deferred_term_counting = array();
        }
    
        $_deferred_term_counting = array_merge( $_deferred_term_counting, $terms );
    }

    它会将传入的 $terms 数组合并到全局变量 $_deferred_term_counting 中。 $_deferred_term_counting 是一个关联数组,键是术语 ID,值是要更新的文章数量。

  • _update_term_count_now( $terms )

    这个函数的作用是立即更新数据库中的术语计数。

    function _update_term_count_now( $terms ) {
        global $wpdb;
    
        foreach ( (array) $terms as $term_id => $count ) {
            $term_id = (int) $term_id;
            $count   = (int) $count;
    
            $wpdb->update( $wpdb->term_taxonomy, array( 'count' => $count ), array( 'term_taxonomy_id' => $term_id ) );
    
            clean_term_cache( $term_id );
        }
    }

    它会遍历 $terms 数组,对于每个术语 ID,使用 $wpdb->update() 函数更新 {$wpdb->term_taxonomy} 表中的 count 字段。 {$wpdb->term_taxonomy} 表存储了术语的分类信息,包括文章数量。

    更新完数据库后,它还会调用 clean_term_cache() 函数清除术语的缓存,确保下次访问分类页面时,能够从数据库中读取最新的文章数量。

何时调用 wp_update_post_count_cache()

wp_update_post_count_cache() 函数通常在以下情况下被调用:

  • 文章被发布、更新或删除时: 当文章的状态发生变化时,需要更新相关术语的文章数量。
  • 文章与术语之间的关联关系发生变化时: 当文章被添加到某个分类或从某个分类中移除时,需要更新相关术语的文章数量。
  • 在后台管理界面中: WordPress 后台提供了一些工具,可以手动更新术语的文章数量。

性能优化:延迟更新的优势

正如我们前面提到的,wp_update_post_count_cache() 函数默认采用延迟更新的方式。 这样做的好处是:

  • 减少数据库写入次数: 如果一次性更新多篇文章,延迟更新可以将多次数据库写入合并为一次,从而提高性能。
  • 避免不必要的缓存失效: 频繁的数据库写入会导致缓存频繁失效,延迟更新可以减少缓存失效的次数,从而提高网站的响应速度。

使用场景示例

假设你正在开发一个插件,该插件允许用户批量更新文章的分类。 在更新完文章的分类后,你需要调用 wp_update_post_count_cache() 函数来更新相关分类的文章数量。

// 获取要更新的分类 ID 数组
$term_ids = array( 1, 2, 3 );

// 调用 wp_update_post_count_cache() 函数,更新分类的文章数量
wp_update_post_count_cache( $term_ids );

总结:wp_update_post_count_cache() 的重要性

wp_update_post_count_cache() 函数是 WordPress 中一个非常重要的函数。它负责更新分类、标签等术语的文章数量,确保网站数据的准确性。 理解它的工作原理,可以帮助你更好地优化 WordPress 网站的性能,避免数据错误。

表格总结:核心函数的功能

函数名 功能
wp_update_post_count_cache() 更新指定术语的文章计数缓存。 会根据 $do_deferred 参数决定是立即更新还是延迟更新。
wp_defer_term_counting() 将要更新的术语计数存储在一个全局变量中,以便稍后批量更新。
_update_term_count_now() 立即更新数据库中的术语计数。 更新完数据库后,还会清除术语的缓存。
get_object_taxonomies() 获取与指定文章类型关联的分类法名称数组。
wp_cache_set() 将数据存储到 WordPress 的对象缓存中。
clean_term_cache() 清除术语的缓存。

常见问题解答

  • 问:我应该何时调用 wp_update_post_count_cache() 函数?

    答:当文章被发布、更新或删除时,或者当文章与术语之间的关联关系发生变化时,都应该调用 wp_update_post_count_cache() 函数。

  • 问:延迟更新和立即更新有什么区别?

    答:延迟更新会将更新任务添加到延迟队列中,稍后批量更新;立即更新会立即更新数据库。 延迟更新可以提高性能,特别是在批量更新文章时。

  • 问:为什么我的分类文章数量显示不正确?

    答:可能是缓存问题。 可以尝试清除 WordPress 的对象缓存,或者手动调用 wp_update_post_count_cache() 函数来更新分类的文章数量。

  • 问:如何强制 wp_update_post_count_cache() 函数立即更新?

    答:可以将 $do_deferred 参数设置为 false,例如:wp_update_post_count_cache( $term_ids, false )

希望今天的讲解能帮助大家更好地理解 wp_update_post_count_cache() 函数。 记住,理解 WordPress 的底层机制,才能更好地驾驭它,打造出更高效、更稳定的网站。下次有机会再和大家分享更多 WordPress 源码相关的知识! 祝大家编码愉快!

发表回复

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