探讨 `wp_get_object_terms()` 函数的源码,它是如何获取指定文章或对象的分类术语的?

各位观众,大家好! 今天咱们不讲段子,来点硬核的,深入扒一扒 WordPress 里面一个常用的函数:wp_get_object_terms()。 这家伙看着不起眼,但在文章和分类之间牵线搭桥,作用可大了。 今天咱们就来拆解它,看看它到底是怎么把文章和分类术语(Terms)给“撮合”到一起的。

Part 1: 啥是 wp_get_object_terms()?它要干嘛?

简单来说,wp_get_object_terms() 函数的作用就是:根据给定的对象(比如文章、自定义文章类型等)的 ID,获取与这个对象相关联的分类术语(比如文章的分类、标签、自定义分类法等等)。

你可以把它想象成一个媒婆,手里拿着对象的 ID,然后去数据库里查找,把所有跟这个对象“有关系”的分类术语都找出来,打包返回给你。

函数原型:

wp_get_object_terms(
    int|int[] $object_ids,
    string|string[] $taxonomies = 'post_tag',
    array $args = array()
);
  • $object_ids: 对象的 ID。可以是一个 ID,也可以是 ID 的数组。比如,你想知道文章 ID 为 1 的分类术语,这里就填 1。
  • $taxonomies: 分类法的名称。默认是 ‘post_tag’ (标签)。 比如,你想获取文章的分类,这里就填 ‘category’。
  • $args: 一些额外的参数,用于控制返回的结果。比如,你可以指定返回结果的排序方式、数量等等。

返回值:

  • 成功:返回一个包含 WP_Term 对象的数组。每个 WP_Term 对象代表一个分类术语。
  • 失败:返回一个空数组或者 WP_Error 对象。

举个栗子:

$post_id = 123; // 假设文章 ID 是 123
$categories = wp_get_object_terms( $post_id, 'category' );

if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) {
    echo '这篇文章属于以下分类:';
    foreach ( $categories as $category ) {
        echo $category->name . ', ';
    }
} else {
    echo '这篇文章没有分类。';
}

这个例子会获取文章 ID 为 123 的所有分类,然后把分类的名称打印出来。

Part 2: 源码剖析:wp_get_object_terms() 的内部世界

现在,咱们就来深入源码,看看 wp_get_object_terms() 函数是如何实现的。 (以下源码基于 WordPress 最新版本,可能会略有差异)

function wp_get_object_terms( $object_ids, $taxonomies = 'post_tag', $args = array() ) {
    global $wpdb;

    if ( empty( $object_ids ) ) {
        return array();
    }

    $single = false;
    if ( ! is_array( $object_ids ) ) {
        $single      = true;
        $object_ids  = array( (int) $object_ids );
        $object_ids  = array_map( 'absint', $object_ids ); //确保是整数
        $object_id = $object_ids[0]; //缓存键用单个ID
    } else {
        $object_ids = array_map( 'absint', $object_ids );
        $object_id = implode( ',', $object_ids ); //缓存键用逗号分隔的ID
    }

    if ( empty( $taxonomies ) ) {
        return array();
    }

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

    $taxonomies = array_map( 'sanitize_key', $taxonomies );

    $defaults = array(
        'orderby'  => 'name',
        'order'    => 'ASC',
        'fields'   => 'all',
        'name'     => '',
        'slug'     => '',
        'count'    => false,
        'id_fields' => false,
        'get'      => '',
        'number'   => '',
        'offset'   => '',
        'search'   => '',
        'cache_domain' => 'core',
        'update_term_meta_cache' => true,
        'hierarchical' => true,
        'child_of' => 0,
        'parent' => '',
        'pad_counts' => false,
        'hide_empty' => false,
    );

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

    $orderby = strtolower( $args['orderby'] );
    $order   = strtoupper( $args['order'] );

    if ( ! in_array( $orderby, array( 'name', 'slug', 'term_group', 'term_id', 'id', 'description', 'count' ), true ) ) {
        $orderby = 'name';
    }

    if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
        $order = 'ASC';
    }

    $fields = $args['fields'];

    if ( ! in_array( $fields, array( 'all', 'ids', 'names', 'slugs', 'tt_ids' ), true ) ) {
        $fields = 'all';
    }

    $id_fields = (bool) $args['id_fields'];

    $cache_key = md5( var_export( wp_array_slice_assoc( $args, array( 'orderby', 'order', 'fields', 'name', 'slug', 'count', 'id_fields', 'get', 'number', 'offset', 'search', 'hierarchical', 'child_of', 'parent', 'pad_counts', 'hide_empty' ) ), true ) . serialize( $object_ids ) . serialize( $taxonomies ) ); // 构建缓存键
    $cache_domain = $args['cache_domain'];
    $cache = wp_cache_get( $cache_key, "{$cache_domain}_term_relationships" ); // 尝试从缓存中获取

    if ( false === $cache ) {
        $terms = _wp_get_object_terms( $object_ids, $taxonomies, $args ); // 如果缓存没有,则调用 _wp_get_object_terms 获取
        wp_cache_set( $cache_key, $terms, "{$cache_domain}_term_relationships" ); // 设置缓存
    } else {
        $terms = $cache;
    }

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

    if ( empty( $terms ) ) {
        return array();
    }

    if ( 'ids' === $fields ) {
        $terms = wp_list_pluck( $terms, 'term_id' );
    } elseif ( 'names' === $fields ) {
        $terms = wp_list_pluck( $terms, 'name' );
    } elseif ( 'slugs' === $fields ) {
        $terms = wp_list_pluck( $terms, 'slug' );
    } elseif ( 'tt_ids' === $fields ) {
        $terms = wp_list_pluck( $terms, 'term_taxonomy_id' );
    }

    if ( $single ) {
        return reset( $terms );
    }

    return $terms;
}

代码解读:

  1. 参数校验和处理:
    • 首先,检查 $object_ids$taxonomies 是否为空。如果为空,直接返回空数组。
    • 如果 $object_ids 不是数组,把它转换成数组。
    • 如果 $taxonomies 不是数组,也把它转换成数组。
    • 使用 array_mapabsint 函数确保 $object_ids 数组中的元素都是整数。
    • 使用 array_mapsanitize_key 函数对 $taxonomies 数组中的元素进行安全过滤。
  2. 参数合并和默认值:
    • 定义一个 $defaults 数组,包含一些默认的参数值,比如排序方式、排序字段、返回字段等等。
    • 使用 wp_parse_args 函数把用户传入的 $args 参数和 $defaults 数组合并,得到最终的参数。
  3. 构建缓存键:
    • 使用 md5 函数和 var_export/serialize 函数构建一个唯一的缓存键。 这个缓存键包含了所有的参数信息,这样可以确保不同的参数组合对应不同的缓存。
  4. 尝试从缓存中获取数据:
    • 使用 wp_cache_get 函数尝试从 WordPress 的对象缓存中获取数据。 如果缓存命中,直接返回缓存中的数据。
  5. 调用 _wp_get_object_terms 函数:
    • 如果缓存没有命中,调用 _wp_get_object_terms 函数从数据库中获取数据。 这个函数才是真正执行数据库查询的函数。
  6. 设置缓存:
    • 使用 wp_cache_set 函数把从数据库中获取到的数据设置到缓存中,以便下次使用。
  7. 处理返回结果:
    • 根据 $fields 参数的值,对返回结果进行处理。 如果 $fields 是 ‘ids’,则只返回分类术语的 ID。 如果 $fields 是 ‘names’,则只返回分类术语的名称。 以此类推。
  8. 返回结果:
    • 如果 $singletrue,则只返回数组中的第一个元素。 否则,返回整个数组。

Part 3: 深入 _wp_get_object_terms 函数:数据库查询的秘密

_wp_get_object_terms 函数才是真正执行数据库查询的函数。 咱们也来扒一扒它的源码。

function _wp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
    global $wpdb;

    $defaults = array(
        'orderby'  => 'name',
        'order'    => 'ASC',
        'fields'   => 'all',
        'name'     => '',
        'slug'     => '',
        'count'    => false,
        'id_fields' => false,
        'get'      => '',
        'number'   => '',
        'offset'   => '',
        'search'   => '',
        'hierarchical' => true,
        'child_of' => 0,
        'parent' => '',
        'pad_counts' => false,
        'hide_empty' => false,
    );

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

    $orderby = strtolower( $args['orderby'] );
    $order   = strtoupper( $args['order'] );

    if ( ! in_array( $orderby, array( 'name', 'slug', 'term_group', 'term_id', 'id', 'description', 'count' ), true ) ) {
        $orderby = 'name';
    }

    if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
        $order = 'ASC';
    }

    $fields = $args['fields'];

    if ( ! in_array( $fields, array( 'all', 'ids', 'names', 'slugs', 'tt_ids' ), true ) ) {
        $fields = 'all';
    }

    $id_fields = (bool) $args['id_fields'];

    $object_ids = array_map( 'intval', (array) $object_ids );
    $object_ids = array_unique( $object_ids );

    $taxonomies = array_map( 'sanitize_key', (array) $taxonomies );
    $taxonomies = array_intersect( $taxonomies, get_taxonomies() );

    if ( empty( $taxonomies ) ) {
        return array();
    }

    $select_fields = '';
    switch ( $fields ) {
        case 'all':
            $select_fields = "t.*, tt.*";
            break;
        case 'ids':
            $select_fields = 't.term_id';
            break;
        case 'names':
            $select_fields = 't.name';
            break;
        case 'slugs':
            $select_fields = 't.slug';
            break;
        case 'tt_ids':
            $select_fields = 'tt.term_taxonomy_id';
            break;
    }

    $join = "INNER JOIN {$wpdb->term_relationships} AS tr ON (t.term_id = tr.term_taxonomy_id)";
    $join .= "INNER JOIN {$wpdb->term_taxonomy} AS tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)";

    $where = "WHERE tt.taxonomy IN ('" . implode( "', '", $taxonomies ) . "')";
    $where .= "AND tr.object_id IN (" . implode( ',', $object_ids ) . ")";

    if ( ! empty( $args['name'] ) ) {
        $names = array_map( 'trim', explode( ',', $args['name'] ) );
        $names = array_map( array( $wpdb, 'esc_like' ), $names );
        $name_like = "AND t.name LIKE '%" . implode( "%' AND t.name LIKE '%", $names ) . "%'";
        $where .= $name_like;
    }

    if ( ! empty( $args['slug'] ) ) {
        $slugs = array_map( 'trim', explode( ',', $args['slug'] ) );
        $slugs = array_map( array( $wpdb, 'esc_like' ), $slugs );
        $slug_like = "AND t.slug LIKE '%" . implode( "%' AND t.slug LIKE '%", $slugs ) . "%'";
        $where .= $slug_like;
    }

    if ( ! empty( $args['search'] ) ) {
        $search = $wpdb->esc_like( $args['search'] );
        $where .= "AND (t.name LIKE '%{$search}%' OR t.slug LIKE '%{$search}%')";
    }

    if ( ! empty( $args['parent'] ) ) {
        $parent = (int) $args['parent'];
        $where .= "AND tt.parent = {$parent}";
    }

    if ( is_numeric( $args['child_of'] ) && $args['child_of'] > 0 ) {
        $child_of = (int) $args['child_of'];
        $where .= "AND tt.parent = {$child_of}";
    }

    $orderby_clause = '';
    switch ( $orderby ) {
        case 'name':
            $orderby_clause = 'ORDER BY t.name';
            break;
        case 'slug':
            $orderby_clause = 'ORDER BY t.slug';
            break;
        case 'term_group':
            $orderby_clause = 'ORDER BY t.term_group';
            break;
        case 'term_id':
        case 'id':
            $orderby_clause = 'ORDER BY t.term_id';
            break;
        case 'description':
            $orderby_clause = 'ORDER BY tt.description';
            break;
        case 'count':
            $orderby_clause = 'ORDER BY tt.count';
            break;
    }

    $orderby_clause .= ' ' . $order;

    if ( is_numeric( $args['number'] ) && $args['number'] > 0 ) {
        $number = (int) $args['number'];
        if ( is_numeric( $args['offset'] ) && $args['offset'] > 0 ) {
            $offset = (int) $args['offset'];
            $limit = "LIMIT {$offset}, {$number}";
        } else {
            $limit = "LIMIT {$number}";
        }
    } else {
        $limit = '';
    }

    $query = "SELECT {$select_fields} FROM {$wpdb->terms} AS t {$join} {$where} {$orderby_clause} {$limit}";

    $terms = $wpdb->get_results( $query );

    if ( ! empty( $terms ) && $args['update_term_meta_cache'] ) {
        _prime_term_caches( $terms, false );
    }

    if ( ! empty( $terms ) && 'all' === $fields ) {
        $terms = array_map( 'get_term', $terms );
    }

    return $terms;
}

代码解读:

  1. 参数校验和处理:
    • wp_get_object_terms 函数类似,首先进行参数校验和处理。
    • 使用 array_intersect 函数确保 $taxonomies 数组中的元素都是有效的分类法名称。
  2. 构建 SQL 查询语句:
    • 根据 $fields 参数的值,选择要查询的字段。
    • 构建 JOIN 子句,连接 wp_termswp_term_relationshipswp_term_taxonomy 表。
    • 构建 WHERE 子句,根据 $taxonomies$object_ids 参数的值,过滤查询结果。
    • 根据 $args 数组中的其他参数,添加额外的过滤条件。
    • 构建 ORDER BY 子句,指定排序方式。
    • 构建 LIMIT 子句,限制返回结果的数量。
  3. 执行 SQL 查询:
    • 使用 $wpdb->get_results 函数执行 SQL 查询,获取查询结果。
  4. 更新分类术语元数据缓存:
    • 如果 $args['update_term_meta_cache']true,则调用 _prime_term_caches 函数更新分类术语元数据缓存。 这个函数可以提高分类术语元数据的访问速度。
  5. 转换查询结果:
    • 如果 $fields 是 ‘all’,则使用 array_mapget_term 函数把查询结果转换成 WP_Term 对象。
  6. 返回结果:
    • 返回查询结果。

Part 4: 数据库表之间的关系

wp_get_object_terms() 函数的底层查询涉及三个关键的数据库表:

表名 作用 关键字段
wp_terms 存储分类术语的信息,例如名称、别名等。 term_id (术语 ID), name (术语名称), slug (术语别名)
wp_term_taxonomy 存储分类术语的分类法信息,例如分类、标签等。 term_taxonomy_id (术语分类 ID), term_id (术语 ID), taxonomy (分类法名称), parent (父级术语分类 ID), count (术语下的对象数量)
wp_term_relationships 存储对象和分类术语之间的关系,例如文章和分类之间的关系。 object_id (对象 ID), term_taxonomy_id (术语分类 ID), term_order (术语顺序)

这三个表之间的关系可以用下图来表示:

wp_terms (1)  <-----> (1) wp_term_taxonomy (N) <-----> (N) wp_term_relationships (N)
  • wp_terms 表和 wp_term_taxonomy 表之间是一对多的关系。 一个分类术语可以属于多个分类法。
  • wp_term_taxonomy 表和 wp_term_relationships 表之间是一对多的关系。 一个分类法可以关联多个对象。

wp_get_object_terms() 函数通过 JOIN 语句把这三个表连接起来,从而获取与指定对象相关联的分类术语。

Part 5: 缓存机制

wp_get_object_terms() 函数使用了 WordPress 的对象缓存机制来提高性能。 具体来说,它使用了 wp_cache_getwp_cache_set 函数来从缓存中获取和设置数据。

缓存键的构建非常重要,它需要包含所有的参数信息,才能确保不同的参数组合对应不同的缓存。 wp_get_object_terms() 函数使用 md5 函数和 var_export/serialize 函数来构建缓存键,确保缓存键的唯一性。

通过使用缓存机制,wp_get_object_terms() 函数可以避免频繁地访问数据库,从而提高性能。

Part 6: 总结

wp_get_object_terms() 函数是一个非常重要的函数,它可以帮助我们获取与指定对象相关联的分类术语。 通过深入源码,我们了解了它的实现原理,包括参数校验、参数合并、构建 SQL 查询语句、执行 SQL 查询、更新分类术语元数据缓存、转换查询结果、返回结果等等。 我们也了解了它使用了 WordPress 的对象缓存机制来提高性能。

掌握 wp_get_object_terms() 函数的原理,可以帮助我们更好地理解 WordPress 的内部机制,从而更好地开发 WordPress 主题和插件。 希望今天的分享对大家有所帮助! 谢谢大家!

发表回复

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