剖析 WordPress `get_users()` 函数的源码:如何通过 `WP_User_Query` 类来查询用户列表。

WordPress 用户查询的秘密:get_users()WP_User_Query 联袂演出

大家好,我是今天的讲师,人称“代码界的包打听”。今天我们要聊聊 WordPress 里一个看似简单,实则暗藏玄机的函数:get_users()。别看它名字朴实无华,背后可是站着一位实力派演员——WP_User_Query

我们要搞清楚,get_users() 并不是单打独斗,它只是 WP_User_Query 的一个便捷接口。就像你在餐厅点菜,服务员(get_users())帮你把菜单(参数)告诉厨房(WP_User_Query),然后把做好的菜(用户列表)端给你。

所以,要彻底理解用户查询,咱们必须深入 WP_User_Query 的源码,看看它是如何把各种筛选条件变成 SQL 查询语句,最终从数据库里捞出我们想要的用户。

get_users():友好的前端

先来简单回顾一下 get_users() 的用法。它接受一个数组作为参数,这个数组里可以包含各种筛选条件,比如角色、ID、用户名等等。

$args = array(
    'role'    => 'editor', // 只获取编辑
    'number'  => 10,      // 最多获取 10 个用户
    'orderby' => 'registered', // 按注册时间排序
    'order'   => 'ASC',     // 升序排列
);

$editors = get_users( $args );

foreach ( $editors as $editor ) {
    echo $editor->user_login . '<br>';
}

这段代码非常简单,它会获取 10 个注册时间最早的编辑,并输出他们的用户名。 那么,get_users() 内部到底发生了什么呢?

实际上,get_users() 做的主要就是两个事情:

  1. 实例化 WP_User_Query 对象: 它把我们传入的参数传递给 WP_User_Query 的构造函数。
  2. 返回查询结果: 调用 WP_User_Query 对象的 get_results() 方法,获取查询到的用户对象列表。

可以用下面的伪代码来表示:

function get_users( $args = array() ) {
    $user_query = new WP_User_Query( $args );
    return $user_query->get_results();
}

所以,真正的幕后英雄是 WP_User_Query。接下来,让我们深入剖析这个类。

WP_User_Query:幕后英雄的自我修养

WP_User_Query 类负责构建和执行用户查询。它的主要任务包括:

  1. 解析查询参数: 接收并处理各种查询参数,比如角色、ID、用户名等等。
  2. 构建 SQL 查询语句: 根据解析后的参数,动态构建 SQL 查询语句。
  3. 执行查询: 连接数据库,执行 SQL 查询语句。
  4. 返回查询结果: 将查询结果转换成用户对象列表。

让我们从 WP_User_Query 的构造函数开始,一步步揭开它的神秘面纱。

构造函数:接收参数,初始化变量

public function __construct( $query = null ) {
    $this->query_vars_defaults = array(
        'blog_id'         => get_current_blog_id(),
        'role'            => '',
        'role__in'        => array(),
        'role__not_in'    => array(),
        'meta_key'        => '',
        'meta_value'      => '',
        'meta_compare'    => '',
        'meta_query'      => array(),
        'date_query'      => null,
        'who'             => '',
        'search'          => '',
        'search_columns'  => array(),
        'include'         => array(),
        'exclude'         => array(),
        'orderby'         => 'login',
        'order'           => 'ASC',
        'offset'          => '',
        'number'          => '',
        'paged'           => 1,
        'count_total'     => true,
        'fields'          => 'all',
        'has_published_posts' => null,
        'nicename' => '',
        'nicename__in' => array(),
        'nicename__not_in' => array(),
    );

    $this->query_vars = $this->query_vars_defaults;

    if ( ! empty( $query ) ) {
        $this->query( $query );
    }
}

构造函数主要做了三件事:

  1. 定义默认查询参数: $this->query_vars_defaults 定义了所有可能的查询参数及其默认值。 这些参数涵盖了用户查询的方方面面,从角色到排序方式,从数量限制到分页等等。
  2. 初始化查询参数: $this->query_vars 是实际使用的查询参数,初始值等于默认参数。
  3. 调用 query() 方法: 如果传入了查询参数,就调用 query() 方法来解析这些参数。

query() 方法:参数解析的核心

query() 方法是 WP_User_Query 的核心方法之一。它负责解析传入的查询参数,并根据这些参数来构建 SQL 查询语句。

public function query( $query ) {
    global $wpdb;

    $this->query_vars = wp_parse_args( $query, $this->query_vars_defaults );
    $this->query_vars = apply_filters( 'pre_user_query', $this->query_vars );

    // ... 省略大量代码 ...

    $this->prepare_query();

    $this->results = $wpdb->get_results( $this->request );

    if ( $this->query_vars['count_total'] ) {
        $this->set_found_users();
    }

    return $this->results;
}

这个方法的主要步骤如下:

  1. 合并参数: wp_parse_args() 函数将传入的查询参数与默认参数合并,确保所有可能的参数都有值。
  2. 应用过滤器: apply_filters( 'pre_user_query', $this->query_vars ) 允许开发者通过过滤器来修改查询参数。这提供了一种灵活的方式来定制用户查询。
  3. 准备查询: $this->prepare_query() 方法根据解析后的参数,构建 SQL 查询语句。 这是整个过程中最复杂的部分,我们稍后会详细讲解。
  4. 执行查询: $wpdb->get_results( $this->request ) 使用 WordPress 的数据库对象 $wpdb 来执行 SQL 查询语句,并将结果保存在 $this->results 中。
  5. 计算总数: 如果 count_total 参数为 true,则调用 $this->set_found_users() 方法来计算符合条件的用户总数。
  6. 返回结果: 返回查询到的用户对象列表。

prepare_query() 方法:SQL 查询语句的构建大师

prepare_query() 方法是构建 SQL 查询语句的关键。它根据各种查询参数,动态生成 SQL 语句的不同部分,并将它们组合在一起。

protected function prepare_query() {
    global $wpdb;

    $qv =& $this->query_vars;

    $fields = $this->get_fields();

    // ... 省略大量代码 ...

    $where = $this->get_where();

    $orderby = $this->get_orderby();

    $limits = $this->get_limits();

    $this->request = "SELECT {$fields} FROM {$wpdb->users} {$join} WHERE 1=1 {$where} {$orderby} {$limits}";
    $this->query = $this->request; // For back compat.

    if ( $qv['count_total'] ) {
        $this->request_count = "SELECT COUNT(*) FROM {$wpdb->users} {$join} WHERE 1=1 {$where}";
    }
}

这个方法的主要步骤如下:

  1. 获取查询字段: $this->get_fields() 方法根据 fields 参数,确定要查询的字段。 默认情况下,查询所有字段。
  2. 构建 JOIN 子句: 根据 meta_queryhas_published_posts 参数,可能需要连接 usermeta 表和 posts 表。
  3. 构建 WHERE 子句: $this->get_where() 方法根据各种查询参数,构建 WHERE 子句。 这是最复杂的部分,我们稍后会详细讲解。
  4. 构建 ORDER BY 子句: $this->get_orderby() 方法根据 orderbyorder 参数,构建 ORDER BY 子句。
  5. 构建 LIMIT 子句: $this->get_limits() 方法根据 numberoffset 参数,构建 LIMIT 子句。
  6. 组合 SQL 语句: 将各个部分组合在一起,形成完整的 SQL 查询语句,并将其保存在 $this->request 属性中。
  7. 构建 COUNT 查询语句: 如果 count_total 参数为 true,则构建用于计算总数的 SQL 查询语句,并将其保存在 $this->request_count 属性中。

get_where() 方法:WHERE 子句的构建艺术

get_where() 方法负责构建 SQL 查询语句的 WHERE 子句。 它根据各种查询参数,动态生成不同的条件,并将它们组合在一起。

protected function get_where() {
    global $wpdb;

    $qv =& $this->query_vars;
    $where = '';

    if ( ! empty( $qv['who'] ) ) {
        switch ( $qv['who'] ) {
            case 'authors':
                $where .= ' AND ID IN ( SELECT post_author FROM ' . $wpdb->posts . ' WHERE post_status = "publish" )';
                break;
        }
    }

    if ( ! empty( $qv['include'] ) ) {
        $ids = implode( ',', array_map( 'intval', $qv['include'] ) );
        $where .= " AND ID IN ($ids)";
    } elseif ( ! empty( $qv['exclude'] ) ) {
        $ids = implode( ',', array_map( 'intval', $qv['exclude'] ) );
        $where .= " AND ID NOT IN ($ids)";
    }

    if ( ! empty( $qv['search'] ) ) {
        $where .= $this->get_search_sql( $qv['search'], $qv['search_columns'] );
    }

    if ( '' !== $qv['nicename'] ) {
        $where .= " AND user_nicename = '" . esc_sql( $qv['nicename'] ) . "'";
    }

    if ( ! empty( $qv['nicename__in'] ) ) {
        $nicenames = implode( "', '", array_map( 'esc_sql', $qv['nicename__in'] ) );
        $where .= " AND user_nicename IN ( '" . $nicenames . "' )";
    }

    if ( ! empty( $qv['nicename__not_in'] ) ) {
        $nicenames = implode( "', '", array_map( 'esc_sql', $qv['nicename__not_in'] ) );
        $where .= " AND user_nicename NOT IN ( '" . $nicenames . "' )";
    }

    if ( ! empty( $qv['role'] ) ) {
        if ( is_array( $qv['role'] ) ) {
            $roles = implode( "', '", array_map( 'esc_sql', $qv['role'] ) );
            $where .= " AND {$wpdb->users}.ID IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$roles}"%' )";
        } else {
            $where .= " AND {$wpdb->users}.ID IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$qv['role']}"%' )";
        }
    }

    if ( ! empty( $qv['role__in'] ) ) {
        $roles = implode( "', '", array_map( 'esc_sql', $qv['role__in'] ) );
        $where .= " AND {$wpdb->users}.ID IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$roles}"%' )";
    }

    if ( ! empty( $qv['role__not_in'] ) ) {
        $roles = implode( "', '", array_map( 'esc_sql', $qv['role__not_in'] ) );
        $where .= " AND {$wpdb->users}.ID NOT IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$roles}"%' )";
    }

    if ( ! empty( $qv['meta_query'] ) ) {
        $meta_query = new WP_Meta_Query( $qv['meta_query'] );
        $where .= $meta_query->get_sql( 'user', $wpdb->users, 'ID' );
    } elseif ( ! empty( $qv['meta_key'] ) || ! empty( $qv['meta_value'] ) ) {
        $where .= $this->get_meta_sql( $qv['meta_key'], $qv['meta_value'], $qv['meta_compare'] );
    }

    if ( ! empty( $qv['date_query'] ) ) {
        $date_query = new WP_Date_Query( $qv['date_query'], 'user_registered' );
        $where .= $date_query->get_sql();
    }

    return $where;
}

这个方法会根据不同的查询参数,添加不同的 WHERE 条件。 例如:

  • who 参数: 用于筛选作者。
  • includeexclude 参数: 用于包含或排除特定的用户 ID。
  • search 参数: 用于搜索用户名、昵称、邮箱等字段。
  • nicename 参数: 用于匹配用户昵称。
  • role 参数: 用于筛选具有特定角色的用户。 这个条件会连接 usermeta 表,并使用 LIKE 运算符来匹配用户角色。
  • meta_query 参数: 用于进行复杂的自定义字段查询。 它会使用 WP_Meta_Query 类来构建 meta_query 子句。
  • date_query 参数: 用于按注册日期进行筛选。 它会使用 WP_Date_Query 类来构建 date_query 子句。

实例分析:一个更复杂的例子

假设我们要查询所有订阅者(subscriber)并且注册时间在2023年1月1日之后的,并且自定义字段 "city" 的值为 "New York" 的用户。

$args = array(
    'role' => 'subscriber',
    'date_query' => array(
        array(
            'column' => 'user_registered',
            'after'  => '2023-01-01',
        ),
    ),
    'meta_query' => array(
        array(
            'key'     => 'city',
            'value'   => 'New York',
            'compare' => '=',
        ),
    ),
);

$users = get_users( $args );

在这个例子中,WP_User_Query 会构建如下的 SQL 查询语句(简化版):

SELECT *
FROM wp_users
INNER JOIN wp_usermeta ON (wp_users.ID = wp_usermeta.user_id)
WHERE 1=1
AND wp_users.ID IN (SELECT user_id FROM wp_usermeta WHERE meta_key = 'wp_capabilities' AND meta_value LIKE '%"subscriber"%')
AND user_registered >= '2023-01-01 00:00:00'
AND ((wp_usermeta.meta_key = 'city' AND wp_usermeta.meta_value = 'New York'))

这个查询语句会连接 wp_users 表和 wp_usermeta 表,并根据角色、注册时间和自定义字段的值进行筛选。

总结:get_users()WP_User_Query 的完美配合

get_users() 函数和 WP_User_Query 类是 WordPress 中用户查询的黄金搭档。 get_users() 提供了一个简单易用的接口,而 WP_User_Query 则负责处理复杂的查询逻辑。

通过深入了解 WP_User_Query 的源码,我们可以更好地理解 WordPress 如何查询用户数据,并能够更灵活地定制用户查询。

希望今天的讲座能够帮助大家更深入地理解 WordPress 的用户查询机制。 记住,理解源码是成为 WordPress 大神的必经之路!

表格总结:常用参数与对应的 SQL 条件

参数名称 描述 对应的 SQL 条件
role 指定用户角色,例如 ‘administrator’, ‘editor’, ‘author’, ‘subscriber’ 等。 AND {$wpdb->users}.ID IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$role}"%')
include 指定要包含的用户 ID 列表,例如 array(1, 2, 3) AND ID IN (1,2,3)
exclude 指定要排除的用户 ID 列表,例如 array(4, 5, 6) AND ID NOT IN (4,5,6)
search 指定要搜索的字符串,用于搜索用户名、昵称、邮箱等字段。 对应 get_search_sql() 函数生成的 SQL,例如 AND ( user_login LIKE '%search_term%' OR user_nicename LIKE '%search_term%' OR user_email LIKE '%search_term%' OR display_name LIKE '%search_term%' )
meta_query 用于进行复杂的自定义字段查询。 使用 WP_Meta_Query 类生成 SQL,例如 AND ( (wp_usermeta.meta_key = 'city' AND wp_usermeta.meta_value = 'New York' AND wp_usermeta.meta_compare = '=' ) )
date_query 用于按注册日期进行筛选。 使用 WP_Date_Query 类生成 SQL,例如 AND user_registered >= '2023-01-01 00:00:00'
orderby 指定排序字段,例如 ‘ID’, ‘login’, ‘nicename’, ’email’, ‘url’, ‘registered’, ‘display_name’, ‘post_count’。 ORDER BY user_login ASC (例如,如果 orderby 为 ‘login’ 且 order 为 ‘ASC’)
order 指定排序方式,’ASC’ (升序) 或 ‘DESC’ (降序)。 决定 ORDER BY 子句的排序方向。
number 指定要返回的用户数量。 LIMIT 10 (例如,如果 number 为 10)
offset 指定要跳过的用户数量,用于分页。 LIMIT 10, 10 (例如,如果 number 为 10 且 offset 为 10,则跳过前 10 个用户,并返回接下来的 10 个用户)
nicename 匹配用户昵称 AND user_nicename = 'john' (例如, 匹配昵称为 john 的用户)
nicename__in 匹配用户昵称 (允许数组) AND user_nicename IN ( 'john', 'jane' ) (例如, 匹配昵称为 john 或者 jane 的用户)
nicename__not_in 排除用户昵称 (允许数组) AND user_nicename NOT IN ( 'john', 'jane' ) (例如, 排除昵称为 john 或者 jane 的用户)
role__in 指定要包含的多个用户角色 AND {$wpdb->users}.ID IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$role1}"%' ) AND {$wpdb->users}.ID IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$role2}"%') (根据包含的角色数量生成多个IN语句)
role__not_in 指定要排除的多个用户角色 AND {$wpdb->users}.ID NOT IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$role1}"%' ) AND {$wpdb->users}.ID NOT IN ( SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = '{$wpdb->prefix}capabilities' AND meta_value LIKE '%"{$role2}"%') (根据排除的角色数量生成多个NOT IN语句)

发表回复

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