解释 `wp_get_sites()` 函数的源码,它在多站点模式下是如何查询所有子站点的?

各位观众,晚上好!我是你们今晚的WordPress多站点探险向导,代号“Bug猎手”。今天咱们不聊风花雪月,就来扒一扒WordPress多站点模式下的一个核心函数:wp_get_sites()

这个函数,就像是多站点网络的“户籍管理员”,负责把所有“居民”(也就是子站点)的信息给你拎出来。你想知道你的网络里都有哪些站点?它们的ID、域名、路径都是啥?就得靠它。

那么,这个“户籍管理员”是怎么工作的呢?让我们深入源码,一探究竟。

一、wp_get_sites() 的身世背景

首先,我们要明确一点:wp_get_sites() 函数只在多站点模式下有效。如果你运行的是单站点WordPress,那它基本上就是个摆设,会直接返回 false

它的基本用法很简单:

<?php
$sites = wp_get_sites();

if ($sites) {
    foreach ($sites as $site) {
        echo "Site ID: " . $site['blog_id'] . "<br>";
        echo "Domain: " . $site['domain'] . "<br>";
        echo "Path: " . $site['path'] . "<br>";
        echo "<hr>";
    }
} else {
    echo "No sites found or not in multisite mode.";
}
?>

这段代码会遍历所有子站点,并输出它们的ID、域名和路径。但是,wp_get_sites() 背后的运作机制远不止这么简单。

二、源码剖析:一步步追踪“户籍管理员”的足迹

让我们深入 wp-includes/ms-functions.php 文件,找到 wp_get_sites() 函数的真身。

function wp_get_sites( $args = array() ) {
    global $wpdb;

    if ( ! is_multisite() ) {
        return false;
    }

    $defaults = array(
        'limit'        => '',
        'offset'       => '',
        'network_id'   => get_current_site()->id,
        'public'       => null,
        'archived'     => null,
        'spam'         => null,
        'deleted'      => null,
        'mature'       => null,
        'fields'       => 'all',
        'search'       => '',
        'search_columns' => array( 'domain', 'path' ),
        'orderby'      => 'blog_id',
        'order'        => 'ASC',
        'number'       => null,
        'update_site_cache' => true
    );

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

    /**
     * Filters the arguments passed to wp_get_sites().
     *
     * @since 4.6.0
     *
     * @param array $args An array of wp_get_sites() arguments.
     */
    $args = apply_filters( 'wp_get_sites_args', $args );

    $site_cache = array();

    $select = '';
    switch ( $args['fields'] ) {
        case 'ids':
            $select = 'blog_id';
            break;
        case 'names':
            $select = 'domain, path';
            break;
        case 'all_with_path':
            $select = 'blog_id, domain, path';
            break;
        default:
            $select = '*';
            break;
    }

    $limit = '';
    if ( ! empty( $args['number'] ) ) {
        if ( $args['number'] < 0 ) {
            $limit = '';
        } else {
            $number = absint( $args['number'] );
            if ( ! empty( $args['offset'] ) ) {
                $offset = absint( $args['offset'] );
                $limit = "LIMIT {$offset}, {$number}";
            } else {
                $limit = "LIMIT {$number}";
            }
        }
    } elseif ( ! empty( $args['limit'] ) ) {
        if ( $args['limit'] < 0 ) {
            $limit = '';
        } else {
            $limit = 'LIMIT ' . absint( $args['limit'] );
            if ( ! empty( $args['offset'] ) ) {
                $limit = 'LIMIT ' . absint( $args['offset'] ) . ', ' . absint( $args['limit'] );
            }
        }
    }

    $where = "WHERE 1=1";

    if ( ! empty( $args['network_id'] ) ) {
        $network_id = absint( $args['network_id'] );
        $where .= " AND site_id = {$network_id}";
    }

    if ( null !== $args['public'] ) {
        $public = absint( $args['public'] );
        $where .= " AND public = {$public}";
    }

    if ( null !== $args['archived'] ) {
        $archived = absint( $args['archived'] );
        $where .= " AND archived = {$archived}";
    }

    if ( null !== $args['spam'] ) {
        $spam = absint( $args['spam'] );
        $where .= " AND spam = {$spam}";
    }

    if ( null !== $args['deleted'] ) {
        $deleted = absint( $args['deleted'] );
        $where .= " AND deleted = {$deleted}";
    }

    if ( null !== $args['mature'] ) {
        $mature = absint( $args['mature'] );
        $where .= " AND mature = {$mature}";
    }

    $search = '';
    if ( ! empty( $args['search'] ) ) {
        $search_term = esc_sql( $wpdb->esc_like( $args['search'] ) );
        $search_columns = array_intersect( $args['search_columns'], array( 'domain', 'path' ) );
        if ( ! empty( $search_columns ) ) {
            $search = ' AND (';
            $search_strings = array();
            foreach ( $search_columns as $column ) {
                $search_strings[] = "{$column} LIKE '%{$search_term}%'";
            }
            $search .= implode( ' OR ', $search_strings ) . ')';
        }
    }

    $orderby = esc_sql( $args['orderby'] );
    $order   = esc_sql( $args['order'] );

    $order_clause = "ORDER BY {$orderby} {$order}";

    $table = $wpdb->blogs;
    $sql = "SELECT {$select} FROM {$table} {$where} {$search} {$order_clause} {$limit}";

    $cache_key = 'sites:' . md5( $sql );

    $sites = wp_cache_get( $cache_key, 'site-transient' );

    if ( false === $sites ) {
        if ( 'ids' === $args['fields'] ) {
            $sites = $wpdb->get_col( $sql );

            if ( ! is_array( $sites ) ) {
                return array();
            }

            $sites = array_map( 'intval', $sites );
        } elseif ( 'names' === $args['fields'] ) {
            $sites = $wpdb->get_results( $sql, ARRAY_A );

            if ( ! is_array( $sites ) ) {
                return array();
            }
        } elseif ( 'all_with_path' === $args['fields'] ) {
            $sites = $wpdb->get_results( $sql, ARRAY_A );

            if ( ! is_array( $sites ) ) {
                return array();
            }
        } else {
            $sites = $wpdb->get_results( $sql, ARRAY_A );

            if ( ! is_array( $sites ) ) {
                return array();
            }

            foreach ( $sites as $site ) {
                $site_cache[ $site['blog_id'] ] = $site;
            }
        }

        wp_cache_set( $cache_key, $sites, 'site-transient', DAY_IN_SECONDS );
    }

    if ( 'all' === $args['fields'] && $args['update_site_cache'] ) {
        // Populate the cache.
        if ( ! empty( $site_cache ) ) {
            foreach ( $site_cache as $key => $value ) {
                wp_cache_set( $key, $value, 'site' );
            }
        } else {
            foreach ( $sites as $site ) {
                wp_cache_set( $site['blog_id'], $site, 'site' );
            }
        }
    }

    /**
     * Filters the array of sites returned by wp_get_sites().
     *
     * @since 3.7.0
     *
     * @param array $sites Array of WP_Site objects.
     * @param array $args  Array of wp_get_sites() arguments.
     */
    return apply_filters( 'wp_get_sites', $sites, $args );
}

代码有点长,别怕,我们把它拆解成几个关键步骤:

  1. 检查是否为多站点模式:

    if ( ! is_multisite() ) {
        return false;
    }

    这是最基础的一步。如果不是多站点,直接返回 false,没啥好说的。

  2. 设置默认参数:

    $defaults = array(
        'limit'        => '',
        'offset'       => '',
        'network_id'   => get_current_site()->id,
        'public'       => null,
        'archived'     => null,
        'spam'         => null,
        'deleted'      => null,
        'mature'       => null,
        'fields'       => 'all',
        'search'       => '',
        'search_columns' => array( 'domain', 'path' ),
        'orderby'      => 'blog_id',
        'order'        => 'ASC',
        'number'       => null,
        'update_site_cache' => true
    );
    
    $args = wp_parse_args( $args, $defaults );

    这里定义了一堆默认参数,允许你自定义查询行为。比如,你可以限制返回站点的数量 (limit),设置偏移量 (offset),指定网络ID (network_id),过滤公开站点 (public)、已存档站点 (archived)、垃圾站点 (spam)、已删除站点 (deleted)、成人站点 (mature),以及指定返回哪些字段 (fields)等等。

    wp_parse_args() 函数会将你传入的参数与默认参数合并,确保所有需要的参数都有值。

  3. 构建 SQL 查询语句:

    这是整个函数的灵魂所在。它根据你传入的参数,动态构建 SQL 查询语句,从数据库中检索站点信息。

    • 选择字段 ($select):

      $select = '';
      switch ( $args['fields'] ) {
          case 'ids':
              $select = 'blog_id';
              break;
          case 'names':
              $select = 'domain, path';
              break;
          case 'all_with_path':
              $select = 'blog_id, domain, path';
              break;
          default:
              $select = '*';
              break;
      }

      根据 fields 参数的值,选择要查询的字段。你可以只获取站点ID (blog_id),或者只获取域名和路径 (domain, path),或者同时获取ID、域名和路径 (blog_id, domain, path),或者获取所有字段 (*)。

    • 限制数量 ($limit):

      $limit = '';
      if ( ! empty( $args['number'] ) ) {
          // ...
      } elseif ( ! empty( $args['limit'] ) ) {
          // ...
      }

      根据 numberlimit 参数的值,设置 LIMIT 子句,限制返回站点的数量。

    • 构建 WHERE 子句 ($where):

      $where = "WHERE 1=1";
      
      if ( ! empty( $args['network_id'] ) ) {
          $network_id = absint( $args['network_id'] );
          $where .= " AND site_id = {$network_id}";
      }
      
      if ( null !== $args['public'] ) {
          $public = absint( $args['public'] );
          $where .= " AND public = {$public}";
      }
      
      // ... 其他过滤条件

      这是最复杂的部分。它根据 network_idpublicarchivedspamdeletedmature 等参数的值,构建 WHERE 子句,过滤出符合条件的站点。

    • 构建搜索子句 ($search):

      $search = '';
      if ( ! empty( $args['search'] ) ) {
          $search_term = esc_sql( $wpdb->esc_like( $args['search'] ) );
          $search_columns = array_intersect( $args['search_columns'], array( 'domain', 'path' ) );
          if ( ! empty( $search_columns ) ) {
              $search = ' AND (';
              $search_strings = array();
              foreach ( $search_columns as $column ) {
                  $search_strings[] = "{$column} LIKE '%{$search_term}%'";
              }
              $search .= implode( ' OR ', $search_strings ) . ')';
          }
      }

      根据 search 参数的值,构建 AND 子句,在指定的列 (search_columns) 中搜索包含指定关键词的站点。 注意这里使用了 esc_sql()$wpdb->esc_like() 进行转义,防止 SQL 注入。

    • 构建排序子句 ($order_clause):

      $orderby = esc_sql( $args['orderby'] );
      $order   = esc_sql( $args['order'] );
      
      $order_clause = "ORDER BY {$orderby} {$order}";

      根据 orderbyorder 参数的值,设置 ORDER BY 子句,指定排序方式。

    • 组装完整的 SQL 语句:

      $table = $wpdb->blogs;
      $sql = "SELECT {$select} FROM {$table} {$where} {$search} {$order_clause} {$limit}";

      将所有子句组合起来,形成完整的 SQL 查询语句。 $wpdb->blogs 通常对应的是 wp_blogs 表,存储着站点的基本信息。

  4. 查询数据库并缓存结果:

    $cache_key = 'sites:' . md5( $sql );
    
    $sites = wp_cache_get( $cache_key, 'site-transient' );
    
    if ( false === $sites ) {
        // ... 查询数据库
        wp_cache_set( $cache_key, $sites, 'site-transient', DAY_IN_SECONDS );
    }

    首先,根据 SQL 语句生成一个缓存键 ($cache_key)。然后,尝试从缓存中获取结果。如果缓存中没有结果,就执行 SQL 查询,并将结果存入缓存,有效期为一天。

    这里使用了 WordPress 的对象缓存机制 (wp_cache_get()wp_cache_set()),可以有效提高查询效率。

  5. 处理查询结果:

    if ( 'ids' === $args['fields'] ) {
        $sites = $wpdb->get_col( $sql );
    
        if ( ! is_array( $sites ) ) {
            return array();
        }
    
        $sites = array_map( 'intval', $sites );
    } elseif ( 'names' === $args['fields'] ) {
        $sites = $wpdb->get_results( $sql, ARRAY_A );
    
        if ( ! is_array( $sites ) ) {
            return array();
        }
    } elseif ( 'all_with_path' === $args['fields'] ) {
        $sites = $wpdb->get_results( $sql, ARRAY_A );
    
        if ( ! is_array( $sites ) ) {
            return array();
        }
    } else {
        $sites = $wpdb->get_results( $sql, ARRAY_A );
    
        if ( ! is_array( $sites ) ) {
            return array();
        }
    
        foreach ( $sites as $site ) {
            $site_cache[ $site['blog_id'] ] = $site;
        }
    }

    根据 fields 参数的值,对查询结果进行处理。如果只需要站点ID,就使用 $wpdb->get_col() 获取ID列表;如果只需要域名和路径,或者需要ID、域名和路径,就使用 $wpdb->get_results( $sql, ARRAY_A ) 获取关联数组;如果需要所有字段,就使用 $wpdb->get_results( $sql, ARRAY_A ) 获取关联数组,并将结果存入 $site_cache 数组,方便后续缓存。

  6. 更新站点缓存:

    if ( 'all' === $args['fields'] && $args['update_site_cache'] ) {
        // Populate the cache.
        if ( ! empty( $site_cache ) ) {
            foreach ( $site_cache as $key => $value ) {
                wp_cache_set( $key, $value, 'site' );
            }
        } else {
            foreach ( $sites as $site ) {
                wp_cache_set( $site['blog_id'], $site, 'site' );
            }
        }
    }

    如果 fields 参数的值为 all 并且 update_site_cache 参数的值为 true,就将查询结果存入站点缓存 (wp_cache_set( $key, $value, 'site' )),方便其他函数使用。

  7. 应用过滤器并返回结果:

    return apply_filters( 'wp_get_sites', $sites, $args );

    最后,应用 wp_get_sites 过滤器,允许其他插件或主题修改查询结果,然后返回最终结果。

三、wp_get_sites() 的参数详解

为了更灵活地使用 wp_get_sites() 函数,我们需要了解它的所有参数。

参数名 类型 默认值 描述
limit int '' 限制返回站点的数量。如果为空,则返回所有站点。
offset int '' 设置偏移量,从第几个站点开始返回。
network_id int get_current_site()->id 指定网络ID。如果为空,则使用当前网络的ID。
public bool null 过滤公开站点。如果为 true,则只返回公开站点;如果为 false,则只返回非公开站点;如果为 null,则不进行过滤。
archived bool null 过滤已存档站点。如果为 true,则只返回已存档站点;如果为 false,则只返回未存档站点;如果为 null,则不进行过滤。
spam bool null 过滤垃圾站点。如果为 true,则只返回垃圾站点;如果为 false,则只返回非垃圾站点;如果为 null,则不进行过滤。
deleted bool null 过滤已删除站点。如果为 true,则只返回已删除站点;如果为 false,则只返回未删除站点;如果为 null,则不进行过滤。
mature bool null 过滤成人站点。如果为 true,则只返回成人站点;如果为 false,则只返回非成人站点;如果为 null,则不进行过滤。
fields string 'all' 指定返回哪些字段。可选值包括:'ids' (只返回站点ID)、'names' (只返回域名和路径)、'all_with_path' (返回ID、域名和路径)、'all' (返回所有字段)。
search string '' 在指定的列中搜索包含指定关键词的站点。
search_columns array array( 'domain', 'path' ) 指定搜索的列。可选值包括:'domain' (域名)、'path' (路径)。
orderby string 'blog_id' 指定排序的字段。可选值包括:'blog_id' (站点ID)、'domain' (域名)、'path' (路径)、'registered' (注册时间)、'last_updated' (上次更新时间)。
order string 'ASC' 指定排序方式。可选值包括:'ASC' (升序)、'DESC' (降序)。
number int null limit 相同,用于限制返回站点的数量。
update_site_cache bool true 是否更新站点缓存。如果为 true,则将查询结果存入站点缓存,方便其他函数使用。

四、使用示例:各种姿势查询子站点

有了这些知识,我们就可以灵活地使用 wp_get_sites() 函数了。

  • 获取所有站点的ID:

    $site_ids = wp_get_sites( array( 'fields' => 'ids' ) );
    
    if ($site_ids) {
        foreach ($site_ids as $site_id) {
            echo "Site ID: " . $site_id . "<br>";
        }
    }
  • 获取前5个公开站点:

    $public_sites = wp_get_sites( array( 'public' => true, 'limit' => 5 ) );
    
    if ($public_sites) {
        foreach ($public_sites as $site) {
            echo "Site ID: " . $site['blog_id'] . "<br>";
            echo "Domain: " . $site['domain'] . "<br>";
            echo "Path: " . $site['path'] . "<br>";
            echo "<hr>";
        }
    }
  • 搜索域名或路径包含 "blog" 的站点:

    $search_sites = wp_get_sites( array( 'search' => 'blog' ) );
    
    if ($search_sites) {
        foreach ($search_sites as $site) {
            echo "Site ID: " . $site['blog_id'] . "<br>";
            echo "Domain: " . $site['domain'] . "<br>";
            echo "Path: " . $site['path'] . "<br>";
            echo "<hr>";
        }
    }
  • 获取所有站点的域名和路径,并按域名升序排序:

    $site_names = wp_get_sites( array( 'fields' => 'names', 'orderby' => 'domain', 'order' => 'ASC' ) );
    
    if ($site_names) {
       foreach ($site_names as $site) {
           echo "Domain: " . $site['domain'] . "<br>";
           echo "Path: " . $site['path'] . "<br>";
           echo "<hr>";
       }
    }

五、总结与建议

wp_get_sites() 函数是 WordPress 多站点模式下管理子站点的利器。通过理解它的源码和参数,你可以灵活地查询和过滤站点信息,满足各种需求。

在使用 wp_get_sites() 函数时,请注意以下几点:

  • 性能优化: 尽量使用缓存,避免频繁查询数据库。合理设置 limitoffset 参数,避免返回过多的数据。
  • 安全性: 对用户输入进行转义,防止 SQL 注入。
  • 代码可读性: 使用有意义的参数名,并添加适当的注释,方便他人理解你的代码。
  • 善用过滤器: wp_get_sites 过滤器允许你自定义查询结果,实现更高级的功能。

好了,今天的“WordPress多站点探险之旅”就到此结束。希望通过这次旅程,你对 wp_get_sites() 函数有了更深入的了解。下次再见,祝大家 Bug Free!

发表回复

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