各位观众老爷,大家好!我是你们的老朋友,BUG终结者。今天咱们来聊聊WordPress多站点模式下,一个神秘而强大的类:WP_Site_Query
。它就像一个神通广大的侦探,专门负责在你的站点宇宙中寻找各种子站点。
准备好了吗? 咱们这就开始一场源码探险之旅!
1. 什么是 WP_Site_Query
? 为什么要学习它?
在单站点WordPress中,我们主要和文章、页面打交道。但在多站点模式下,情况就复杂多了。我们需要管理多个独立的站点,每个站点都有自己的内容、用户和设置。WP_Site_Query
就应运而生,它提供了一种结构化的方式来查询和获取这些子站点的信息。
为什么要学习它?原因很简单:
- 定制化需求: WordPress自带的站点管理界面可能无法满足你所有的需求。例如,你可能需要根据特定条件筛选站点,或者以不同的方式展示站点列表。
- 性能优化: 如果你需要频繁地查询站点信息,直接使用SQL语句可能会导致性能问题。
WP_Site_Query
提供了一些缓存机制,可以提高查询效率。 - 理解底层机制: 深入了解
WP_Site_Query
可以帮助你更好地理解 WordPress 多站点的工作原理,从而更好地进行开发和维护。
2. WP_Site_Query
的构造函数:侦探的初始化
咱们先从 WP_Site_Query
的构造函数开始。这就像侦探拿到案件卷宗,开始了解基本情况。
/**
* WP_Site_Query class.
*
* @since 4.6.0
*/
class WP_Site_Query {
/**
* SQL query string.
*
* @since 4.6.0
* @access public
* @var string
*/
public $request;
/**
* List of found site IDs.
*
* @since 4.6.0
* @access public
* @var array
*/
public $sites;
/**
* The total number of found sites.
*
* @since 4.6.0
* @access public
* @var int
*/
public $found_sites = 0;
/**
* The number of found sites for the current query.
*
* @since 4.6.0
* @access public
* @var int
*/
public $site_count = 0;
/**
* Query vars set by the user.
*
* @since 4.6.0
* @access public
* @var array
*/
public $query_vars = array();
/**
* Default values for query vars.
*
* @since 4.6.0
* @access public
* @var array
*/
public $default_query = array(
'orderby' => 'id',
'order' => 'ASC',
'number' => 100,
'offset' => '',
'fields' => 'all',
'search' => '',
'network_id' => 0,
'ID' => 0,
'site__in' => array(),
'site__not_in' => array(),
'public' => null,
'archived' => null,
'mature' => null,
'spam' => null,
'deleted' => null,
'lang_id' => 0,
'domain' => '',
'path' => '',
'domain__like' => '',
'path__like' => '',
'search_columns' => array( 'domain', 'path' ),
'update_site_cache' => true,
'cache_domain' => 'core',
);
/**
* Constructor.
*
* @since 4.6.0
* @access public
*
* @param string|array $query Optional. Array or query string of site query parameters.
*/
public function __construct( $query = '' ) {
$this->query_vars = wp_parse_args( $query );
$this->query_vars = array_merge( $this->default_query, $this->query_vars );
$this->prepare_query();
$this->query();
}
}
这个构造函数接收一个可选的 $query
参数,它可以是一个数组或者一个查询字符串,用于指定查询的条件。
wp_parse_args()
: 这个函数会将传入的$query
参数解析成一个数组,并将其与默认的查询参数合并。 这样,即使你只传入了部分参数,也能保证所有的查询参数都有值。prepare_query()
: 这个函数会根据查询参数,构建SQL查询语句。query()
: 这个函数会执行SQL查询,并将结果保存到$sites
属性中。
可以看到,构造函数的主要任务是:
- 解析查询参数。
- 准备SQL查询语句。
- 执行查询。
3. 查询参数:侦探的工具箱
WP_Site_Query
提供了丰富的查询参数,可以让你像侦探一样,根据各种线索来寻找目标站点。
参数 | 描述 | 默认值 |
---|---|---|
orderby |
排序的字段,可以是 id 、domain 、path 等。 |
id |
order |
排序的方式,可以是 ASC (升序)或 DESC (降序)。 |
ASC |
number |
返回站点的数量。 | 100 |
offset |
偏移量,用于分页。 | '' |
fields |
返回的字段,可以是 all (返回所有字段)或 ids (只返回站点ID)。 |
all |
search |
搜索关键词,会在 domain 和 path 字段中搜索。 |
'' |
network_id |
网络ID,用于指定查询哪个网络下的站点。 | 0 |
ID |
站点ID,用于精确匹配某个站点。 | 0 |
site__in |
一个站点ID数组,用于查询指定ID的站点。 | array() |
site__not_in |
一个站点ID数组,用于排除指定ID的站点。 | array() |
public |
是否公开,可以是 1 (公开)或 0 (不公开)。 |
null |
archived |
是否已存档,可以是 1 (已存档)或 0 (未存档)。 |
null |
mature |
是否包含成人内容,可以是 1 (包含)或 0 (不包含)。 |
null |
spam |
是否被标记为垃圾站点,可以是 1 (是)或 0 (否)。 |
null |
deleted |
是否已删除,可以是 1 (已删除)或 0 (未删除)。 |
null |
lang_id |
语言ID,用于指定查询哪个语言的站点。 | 0 |
domain |
域名,用于精确匹配某个站点。 | '' |
path |
路径,用于精确匹配某个站点。 | '' |
domain__like |
域名,支持模糊匹配。 | '' |
path__like |
路径,支持模糊匹配。 | '' |
search_columns |
搜索的字段,可以是 domain 或 path 。 |
array('domain', 'path') |
update_site_cache |
是否更新站点缓存。 | true |
cache_domain |
缓存的域名。 | core |
这些参数就像侦探工具箱里的各种工具,可以根据不同的案件需求选择合适的工具。
4. prepare_query()
:构建SQL查询语句
prepare_query()
函数是 WP_Site_Query
的核心之一,它负责根据查询参数,构建最终的SQL查询语句。
/**
* Prepares the site query variables.
*
* @since 4.6.0
* @access public
*
* @return null
*/
public function prepare_query() {
global $wpdb;
$qv = $this->query_vars;
$orderby = $this->parse_orderby( $qv['orderby'] );
$qv['order'] = strtoupper( $qv['order'] );
if ( 'DESC' !== $qv['order'] ) {
$qv['order'] = 'ASC';
}
$number = absint( $qv['number'] );
$offset = absint( $qv['offset'] );
$this->query_vars = $qv;
$site_fields = $this->get_fields_for_db( $qv['fields'] );
$where = $this->get_sql_clauses( $qv );
$join = $where['join'];
$where = $where['where'];
$limits = '';
if ( ! empty( $number ) ) {
if ( $offset ) {
$limits = 'LIMIT ' . $offset . ',' . $number;
} else {
$limits = 'LIMIT ' . $number;
}
}
$this->request = "SELECT $site_fields FROM {$wpdb->sitemeta} AS sm INNER JOIN {$wpdb->sites} AS s ON sm.site_id = s.id $join WHERE 1=1 $where ORDER BY {$orderby} {$qv['order']} $limits";
$this->query_vars['number'] = $number;
$this->query_vars['offset'] = $offset;
}
这个函数的主要步骤如下:
- 解析排序参数: 使用
parse_orderby()
函数解析orderby
参数,将其转换为SQL语句中使用的排序字段。 - 处理排序方式: 将
order
参数转换为大写,并确保其值为ASC
或DESC
。 - 处理数量和偏移量: 将
number
和offset
参数转换为整数。 - 获取数据库字段: 使用
get_fields_for_db()
函数根据fields
参数,获取需要查询的数据库字段。 - 构建WHERE子句: 使用
get_sql_clauses()
函数根据查询参数,构建SQL语句中的WHERE
子句。这是最复杂的部分,后面会详细介绍。 - 构建LIMIT子句: 根据
number
和offset
参数,构建SQL语句中的LIMIT
子句,用于分页。 - 构建完整的SQL语句: 将所有的SQL片段拼接在一起,形成最终的SQL查询语句,并将其保存到
$this->request
属性中。
5. get_sql_clauses()
:构建 WHERE 子句的核心
get_sql_clauses()
函数是构建 WHERE 子句的核心,它会根据查询参数,生成相应的 SQL 条件。
/**
* Generates SQL clauses to be appended to the main query.
*
* @since 4.6.0
* @access protected
*
* @param array $qv An array of query variables.
* @return array An array containing the JOIN and WHERE clauses.
*/
protected function get_sql_clauses( $qv = array() ) {
global $wpdb;
$where = array();
$join = '';
// Network ID.
if ( ! empty( $qv['network_id'] ) ) {
$where['network_id'] = $wpdb->prepare( 's.network_id = %d', $qv['network_id'] );
}
// ID.
if ( ! empty( $qv['ID'] ) ) {
$where['ID'] = $wpdb->prepare( 's.id = %d', $qv['ID'] );
}
// Site IDs.
if ( ! empty( $qv['site__in'] ) ) {
$site__in = wp_parse_id_list( $qv['site__in'] );
$where['site__in'] = 's.id IN ( ' . implode( ',', array_map( 'intval', $site__in ) ) . ' )';
} elseif ( ! empty( $qv['site__not_in'] ) ) {
$site__not_in = wp_parse_id_list( $qv['site__not_in'] );
$where['site__not_in'] = 's.id NOT IN ( ' . implode( ',', array_map( 'intval', $site__not_in ) ) . ' )';
}
// Public, Archived, Mature, Spam, Deleted.
foreach ( array( 'public', 'archived', 'mature', 'spam', 'deleted' ) as $key ) {
if ( is_numeric( $qv[ $key ] ) ) {
$where[ $key ] = $wpdb->prepare( "s.{$key} = %d", $qv[ $key ] );
}
}
// Lang ID.
if ( ! empty( $qv['lang_id'] ) ) {
$where['lang_id'] = $wpdb->prepare( 'sm.meta_value = %d AND sm.meta_key = %s', $qv['lang_id'], 'lang_id' );
$join .= " INNER JOIN {$wpdb->sitemeta} AS sm ON s.id = sm.site_id";
}
// Domain & Path.
if ( ! empty( $qv['domain'] ) ) {
$where['domain'] = $wpdb->prepare( 's.domain = %s', $qv['domain'] );
}
if ( ! empty( $qv['path'] ) ) {
$where['path'] = $wpdb->prepare( 's.path = %s', $qv['path'] );
}
// Domain & Path LIKE.
if ( ! empty( $qv['domain__like'] ) ) {
$where['domain__like'] = $wpdb->prepare( 's.domain LIKE %s', '%' . $wpdb->esc_like( $qv['domain__like'] ) . '%' );
}
if ( ! empty( $qv['path__like'] ) ) {
$where['path__like'] = $wpdb->prepare( 's.path LIKE %s', '%' . $wpdb->esc_like( $qv['path__like'] ) . '%' );
}
// Search.
if ( ! empty( $qv['search'] ) ) {
$searches = array();
foreach ( (array) $qv['search_columns'] as $column ) {
if ( 'domain' === $column ) {
$searches[] = $wpdb->prepare( 's.domain LIKE %s', '%' . $wpdb->esc_like( $qv['search'] ) . '%' );
} elseif ( 'path' === $column ) {
$searches[] = $wpdb->prepare( 's.path LIKE %s', '%' . $wpdb->esc_like( $qv['search'] ) . '%' );
}
}
if ( ! empty( $searches ) ) {
$where['search'] = '(' . implode( ' OR ', $searches ) . ')';
}
}
/**
* Filters the WHERE clause of the sites query.
*
* @since 4.6.0
*
* @param array $where An associative array of WHERE clauses.
* @param WP_Site_Query $this The WP_Site_Query instance.
*/
$where = apply_filters( 'sites_pre_query', $where, $this );
return array(
'join' => implode( ' ', array_unique( array_filter( $join ) ) ),
'where' => implode( ' AND ', array_filter( $where ) ),
);
}
这个函数会遍历所有的查询参数,并根据不同的参数类型,生成相应的SQL条件。
network_id
和ID
: 使用$wpdb->prepare()
函数,生成精确匹配的SQL条件。site__in
和site__not_in
: 使用IN
和NOT IN
运算符,生成包含或排除指定ID的SQL条件。public
、archived
、mature
、spam
和deleted
: 使用$wpdb->prepare()
函数,生成精确匹配的SQL条件。lang_id
: 需要特别注意,它查询的是wp_sitemeta
表,所以需要添加一个JOIN
子句,将wp_sites
表和wp_sitemeta
表连接起来。domain
和path
: 使用$wpdb->prepare()
函数,生成精确匹配的SQL条件。domain__like
和path__like
: 使用LIKE
运算符,生成模糊匹配的SQL条件。search
: 根据search_columns
参数,在domain
和path
字段中进行模糊搜索。
最后,这个函数会将所有的SQL条件拼接在一起,形成最终的 WHERE 子句,并将其返回。
6. query()
:执行查询并获取结果
query()
函数负责执行SQL查询,并将结果保存到 $sites
属性中。
/**
* Executes the query, with the current variables.
*
* @since 4.6.0
* @access public
*
* @return array|int List of sites, or number of sites when 'count' is passed as a query var.
*/
public function query() {
global $wpdb;
/**
* Fires before the site query.
*
* @since 4.6.0
*
* @param WP_Site_Query $this The WP_Site_Query instance.
*/
do_action( 'pre_get_sites', $this );
$this->sites = array();
$this->site_count = 0;
// Count results if limiting is not in effect.
if ( ! empty( $this->query_vars['number'] ) ) {
$found_sites_query = "SELECT FOUND_ROWS()";
} else {
$found_sites_query = $this->request;
}
if ( 'ids' === $this->query_vars['fields'] ) {
$this->sites = $wpdb->get_col( $this->request );
} else {
$this->sites = $wpdb->get_results( $this->request );
}
if ( ! empty( $this->query_vars['number'] ) ) {
$this->found_sites = (int) $wpdb->get_var( $found_sites_query );
}
$this->site_count = count( $this->sites );
if ( $this->site_count && $this->query_vars['update_site_cache'] ) {
wp_cache_add_multiple( $this->sites, 'sites', $this->query_vars['cache_domain'] );
}
/**
* Fires after the site query.
*
* @since 4.6.0
*
* @param array $sites The array of queried sites.
* @param WP_Site_Query $this The WP_Site_Query instance.
*/
do_action( 'sites_pre_query', $this->sites, $this );
return $this->sites;
}
这个函数的主要步骤如下:
- 执行SQL查询: 使用
$wpdb->get_col()
或$wpdb->get_results()
函数,执行SQL查询,并将结果保存到$this->sites
属性中。 - 获取总数: 如果指定了
number
参数,则使用FOUND_ROWS()
函数获取总的站点数量,并将其保存到$this->found_sites
属性中。 - 更新缓存: 如果
update_site_cache
参数为true
,则将查询结果添加到缓存中,以提高后续查询的效率。
7. 实际应用:侦探的实战案例
现在,我们来模拟几个实际的应用场景,看看如何使用 WP_Site_Query
。
案例1:查找所有未存档的站点
$args = array(
'archived' => 0,
);
$site_query = new WP_Site_Query( $args );
$sites = $site_query->get_sites();
foreach ( $sites as $site ) {
echo 'Site ID: ' . $site->id . '<br>';
echo 'Site Domain: ' . $site->domain . '<br>';
echo 'Site Path: ' . $site->path . '<br>';
echo '<hr>';
}
案例2:查找域名包含 "example" 的站点
$args = array(
'domain__like' => 'example',
);
$site_query = new WP_Site_Query( $args );
$sites = $site_query->get_sites();
foreach ( $sites as $site ) {
echo 'Site ID: ' . $site->id . '<br>';
echo 'Site Domain: ' . $site->domain . '<br>';
echo 'Site Path: ' . $site->path . '<br>';
echo '<hr>';
}
案例3:分页显示站点列表
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$per_page = 10;
$args = array(
'number' => $per_page,
'offset' => ( $paged - 1 ) * $per_page,
);
$site_query = new WP_Site_Query( $args );
$sites = $site_query->get_sites();
$total_sites = $site_query->found_sites;
echo '<p>Total Sites: ' . $total_sites . '</p>';
foreach ( $sites as $site ) {
echo 'Site ID: ' . $site->id . '<br>';
echo 'Site Domain: ' . $site->domain . '<br>';
echo 'Site Path: ' . $site->path . '<br>';
echo '<hr>';
}
// 分页链接
$total_pages = ceil( $total_sites / $per_page );
$paginate_links = paginate_links( array(
'base' => get_pagenum_link( 1 ) . '%_%',
'format' => '/page/%#%',
'current' => $paged,
'total' => $total_pages,
) );
echo '<div class="pagination">' . $paginate_links . '</div>';
8. 总结:成为站点侦探
通过今天的学习,我们深入了解了 WP_Site_Query
的源码,掌握了它的基本原理和使用方法。
WP_Site_Query
是一个强大的类,用于在 WordPress 多站点模式下查询子站点。- 它提供了丰富的查询参数,可以让你根据各种条件来筛选站点。
prepare_query()
函数负责构建SQL查询语句。get_sql_clauses()
函数是构建 WHERE 子句的核心。query()
函数负责执行SQL查询,并将结果保存到$sites
属性中。
掌握了 WP_Site_Query
,你就可以像一位经验丰富的站点侦探,轻松地在你的站点宇宙中找到任何你需要的站点信息。
希望今天的讲解对你有所帮助。 如果你还有什么问题,欢迎随时提问。 我们下次再见!