分析 WordPress `WP_User_Query` 类源码:用户查询的 SQL 构建与 `get_results()` 方法。

各位观众老爷,大家好!今天咱们来聊聊WordPress里一个相当重要的类:WP_User_Query。这家伙负责在数据库里捞人,哦不,是捞用户的信息。它的核心任务就是把咱们的需求翻译成SQL语句,然后把结果打包好送回来。今天我们就扒一扒它的源码,看看它是怎么干活的,重点关注SQL语句的构建和get_results()方法。

一、WP_User_Query:你的专属用户搜索官

想象一下,你是个HR,需要从一大堆员工信息里找到符合特定条件的人,比如“所有部门是销售部的,入职时间在2022年之后的员工”。在WordPress里,WP_User_Query就是你的HR,它能帮你从wp_users表以及相关的wp_usermeta表里找到符合条件的用户。

二、构造函数:接收你的搜索指令

首先,我们来看看WP_User_Query的构造函数,它负责接收你的搜索条件:

public function __construct( $query = null ) {
    $this->query_vars_defaults = array(
        'blog_id'         => get_current_blog_id(),
        'fields'          => 'all',
        'include'         => array(),
        'exclude'         => array(),
        'search'          => '',
        'search_columns'  => array(),
        'orderby'         => 'login',
        'order'           => 'ASC',
        'offset'          => '',
        'number'          => '',
        'paged'           => '',
        'count_total'     => true,
        'who'             => '',
        'has_published_posts' => null,
        'nicename'        => '',
        'nicename__in'    => array(),
        'nicename__not_in' => array(),
        'login'           => '',
        'login__in'       => array(),
        'login__not_in'    => array(),
        'user_email'      => '',
        'user_email__in'  => array(),
        'user_email__not_in' => array(),
        'user_url'        => '',
        'user_url__in'    => array(),
        'user_url__not_in' => array(),
        'ID'              => '',
        'ID__in'          => array(),
        'ID__not_in'       => array(),
        'meta_key'        => '',
        'meta_value'      => '',
        'meta_compare'    => '',
        'meta_query'      => array(),
        'date_query'      => null,
        'capability'      => '',
        'capability__in'  => array(),
        'capability__not_in' => array(),
        'role'            => '',
        'role__in'        => array(),
        'role__not_in'     => array(),
        'lang'            => '',
        'feed'            => '',
    );

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

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

这个构造函数接收一个 $query 参数,它是一个数组,包含了你的搜索条件,比如'role' => 'editor'。 构造函数会把这些条件赋值给 $this->query_vars 属性。 这些就是筛选用户的依据,各种各样的参数,应有尽有,只有你想不到,没有它不支持。

三、query() 方法:解析你的指令

query() 方法负责解析你的搜索条件,并进行一些必要的处理。

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

    // Backward compatibility for meta_key => meta_value usage.
    if ( ! empty( $this->query_vars['meta_key'] ) && empty( $this->query_vars['meta_query'] ) ) {
        $this->query_vars['meta_query'] = array(
            array(
                'key'   => $this->query_vars['meta_key'],
                'value' => $this->query_vars['meta_value'],
                'compare' => $this->query_vars['meta_compare'],
            ),
        );
    }

    $this->prepare_query();

    $this->get_results();
}

这里有几个关键步骤:

  1. wp_parse_args(): 把你的搜索条件和默认值合并起来,确保所有必要的参数都有值。
  2. apply_filters( 'pre_user_query', $this->query_vars ): 允许你通过过滤器修改搜索条件,这为你提供了额外的灵活性。
  3. prepare_query(): 这是最关键的一步,它负责根据你的搜索条件构建SQL语句。我们稍后会详细介绍。
  4. get_results(): 执行SQL查询,并把结果返回给你。

四、prepare_query():SQL语句的魔法工厂

prepare_query() 方法是WP_User_Query的核心,它负责把你的搜索条件翻译成SQL语句。 让我们深入了解一下它的工作原理。它内部又调用了很多其他方法,负责不同部分的SQL语句构建。

protected function prepare_query() {
    global $wpdb;

    $qv = $this->query_vars;

    $this->query_fields = $this->get_fields();

    // Prepare orderby clause.
    $this->prepare_orderby();

    // Assemble the query.
    $fields = $this->query_fields;
    $join   = $this->get_sql_join();
    $where  = $this->get_sql_where();
    $orderby = ! empty( $this->orderby ) ? "ORDER BY {$this->orderby}" : '';
    $limits = $this->get_sql_limits();

    $this->request = "SELECT {$fields} FROM {$wpdb->users} {$join} WHERE 1=1 {$where} {$orderby} {$limits}";

    if ( $this->query_vars['count_total'] ) {
        $this->query_total = "SELECT COUNT(DISTINCT {$wpdb->users}.ID) FROM {$wpdb->users} {$join} WHERE 1=1 {$where}";
    }
}

这个方法做了以下几件事:

  1. $this->get_fields(): 确定要查询的字段,比如 ID, user_login, user_email
  2. $this->prepare_orderby(): 构建 ORDER BY 子句,指定排序方式。
  3. $this->get_sql_join(): 构建 JOIN 子句,用于连接 wp_userswp_usermeta 表(如果需要的话)。
  4. $this->get_sql_where(): 构建 WHERE 子句,根据你的搜索条件筛选用户。
  5. $this->get_sql_limits(): 构建 LIMIT 子句,用于分页。
  6. $this->request: 把所有部分组装成完整的SQL查询语句。
  7. $this->query_total: 如果需要统计总用户数,构建一个单独的SQL查询语句。

让我们更详细地看看get_sql_where()方法,因为它负责构建最重要的WHERE子句。

protected function get_sql_where() {
    global $wpdb;

    $where = '';

    $qv = $this->query_vars;

    // ID.
    if ( ! empty( $qv['ID'] ) ) {
        $where .= ' AND ' . $wpdb->users . '.ID = '' . esc_sql( $qv['ID'] ) . ''';
    } elseif ( ! empty( $qv['ID__in'] ) ) {
        $ids = array_map( 'intval', $qv['ID__in'] );
        $where .= ' AND ' . $wpdb->users . '.ID IN (' . implode( ',', $ids ) . ')';
    } elseif ( ! empty( $qv['ID__not_in'] ) ) {
        $ids = array_map( 'intval', $qv['ID__not_in'] );
        $where .= ' AND ' . $wpdb->users . '.ID NOT IN (' . implode( ',', $ids ) . ')';
    }

    // Login.
    if ( ! empty( $qv['login'] ) ) {
        $where .= ' AND ' . $wpdb->users . '.user_login = '' . esc_sql( $qv['login'] ) . ''';
    } elseif ( ! empty( $qv['login__in'] ) ) {
        $logins = array_map( 'esc_sql', $qv['login__in'] );
        $where .= ' AND ' . $wpdb->users . '.user_login IN ('' . implode( '', '', $logins ) . '')';
    } elseif ( ! empty( $qv['login__not_in'] ) ) {
        $logins = array_map( 'esc_sql', $qv['login__not_in'] );
        $where .= ' AND ' . $wpdb->users . '.user_login NOT IN ('' . implode( '', '', $logins ) . '')';
    }

    // User nicename.
    if ( ! empty( $qv['nicename'] ) ) {
        $where .= ' AND ' . $wpdb->users . '.user_nicename = '' . esc_sql( $qv['nicename'] ) . ''';
    } elseif ( ! empty( $qv['nicename__in'] ) ) {
        $nicenames = array_map( 'esc_sql', $qv['nicename__in'] );
        $where .= ' AND ' . $wpdb->users . '.user_nicename IN ('' . implode( '', '', $nicenames ) . '')';
    } elseif ( ! empty( $qv['nicename__not_in'] ) ) {
        $nicenames = array_map( 'esc_sql', $qv['nicename__not_in'] );
        $where .= ' AND ' . $wpdb->users . '.user_nicename NOT IN ('' . implode( '', '', $nicenames ) . '')';
    }

    // User email.
    if ( ! empty( $qv['user_email'] ) ) {
        $where .= ' AND ' . $wpdb->users . '.user_email = '' . esc_sql( $qv['user_email'] ) . ''';
    } elseif ( ! empty( $qv['user_email__in'] ) ) {
        $emails = array_map( 'esc_sql', $qv['user_email__in'] );
        $where .= ' AND ' . $wpdb->users . '.user_email IN ('' . implode( '', '', $emails ) . '')';
    } elseif ( ! empty( $qv['user_email__not_in'] ) ) {
        $emails = array_map( 'esc_sql', $qv['user_email__not_in'] );
        $where .= ' AND ' . $wpdb->users . '.user_email NOT IN ('' . implode( '', '', $emails ) . '')';
    }

    // User URL.
    if ( ! empty( $qv['user_url'] ) ) {
        $where .= ' AND ' . $wpdb->users . '.user_url = '' . esc_sql( $qv['user_url'] ) . ''';
    } elseif ( ! empty( $qv['user_url__in'] ) ) {
        $urls = array_map( 'esc_sql', $qv['user_url__in'] );
        $where .= ' AND ' . $wpdb->users . '.user_url IN ('' . implode( '', '', $urls ) . '')';
    } elseif ( ! empty( $qv['user_url__not_in'] ) ) {
        $urls = array_map( 'esc_sql', $qv['user_url__not_in'] );
        $where .= ' AND ' . $wpdb->users . '.user_url NOT IN ('' . implode( '', '', $urls ) . '')';
    }

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

    // Role stuff.
    if ( ! empty( $qv['role'] ) ) {
        $where .= $this->get_role_sql( $qv['role'] );
    } elseif ( ! empty( $qv['role__in'] ) ) {
        $where .= $this->get_role_sql( $qv['role__in'], 'IN' );
    } elseif ( ! empty( $qv['role__not_in'] ) ) {
        $where .= $this->get_role_sql( $qv['role__not_in'], 'NOT IN' );
    }

    // Meta query stuff.
    if ( ! empty( $qv['meta_query'] ) ) {
        $meta_query = new WP_Meta_Query( $qv['meta_query'] );
        $where .= $meta_query->get_sql( 'user', $wpdb->users, 'ID' );
    }

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

    // Include/exclude stuff.
    if ( ! empty( $qv['include'] ) ) {
        $ids = array_map( 'intval', $qv['include'] );
        $where .= ' AND ' . $wpdb->users . '.ID IN (' . implode( ',', $ids ) . ')';
    } elseif ( ! empty( $qv['exclude'] ) ) {
        $ids = array_map( 'intval', $qv['exclude'] );
        $where .= ' AND ' . $wpdb->users . '.ID NOT IN (' . implode( ',', $ids ) . ')';
    }

    return $where;
}

可以看到,这个方法会根据不同的搜索条件,拼接不同的SQL片段。 例如,如果你的搜索条件包含 role,它会调用 $this->get_role_sql() 方法来构建角色相关的SQL片段。 如果包含meta_query, 会调用WP_Meta_Query类来处理元数据相关的查询。

五、get_results():执行查询,返回结果

get_results() 方法负责执行SQL查询,并把结果返回给你。

public function get_results() {
    global $wpdb;

    if ( $this->results === null ) {
        $this->prepare_query();

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

        if ( $this->query_vars['count_total'] ) {
            $this->total_users = (int) $wpdb->get_var( $this->query_total );
        }
    }

    return $this->results;
}

这个方法做了以下几件事:

  1. $this->prepare_query(): 确保SQL查询语句已经构建好。
  2. $wpdb->get_results( $this->request ): 使用 $wpdb 对象执行SQL查询,并把结果保存在 $this->results 属性中。
  3. $wpdb->get_var( $this->query_total ): 如果需要统计总用户数,执行统计总数的SQL查询,并把结果保存在 $this->total_users 属性中。
  4. return $this->results: 返回查询结果。

六、代码示例:实际应用

让我们看几个实际应用的例子。

示例1:查找所有管理员用户

$args = array(
    'role' => 'administrator'
);

$user_query = new WP_User_Query( $args );

if ( ! empty( $user_query->results ) ) {
    foreach ( $user_query->results as $user ) {
        echo 'User ID: ' . $user->ID . ', User Login: ' . $user->user_login . '<br>';
    }
} else {
    echo 'No administrators found.';
}

这段代码会创建一个 WP_User_Query 对象,搜索条件是 role 等于 administrator。 然后,它会遍历查询结果,并输出每个管理员用户的ID和登录名。

示例2:查找所有注册时间在2023年1月1日之后的用户

$args = array(
    'date_query' => array(
        array(
            'after'     => '2023-01-01',
            'inclusive' => true,
        ),
    ),
);

$user_query = new WP_User_Query( $args );

if ( ! empty( $user_query->results ) ) {
    foreach ( $user_query->results as $user ) {
        echo 'User ID: ' . $user->ID . ', User Login: ' . $user->user_login . '<br>';
    }
} else {
    echo 'No users found registered after 2023-01-01.';
}

这段代码使用 date_query 参数来指定注册时间的范围。

示例3:查找元数据中 ‘city’ 字段值为 ‘New York’ 的用户

$args = array(
    'meta_query' => array(
        array(
            'key'     => 'city',
            'value'   => 'New York',
            'compare' => '=',
        ),
    ),
);

$user_query = new WP_User_Query( $args );

if ( ! empty( $user_query->results ) ) {
    foreach ( $user_query->results as $user ) {
        echo 'User ID: ' . $user->ID . ', User Login: ' . $user->user_login . '<br>';
    }
} else {
    echo 'No users found with city New York.';
}

这段代码使用 meta_query 参数来指定元数据字段和值。

七、总结:WP_User_Query 的强大之处

WP_User_Query 类是WordPress里一个非常强大的工具,它可以让你方便地从数据库里检索用户信息。 它的核心在于把你的搜索条件翻译成SQL语句,并执行查询。 通过理解它的源码,你可以更好地利用它来满足你的需求,并避免一些常见的性能问题。

一些建议:

  • 避免过度复杂的查询: 复杂的查询会导致性能下降。 尽量简化你的搜索条件。
  • 使用缓存: 如果你的查询结果不会经常变化,可以考虑使用缓存来提高性能。
  • 注意SQL注入: 始终使用 esc_sql() 函数来转义用户输入,防止SQL注入攻击。

表格总结:常用参数

参数名 描述 示例
role 用户的角色 'role' => 'editor'
include 只包含指定ID的用户 'include' => array(1, 2, 3)
exclude 排除指定ID的用户 'exclude' => array(4, 5, 6)
search 搜索用户的登录名、昵称、邮箱等 'search' => 'john'
meta_key 元数据键名 'meta_key' => 'city'
meta_value 元数据键值 'meta_value' => 'New York'
meta_query 更复杂的元数据查询,支持比较运算符 'meta_query' => array(array('key' => 'age', 'value' => 25, 'compare' => '>='))
date_query 日期查询,可以根据用户注册时间进行筛选 'date_query' => array(array('after' => '2023-01-01'))
orderby 排序字段 'orderby' => 'user_registered'
order 排序方式(ASC或DESC) 'order' => 'DESC'
number 每页显示的用户数 'number' => 10
offset 偏移量,用于分页 'offset' => 20
count_total 是否统计总用户数 'count_total' => true

希望今天的讲解对大家有所帮助。 下次再见!

发表回复

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