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

各位观众老爷们,晚上好!我是你们的老朋友,代码界的段子手。今天咱们要聊聊WordPress里一个看似简单,实则内藏乾坤的函数——get_users()

get_users()这玩意儿,相信大家或多或少都用过,它就是用来获取用户列表的。但你有没有想过,它背后到底是怎么运作的?别急,今天咱们就扒开它的外衣,看看它的真实面目!

第一幕:get_users() 闪亮登场

首先,咱们来看看 get_users() 这家伙的真身。以下是 WordPress 源码中 get_users() 函数的定义:

function get_users( $args = array() ) {
    $query = new WP_User_Query( $args );

    return $query->get_results();
}

简单粗暴有没有? 就两行代码! 核心就是实例化了 WP_User_Query 类,然后调用了 get_results() 方法。

看到这里,有没有觉得有点懵?别慌,这就是 WordPress 源码的风格,把复杂的逻辑都藏在类里面了。

第二幕:幕后英雄 WP_User_Query

WP_User_Query 类,才是真正的幕后英雄。 它的职责就是根据你给的参数,构建 SQL 查询语句,然后从数据库里捞出符合条件的用户数据。

我们深入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,
        'include'         => array(),
        'exclude'         => array(),
        'search'          => '',
        'search_columns'  => array(),
        'orderby'         => 'login',
        'order'           => 'ASC',
        'offset'          => '',
        'number'          => '',
        'paged'           => 1,
        'count_total'     => true,
        'fields'          => 'all',
        'who'             => '',
        'has_published_posts' => null,
    );

    $this->query_vars = wp_parse_args( $query, $this->query_vars_defaults );
    $this->prepare_query();
    $this->query();
}

这个构造函数做了几件事:

  1. 定义默认参数: $this->query_vars_defaults 定义了查询用户的各种默认参数,比如默认排序方式是 login,排序顺序是 ASC (升序) 等等。
  2. 解析参数: wp_parse_args() 函数会将你传入的参数 $query 和默认参数合并,最终赋值给 $this->query_vars。 如果你传入了参数,会覆盖默认参数。
  3. 准备查询: $this->prepare_query() 这个函数负责根据 $this->query_vars 里的参数,构建 SQL 查询语句。
  4. 执行查询: $this->query() 函数执行 SQL 查询,从数据库里获取用户数据。

第三幕:prepare_query() 磨刀霍霍

prepare_query() 函数是构建 SQL 查询语句的关键。 让我们来看看它的核心逻辑。

protected function prepare_query() {
    global $wpdb;

    $qv = &$this->query_vars;
    $this->prepare_user_search( $qv );

    $this->sql_fields = 'SQL_CALC_FOUND_ROWS wp_users.*';

    if ( 'count' === $qv['fields'] ) {
        $this->sql_fields = 'COUNT(*)';
    }

    $this->sql_from = "FROM {$wpdb->users} AS wp_users";
    $this->sql_where = 'WHERE 1=1';
    $this->sql_orderby = '';
    $this->sql_groupby = '';
    $this->sql_limit = '';

    if ( is_array( $qv['include'] ) && ! empty( $qv['include'] ) ) {
        $ids = implode( ',', array_map( 'intval', $qv['include'] ) );
        $this->sql_where .= " AND wp_users.ID IN ($ids)";
    } elseif ( is_array( $qv['exclude'] ) && ! empty( $qv['exclude'] ) ) {
        $ids = implode( ',', array_map( 'intval', $qv['exclude'] ) );
        $this->sql_where .= " AND wp_users.ID NOT IN ($ids)";
    }

    if ( '' !== $qv['who'] ) {
        if ( 'authors' === $qv['who'] ) {
            $this->sql_from .= " INNER JOIN {$wpdb->posts} ON (wp_users.ID = {$wpdb->posts}.post_author)";
            $this->sql_where .= " AND {$wpdb->posts}.post_status = 'publish'";
            $this->sql_where .= " AND {$wpdb->posts}.post_type = 'post'";
        }
    }

    if ( ! empty( $qv['blog_id'] ) ) {
        $this->sql_from .= " INNER JOIN {$wpdb->usermeta} ON (wp_users.ID = {$wpdb->usermeta}.user_id)";
        $this->sql_where .= $wpdb->prepare( " AND {$wpdb->usermeta}.meta_key = %s", $wpdb->get_blog_prefix( $qv['blog_id'] ) . 'capabilities' );
    }

    if ( ! empty( $qv['role'] ) ) {
        $this->sql_from .= " INNER JOIN {$wpdb->usermeta} AS mt1 ON (wp_users.ID = mt1.user_id)";
        $this->sql_where .= ' AND mt1.meta_key = '' . $wpdb->prefix . 'capabilities'';
        $this->sql_where .= ' AND mt1.meta_value LIKE '%' . esc_sql( like_escape( $qv['role'] ) ) . '%'';
    } elseif ( ! empty( $qv['role__in'] ) ) {
        // ... 处理 role__in
    } elseif ( ! empty( $qv['role__not_in'] ) ) {
        // ... 处理 role__not_in
    }

    if ( ! empty( $qv['meta_key'] ) || ! empty( $qv['meta_value'] ) || ! empty( $qv['meta_query'] ) ) {
        $this->get_meta_sql( $qv['meta_query'], $qv['meta_key'], $qv['meta_value'], $qv['meta_compare'] );
    }

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

    $orderby_array = $this->parse_orderby( $qv['orderby'] );
    $orderby = '';

    if ( $orderby_array ) {
        $orderby = implode( ', ', $orderby_array );
    }

    if ( $orderby ) {
        $this->sql_orderby = "ORDER BY $orderby";
        $this->sql_orderby .= ' ' . $qv['order'];
    }

    if ( is_numeric( $qv['number'] ) ) {
        if ( is_numeric( $qv['offset'] ) ) {
            $this->sql_limit = $wpdb->prepare( 'LIMIT %d, %d', $qv['offset'], $qv['number'] );
        } else {
            $this->sql_limit = $wpdb->prepare( 'LIMIT %d, %d', ( $qv['paged'] - 1 ) * $qv['number'], $qv['number'] );
        }
    }
}

代码有点长,但别怕,咱们来慢慢分析:

  1. 初始化 SQL 语句片段: $this->sql_fields (要查询的字段), $this->sql_from (FROM 子句), $this->sql_where (WHERE 子句), $this->sql_orderby (ORDER BY 子句), $this->sql_groupby (GROUP BY 子句), $this->sql_limit (LIMIT 子句) 都被初始化为空字符串。
  2. 处理 includeexclude 参数: 如果你指定了 include (包含哪些用户 ID) 或 exclude (排除哪些用户 ID) 参数,这里会构建相应的 WHERE 子句。
  3. 处理 who 参数: who 参数可以用来筛选 "authors" (作者)。 如果指定了 who = 'authors', 就会 INNER JOIN wp_posts 表,并添加额外的 WHERE 子句,限制只查询发布过文章的作者。
  4. 处理 blog_id 参数: 如果你在使用多站点 WordPress,并且指定了 blog_id,这里会 INNER JOIN wp_usermeta 表,并添加 WHERE 子句,限制只查询特定站点的用户。
  5. 处理 rolerole__inrole__not_in 参数: 这些参数用于根据用户角色筛选用户。同样会 INNER JOIN wp_usermeta 表,并添加 WHERE 子句,筛选出符合特定角色的用户。 role__in 允许你指定多个角色, role__not_in 允许你排除多个角色。
  6. 处理 meta_keymeta_valuemeta_query 参数: 这些参数用于根据用户元数据 (usermeta) 筛选用户。 meta_keymeta_value 可以让你根据单个元数据键值对进行筛选。 meta_query 允许你构建更复杂的元数据查询条件。 get_meta_sql() 方法会负责生成相应的 SQL 片段。
  7. 处理 date_query 参数: date_query 参数允许你根据用户的注册日期筛选用户。 它会使用 WP_Date_Query 类来构建日期查询的 SQL 片段。
  8. 处理 orderbyorder 参数: 这些参数用于指定排序方式和排序顺序。 parse_orderby() 函数会将 orderby 参数解析成一个 SQL 字段列表。
  9. 处理 numberoffsetpaged 参数: 这些参数用于分页查询用户。 number 指定每页显示的用户数量, offset 指定查询的起始位置, paged 指定当前页码。

第四幕:query() 执行 SQL,获取数据

prepare_query() 函数只是磨刀,真正干活的是 query() 函数。 它的职责就是执行 prepare_query() 构建好的 SQL 查询语句,然后将查询结果保存到 $this->results 属性中。

public function query() {
    global $wpdb;

    $this->prepare_query();

    $this->request = "SELECT {$this->sql_fields} {$this->sql_from} {$this->sql_where} {$this->sql_orderby} {$this->sql_limit}";

    if ( is_numeric( $this->query_vars['number'] ) && $this->query_vars['count_total'] ) {
        $this->total_users = (int) $wpdb->get_var( "SELECT FOUND_ROWS()" );
    }

    if ( 'count' === $this->query_vars['fields'] ) {
        $this->results = (int) $wpdb->get_var( $this->request );
        return;
    }

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

    if ( 'all' === $this->query_vars['fields'] ) {
        foreach ( $this->results as $key => $user ) {
            $this->results[ $key ] = new WP_User( $user );
        }
    }
}

这个函数主要做了以下几件事:

  1. 再次调用 prepare_query() 这是为了确保 SQL 查询语句是最新的。 虽然构造函数里已经调用过 prepare_query() 了,但这里再调用一次可以防止在 query() 函数调用之前,有其他代码修改了 $this->query_vars 属性。
  2. 构建完整的 SQL 查询语句:$this->sql_fields$this->sql_from$this->sql_where$this->sql_orderby$this->sql_limit 拼接成完整的 SQL 查询语句,并赋值给 $this->request 属性。
  3. 获取总用户数量: 如果指定了 number 参数 (每页显示的用户数量) 并且 count_total 参数为 true (需要计算总用户数量), 就会执行 SELECT FOUND_ROWS() 查询,获取总用户数量,并保存到 $this->total_users 属性中。 SQL_CALC_FOUND_ROWSprepare_query() 中已经添加过了。
  4. 执行 SQL 查询: 使用 $wpdb->get_results() 函数执行 SQL 查询,并将查询结果保存到 $this->results 属性中。
  5. 将查询结果转换为 WP_User 对象: 如果 fields 参数为 'all' (需要获取所有用户数据), 就会遍历 $this->results 数组,将每个用户数据转换为 WP_User 对象。

第五幕:get_results() 返回结果

最后, get_users() 函数调用 WP_User_Query 类的 get_results() 方法,将查询结果返回。

public function get_results() {
    return $this->results;
}

get_results() 就只是简单地返回 $this->results 属性的值, 也就是 query() 函数查询到的用户数据。

总结:get_users() 的工作流程

现在,让我们来总结一下 get_users() 函数的工作流程:

  1. 调用 get_users() 函数,传入查询参数。
  2. get_users() 函数实例化 WP_User_Query 类,并将查询参数传递给 WP_User_Query 类的构造函数。
  3. WP_User_Query 类的构造函数会:
    • 定义默认查询参数。
    • 将传入的查询参数和默认参数合并。
    • 调用 prepare_query() 函数构建 SQL 查询语句。
    • 调用 query() 函数执行 SQL 查询,并将查询结果保存到 $this->results 属性中。
  4. get_users() 函数调用 WP_User_Query 类的 get_results() 方法,获取查询结果。
  5. get_results() 方法返回查询结果。

参数详解:get_users() 的十八般武艺

get_users() 函数支持很多参数,可以让你灵活地查询用户。 下面是一些常用的参数:

参数名 类型 描述
blog_id int 指定要查询的站点 ID (多站点 WordPress)。
role string 指定要查询的用户角色。
role__in array 指定要查询的多个用户角色。
role__not_in array 指定要排除的多个用户角色。
meta_key string 指定要查询的用户元数据的键名。
meta_value string 指定要查询的用户元数据的键值。
meta_compare string 指定元数据键值对的比较方式 (例如 ‘=’, ‘!=’, ‘>’, ‘<‘, ‘LIKE’, ‘NOT LIKE’, ‘IN’, ‘NOT IN’, ‘BETWEEN’, ‘NOT BETWEEN’)。
meta_query array 允许构建更复杂的元数据查询条件。
date_query array 允许根据用户的注册日期筛选用户。
include array 指定要包含的用户 ID 列表。
exclude array 指定要排除的用户 ID 列表。
search string 指定要搜索的关键词。
search_columns array 指定要在哪些字段中搜索关键词 (例如 ‘ID’, ‘user_login’, ‘user_email’, ‘user_url’, ‘user_nicename’, ‘display_name’)。
orderby string 指定排序方式 (例如 ‘ID’, ‘login’, ‘nicename’, ’email’, ‘url’, ‘registered’, ‘display_name’, ‘post_count’, ‘meta_value’, ‘meta_value_num’)。
order string 指定排序顺序 (‘ASC’ 或 ‘DESC’)。
offset int 指定查询的起始位置。
number int 指定每页显示的用户数量。
paged int 指定当前页码。
count_total bool 是否计算总用户数量。
fields string 指定要返回的字段 (‘all’ (默认), ‘ID’, ‘login’, ‘nicename’, ’email’, ‘url’, ‘registered’, ‘display_name’, ‘post_count’, ‘meta_value’, ‘meta_value_num’, ‘count’)。 如果是’count’,则返回用户总数。
who string 如果设置为 'authors',则只返回作者。
has_published_posts boolean 根据用户是否发表过文章来过滤用户。true 只返回发表过文章的用户,false 只返回没有发表过文章的用户。 null (默认) 则不进行过滤。

举个栗子:用 get_users() 查询用户

咱们来举几个例子,看看 get_users() 函数怎么用。

  • 获取所有管理员:
$args = array(
    'role' => 'administrator',
);

$administrators = get_users( $args );

foreach ( $administrators as $administrator ) {
    echo $administrator->user_login . '<br>';
}
  • 获取注册日期在 2023 年 1 月 1 日之后的用户:
$args = array(
    'date_query' => array(
        array(
            'after' => '2023-01-01',
            'column' => 'user_registered',
        ),
    ),
);

$new_users = get_users( $args );

foreach ( $new_users as $new_user ) {
    echo $new_user->user_login . '<br>';
}
  • 获取用户元数据 favorite_color'blue' 的用户:
$args = array(
    'meta_key'   => 'favorite_color',
    'meta_value' => 'blue',
);

$blue_lovers = get_users( $args );

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

总结与思考

get_users() 函数的源码分析就到这里了。 通过这次剖析,我们可以看到:

  • get_users() 函数只是一个简单的入口, 真正的逻辑都藏在 WP_User_Query 类里。
  • WP_User_Query 类通过构建 SQL 查询语句,从数据库里获取用户数据。
  • get_users() 函数支持很多参数,可以灵活地查询用户。

希望这次讲座能让你对 get_users() 函数有更深入的了解。 以后再用它的时候,就不会觉得它只是一个黑盒了。

最后,留个小作业: 尝试使用 meta_query 参数,构建一个更复杂的元数据查询条件,例如查询 favorite_color'blue' 或者 favorite_food'pizza' 的用户。 试试看,你会发现 get_users() 函数的强大之处!

各位,下课!

发表回复

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