WordPress启动流程分析

1,019次阅读
没有评论

假设我们要访问https://xxxxxx.com/archives/110 这篇文章,并且已经设置好了伪静态了,伪静态规则都指向了入口index.php
Wordpress启动流程分析
那么我们开始从入口index.php分析

define( 'WP_USE_THEMES', true );

/** Loads the WordPress Environment and Template */
require __DIR__ . '/wp-blog-header.php';

这里非常简单,直接载入wp-blog-header.php

    $wp_did_header = true;
    // Load the WordPress library.
    require_once __DIR__ . '/wp-load.php';
    // Set up the WordPress query.
    wp();
    // Load the theme template.
    require_once ABSPATH . WPINC . '/template-loader.php';

看这写法,大概猜测是由里及外的执行,先载入核心,直接控制,再呈现模板。所以我们就分3部分分析。

1、wp-load.php


/*
 * If wp-config.php exists in the WordPress root, or if it exists in the root and wp-settings.php
 * doesn't, load wp-config.php. The secondary check for wp-settings.php has the added benefit
 * of avoiding cases where the current directory is a nested installation, e.g. / is WordPress(a)
 * and /blog/ is WordPress(b).
 *
 * If neither set of conditions is true, initiate loading the setup process.
 */
if ( file_exists( ABSPATH . 'wp-config.php' ) ) {

    /** The config file resides in ABSPATH */
    require_once ABSPATH . 'wp-config.php';

} elseif ( @file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {

    /** The config file resides one level above ABSPATH but is not part of another installation */
    require_once dirname( ABSPATH ) . '/wp-config.php';

} else {

    // A config file doesn't exist.

    define( 'WPINC', 'wp-includes' );
    require_once ABSPATH . WPINC . '/load.php';

    // Standardize $_SERVER variables across setups.
    wp_fix_server_vars();

    require_once ABSPATH . WPINC . '/functions.php';

    $path = wp_guess_url() . '/wp-admin/setup-config.php';

    /*
     * We're going to redirect to setup-config.php. While this shouldn't result
     * in an infinite loop, that's a silly thing to assume, don't you think? If
     * we're traveling in circles, our last-ditch effort is "Need more help?"
     */
    if ( false === strpos( $_SERVER['REQUEST_URI'], 'setup-config' ) ) {
        header( 'Location: ' . $path );
        exit;
    }

    define( 'WP_CONTENT_DIR', ABSPATH . 'wp-content' );
    require_once ABSPATH . WPINC . '/version.php';

    wp_check_php_mysql_versions();
    wp_load_translations_early();

    // Die with an error message.
    $die = '<p>' . sprintf(
        /* translators: %s: wp-config.php */
        __( "There doesn't seem to be a %s file. It is needed before the installation can continue." ),
        '<code>wp-config.php</code>'
    ) . '</p>';
    $die .= '<p>' . sprintf(
        /* translators: 1: Documentation URL, 2: wp-config.php */
        __( 'Need more help? <a href="%1$s">Read the support article on %2$s</a>.' ),
        __( 'https://wordpress.org/support/article/editing-wp-config-php/' ),
        '<code>wp-config.php</code>'
    ) . '</p>';
    $die .= '<p>' . sprintf(
        /* translators: %s: wp-config.php */
        __( "You can create a %s file through a web interface, but this doesn't work for all server setups. The safest way is to manually create the file." ),
        '<code>wp-config.php</code>'
    ) . '</p>';
    $die .= '<p><a href="' . $path . '" class="button button-large">' . __( 'Create a Configuration File' ) . '</a></p>';

    wp_die( $die, __( 'WordPress › Error' ) );
}

WordPress的代码注释写得很详细。可以看到,这个文件主要做安装判断的。安装了就载入配置wp-config.php配置,没安装就执行安装程序。看wp-config.php,定义数据库,key那些就不用分析了,重点在这里:

/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
    define( 'ABSPATH', __DIR__ . '/' );
}

/** Sets up WordPress vars and included files. */
require_once ABSPATH . 'wp-settings.php';

wp-settings.php这个文件是核心,因为它做了大量的工作,载入了各种核心程序。

wp-settings.php分析

先看第一部分代码。

require ABSPATH . WPINC . '/version.php';
require ABSPATH . WPINC . '/load.php';

// Check for the required PHP version and for the MySQL extension or a database drop-in.
wp_check_php_mysql_versions();

// Include files required for initialization.
require ABSPATH . WPINC . '/class-wp-paused-extensions-storage.php';
require ABSPATH . WPINC . '/class-wp-fatal-error-handler.php';
require ABSPATH . WPINC . '/class-wp-recovery-mode-cookie-service.php';
require ABSPATH . WPINC . '/class-wp-recovery-mode-key-service.php';
require ABSPATH . WPINC . '/class-wp-recovery-mode-link-service.php';
require ABSPATH . WPINC . '/class-wp-recovery-mode-email-service.php';
require ABSPATH . WPINC . '/class-wp-recovery-mode.php';
require ABSPATH . WPINC . '/error-protection.php';
require ABSPATH . WPINC . '/default-constants.php';
require_once ABSPATH . WPINC . '/plugin.php';

load.php定义了大量wp函数库用于支撑后面的加载运行。比如wp_check_php_mysql_versions就在load.php里面。
class-wp-paused-extensions-storage.php:用于存储暂停的插件或者主题。
class-wp-fatal-error-handler.php:wordpress的异常捕获类
class-wp-recovery-mode-cookie-service.php:cookies相关类
class-wp-recovery-*.php这些是用于恢复模式的
default-constants.php用于初始化常量定义
plugin.php载入插件Api相关类和函数,这里看一下plugin.php(只看部分)


// Initialize the filter globals.
require __DIR__ . '/class-wp-hook.php';

/** @var WP_Hook[] $wp_filter */
global $wp_filter;

/** @var int[] $wp_actions */
global $wp_actions;

/** @var int[] $wp_filters */
global $wp_filters;

/** @var string[] $wp_current_filter */
global $wp_current_filter;

if ( $wp_filter ) {
  $wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter );
} else {
  $wp_filter = array();
}

if ( ! isset( $wp_actions ) ) {
  $wp_actions = array();
}

if ( ! isset( $wp_filters ) ) {
  $wp_filters = array();
}

if ( ! isset( $wp_current_filter ) ) {
  $wp_current_filter = array();
}
function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
  global $wp_filter;

  if ( ! isset( $wp_filter[ $hook_name ] ) ) {
    $wp_filter[ $hook_name ] = new WP_Hook();
  }

  $wp_filter[ $hook_name ]->add_filter( $hook_name, $callback, $priority, $accepted_args );

  return true;
}

function apply_filters( $hook_name, $value, ...$args ) {
  global $wp_filter, $wp_filters, $wp_current_filter;

  if ( ! isset( $wp_filters[ $hook_name ] ) ) {
    $wp_filters[ $hook_name ] = 1;
  } else {
    ++$wp_filters[ $hook_name ];
  }

  // Do 'all' actions first.
  if ( isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;

    $all_args = func_get_args(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
    _wp_call_all_hook( $all_args );
  }

  if ( ! isset( $wp_filter[ $hook_name ] ) ) {
    if ( isset( $wp_filter['all'] ) ) {
      array_pop( $wp_current_filter );
    }

    return $value;
  }

  if ( ! isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;
  }

  // Pass the value to WP_Hook.
  array_unshift( $args, $value );

  $filtered = $wp_filter[ $hook_name ]->apply_filters( $value, $args );

  array_pop( $wp_current_filter );

  return $filtered;
}

function apply_filters_ref_array( $hook_name, $args ) {
  global $wp_filter, $wp_filters, $wp_current_filter;

  if ( ! isset( $wp_filters[ $hook_name ] ) ) {
    $wp_filters[ $hook_name ] = 1;
  } else {
    ++$wp_filters[ $hook_name ];
  }

  // Do 'all' actions first.
  if ( isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;
    $all_args            = func_get_args(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
    _wp_call_all_hook( $all_args );
  }

  if ( ! isset( $wp_filter[ $hook_name ] ) ) {
    if ( isset( $wp_filter['all'] ) ) {
      array_pop( $wp_current_filter );
    }

    return $args[0];
  }

  if ( ! isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;
  }

  $filtered = $wp_filter[ $hook_name ]->apply_filters( $args[0], $args );

  array_pop( $wp_current_filter );

  return $filtered;
}

function has_filter( $hook_name, $callback = false ) {
  global $wp_filter;

  if ( ! isset( $wp_filter[ $hook_name ] ) ) {
    return false;
  }

  return $wp_filter[ $hook_name ]->has_filter( $hook_name, $callback );
}

function remove_filter( $hook_name, $callback, $priority = 10 ) {
  global $wp_filter;

  $r = false;

  if ( isset( $wp_filter[ $hook_name ] ) ) {
    $r = $wp_filter[ $hook_name ]->remove_filter( $hook_name, $callback, $priority );

    if ( ! $wp_filter[ $hook_name ]->callbacks ) {
      unset( $wp_filter[ $hook_name ] );
    }
  }

  return $r;
}

function remove_all_filters( $hook_name, $priority = false ) {
  global $wp_filter;

  if ( isset( $wp_filter[ $hook_name ] ) ) {
    $wp_filter[ $hook_name ]->remove_all_filters( $priority );

    if ( ! $wp_filter[ $hook_name ]->has_filters() ) {
      unset( $wp_filter[ $hook_name ] );
    }
  }

  return true;
}

function current_filter() {
  global $wp_current_filter;

  return end( $wp_current_filter );
}

function doing_filter( $hook_name = null ) {
  global $wp_current_filter;

  if ( null === $hook_name ) {
    return ! empty( $wp_current_filter );
  }

  return in_array( $hook_name, $wp_current_filter, true );
}

function did_filter( $hook_name ) {
  global $wp_filters;

  if ( ! isset( $wp_filters[ $hook_name ] ) ) {
    return 0;
  }

  return $wp_filters[ $hook_name ];
}

function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
  return add_filter( $hook_name, $callback, $priority, $accepted_args );
}

function do_action( $hook_name, ...$arg ) {
  global $wp_filter, $wp_actions, $wp_current_filter;

  if ( ! isset( $wp_actions[ $hook_name ] ) ) {
    $wp_actions[ $hook_name ] = 1;
  } else {
    ++$wp_actions[ $hook_name ];
  }

  // Do 'all' actions first.
  if ( isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;
    $all_args            = func_get_args(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
    _wp_call_all_hook( $all_args );
  }

  if ( ! isset( $wp_filter[ $hook_name ] ) ) {
    if ( isset( $wp_filter['all'] ) ) {
      array_pop( $wp_current_filter );
    }

    return;
  }

  if ( ! isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;
  }

  if ( empty( $arg ) ) {
    $arg[] = '';
  } elseif ( is_array( $arg[0] ) && 1 === count( $arg[0] ) && isset( $arg[0][0] ) && is_object( $arg[0][0] ) ) {
    // Backward compatibility for PHP4-style passing of `array( &$this )` as action `$arg`.
    $arg[0] = $arg[0][0];
  }

  $wp_filter[ $hook_name ]->do_action( $arg );

  array_pop( $wp_current_filter );
}

function do_action_ref_array( $hook_name, $args ) {
  global $wp_filter, $wp_actions, $wp_current_filter;

  if ( ! isset( $wp_actions[ $hook_name ] ) ) {
    $wp_actions[ $hook_name ] = 1;
  } else {
    ++$wp_actions[ $hook_name ];
  }

  // Do 'all' actions first.
  if ( isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;
    $all_args            = func_get_args(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
    _wp_call_all_hook( $all_args );
  }

  if ( ! isset( $wp_filter[ $hook_name ] ) ) {
    if ( isset( $wp_filter['all'] ) ) {
      array_pop( $wp_current_filter );
    }

    return;
  }

  if ( ! isset( $wp_filter['all'] ) ) {
    $wp_current_filter[] = $hook_name;
  }

  $wp_filter[ $hook_name ]->do_action( $args );

  array_pop( $wp_current_filter );
}

function has_action( $hook_name, $callback = false ) {
  return has_filter( $hook_name, $callback );
}

function remove_action( $hook_name, $callback, $priority = 10 ) {
  return remove_filter( $hook_name, $callback, $priority );
}

function remove_all_actions( $hook_name, $priority = false ) {
  return remove_all_filters( $hook_name, $priority );
}

function current_action() {
  return current_filter();
}

function doing_action( $hook_name = null ) {
  return doing_filter( $hook_name );
}

function did_action( $hook_name ) {
  global $wp_actions;

  if ( ! isset( $wp_actions[ $hook_name ] ) ) {
    return 0;
  }

  return $wp_actions[ $hook_name ];
}

filters和actions相关的函数都在这里定义了,wordpress能有这么好的扩展性,就得益于它的大量filter钩子和action钩子
继续看wp-settings.php


// Set initial default constants including WP_MEMORY_LIMIT, WP_MAX_MEMORY_LIMIT, WP_DEBUG, SCRIPT_DEBUG, WP_CONTENT_DIR and WP_CACHE.
wp_initial_constants();

// Make sure we register the shutdown handler for fatal errors as soon as possible.
wp_register_fatal_error_handler();

// WordPress calculates offsets from UTC.
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
date_default_timezone_set( 'UTC' );

// Standardize $_SERVER variables across setups.
wp_fix_server_vars();

// Check if we're in maintenance mode.
wp_maintenance();

// Start loading timer.
timer_start();

// Check if we're in WP_DEBUG mode.
wp_debug_mode();

if ( WP_CACHE && apply_filters( 'enable_loading_advanced_cache_dropin', true ) && file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ) ) {
    // For an advanced caching plugin to use. Uses a static drop-in because you would only want one.
    include WP_CONTENT_DIR . '/advanced-cache.php';

    // Re-initialize any hooks added manually by advanced-cache.php.
    if ( $wp_filter ) {
        $wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter );
    }
}

// Define WP_LANG_DIR if not set.
wp_set_lang_dir();

// Load early WordPress files.
require ABSPATH . WPINC . '/compat.php';
require ABSPATH . WPINC . '/class-wp-list-util.php';
require ABSPATH . WPINC . '/formatting.php';
require ABSPATH . WPINC . '/meta.php';
require ABSPATH . WPINC . '/functions.php';
require ABSPATH . WPINC . '/class-wp-meta-query.php';
require ABSPATH . WPINC . '/class-wp-matchesmapregex.php';
require ABSPATH . WPINC . '/class-wp.php';
require ABSPATH . WPINC . '/class-wp-error.php';
require ABSPATH . WPINC . '/pomo/mo.php';

/**
 * @global wpdb $wpdb WordPress database abstraction object.
 * @since 0.71
 */
global $wpdb;
// Include the wpdb class and, if present, a db.php database drop-in.
require_wp_db();

// Set the database table prefix and the format specifiers for database table columns.
$GLOBALS['table_prefix'] = $table_prefix;
wp_set_wpdb_vars();

// Start the WordPress object cache, or an external object cache if the drop-in is present.
wp_start_object_cache();

// Attach the default filters.
require ABSPATH . WPINC . '/default-filters.php';

// Initialize multisite if enabled.
if ( is_multisite() ) {
    require ABSPATH . WPINC . '/class-wp-site-query.php';
    require ABSPATH . WPINC . '/class-wp-network-query.php';
    require ABSPATH . WPINC . '/ms-blogs.php';
    require ABSPATH . WPINC . '/ms-settings.php';
} elseif ( ! defined( 'MULTISITE' ) ) {
    define( 'MULTISITE', false );
}

register_shutdown_function( 'shutdown_action_hook' );

// Stop most of WordPress from being loaded if we just want the basics.
if ( SHORTINIT ) {
    return false;
}
。。。。。。。。。
。。。。。。。。
。。。。。。

紧接着,wp-setting.php初始化常量,注册异常捕获,设置时区,处理$_SERVER,监测站点维护模式,记录接口触发时间,监测开发模式下调试参数,设置多语言包路径。然后compat.php载入兼容函数用于兼容旧版本php,然后载入对象,数组处理、格式化输出、元数据等相关函数。来到require ABSPATH . WPINC . '/functions.php';,这个文件和刚才的plugin.php有点相似,也定义了大量的核心函数,包括时间、本地化、格式化、http、数据库等的处理,还有Option API,然后class-wp-meta-query.php用于组装sql,class-wp-matchesmapregex.php正则匹配执行回调。class-wp.php也是核心,主要是和wp()相关,我们第二部分讲,class-wp-error.php自定义错误类。pomo/mo.php处理多语言本地化。require_wp_db()定义数据库操作类。wp_start_object_cache启动WordPress对象缓存,如果存在插件,则启动外部对象缓存。接下来default-filters.php设置了大量filter钩子和action钩子,接下去末尾省略号部分又载入了大量类和库,这些都是按需使用的,有用到我们再针对性讲解。继续看wp-settings.php的尾部

require ABSPATH . WPINC . '/vars.php';
// Load active plugins.
foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
    wp_register_plugin_realpath( $plugin );

    $_wp_plugin_file = $plugin;
    include_once $plugin;
    $plugin = $_wp_plugin_file; // Avoid stomping of the $plugin variable in a plugin.

    /**
     * Fires once a single activated plugin has loaded.
     *
     * @since 5.1.0
     *
     * @param string $plugin Full path to the plugin's main file.
     */
    do_action( 'plugin_loaded', $plugin );
}
$GLOBALS['wp_the_query'] = new WP_Query();
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];

$GLOBALS['wp_rewrite'] = new WP_Rewrite();

$GLOBALS['wp'] = new WP();

$GLOBALS['wp_widget_factory'] = new WP_Widget_Factory();

$GLOBALS['wp_roles'] = new WP_Roles();

do_action( 'setup_theme' );

// Define the template related constants.
wp_templating_constants();

// Load the default text localization domain.
load_default_textdomain();

$locale      = get_locale();
$locale_file = WP_LANG_DIR . "/$locale.php";
if ( ( 0 === validate_file( $locale ) ) && is_readable( $locale_file ) ) {
    require $locale_file;
}
unset( $locale_file );

$GLOBALS['wp_locale'] = new WP_Locale();

$GLOBALS['wp_locale_switcher'] = new WP_Locale_Switcher();
$GLOBALS['wp_locale_switcher']->init();

// Load the functions for the active theme, for both parent and child theme if applicable.
foreach ( wp_get_active_and_valid_themes() as $theme ) {
    if ( file_exists( $theme . '/functions.php' ) ) {
        include $theme . '/functions.php';
    }
}
unset( $theme );

do_action( 'after_setup_theme' );

// Create an instance of WP_Site_Health so that Cron events may fire.
if ( ! class_exists( 'WP_Site_Health' ) ) {
    require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
}
WP_Site_Health::get_instance();

// Set up current user.
$GLOBALS['wp']->init();

do_action( 'init' );

// Check site status.
if ( is_multisite() ) {
    $file = ms_site_check();
    if ( true !== $file ) {
        require $file;
        die();
    }
    unset( $file );
}
do_action( 'wp_loaded' );

可以看到,vars.php定义了$pagenow,
foreach ( wp_get_active_and_valid_plugins() as $plugin )这里是重点,这里载入了插件文件,从而使插件里面定义的各种钩子能够生效
然后这里创建了各种全局对象,*WPQuery是重点,用来找出WordPress目前正在处理的请求类型。$is属性被设计用来保存这些信息。WP_Rewrite用来处理伪静态,WP_Widget_Factory用于处理后台小组件,WP_Roles用于处理角色相关,接下去就是处理本地化多语言相关操作
foreach ( wp_get_active_and_valid_themes() as $theme )这里也是重点,这里载入了主题的functions.php文件,从而使主题定义的钩子能够生效**
$GLOBALS['wp']->init();初始化了当前用户

wp()

我们查看wp(),

function wp( $query_vars = '' ) {
    global $wp, $wp_query, $wp_the_query;

    $wp->main( $query_vars );

    if ( ! isset( $wp_the_query ) ) {
        $wp_the_query = $wp_query;
    }
}

从上一节$GLOBALS['wp'] = new WP();我们知道$wp的类定义,看一下其main方法

    public function main( $query_args = '' ) {
        $this->init();

        $parsed = $this->parse_request( $query_args );

        if ( $parsed ) {
            $this->query_posts();
            $this->handle_404();
            $this->register_globals();
        }

        $this->send_headers();

        /**
         * Fires once the WordPress environment has been set up.
         *
         * @since 2.1.0
         *
         * @param WP $wp Current WordPress environment instance (passed by reference).
         */
        do_action_ref_array( 'wp', array( &$this ) );
    }

重点$parsed = $this->parse_request( $query_args );

    public function parse_request( $extra_query_vars = '' ) {
        global $wp_rewrite;

        /**
         * Filters whether to parse the request.
         *
         * @since 3.5.0
         *
         * @param bool         $bool             Whether or not to parse the request. Default true.
         * @param WP           $wp               Current WordPress environment instance.
         * @param array|string $extra_query_vars Extra passed query variables.
         */
        if ( ! apply_filters( 'do_parse_request', true, $this, $extra_query_vars ) ) {
            return false;
        }

        $this->query_vars     = array();
        $post_type_query_vars = array();

        if ( is_array( $extra_query_vars ) ) {
            $this->extra_query_vars = & $extra_query_vars;
        } elseif ( ! empty( $extra_query_vars ) ) {
            parse_str( $extra_query_vars, $this->extra_query_vars );
        }
        // Process PATH_INFO, REQUEST_URI, and 404 for permalinks.

        // Fetch the rewrite rules.
        $rewrite = $wp_rewrite->wp_rewrite_rules();
// var_dump($rewrite);
        if ( ! empty( $rewrite ) ) {
            // If we match a rewrite rule, this will be cleared.
            $error               = '404';
            $this->did_permalink = true;

            $pathinfo         = isset( $_SERVER['PATH_INFO'] ) ? $_SERVER['PATH_INFO'] : '';
            list( $pathinfo ) = explode( '?', $pathinfo );
            $pathinfo         = str_replace( '%', '%25', $pathinfo );

            list( $req_uri ) = explode( '?', $_SERVER['REQUEST_URI'] );
            $self            = $_SERVER['PHP_SELF'];

            $home_path       = parse_url( home_url(), PHP_URL_PATH );
            $home_path_regex = '';
            if ( is_string( $home_path ) && '' !== $home_path ) {
                $home_path       = trim( $home_path, '/' );
                $home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
            }

            /*
             * Trim path info from the end and the leading home path from the front.
             * For path info requests, this leaves us with the requesting filename, if any.
             * For 404 requests, this leaves us with the requested permalink.
             */
            $req_uri  = str_replace( $pathinfo, '', $req_uri );
            $req_uri  = trim( $req_uri, '/' );
            $pathinfo = trim( $pathinfo, '/' );
            $self     = trim( $self, '/' );

            if ( ! empty( $home_path_regex ) ) {
                $req_uri  = preg_replace( $home_path_regex, '', $req_uri );
                $req_uri  = trim( $req_uri, '/' );
                $pathinfo = preg_replace( $home_path_regex, '', $pathinfo );
                $pathinfo = trim( $pathinfo, '/' );
                $self     = preg_replace( $home_path_regex, '', $self );
                $self     = trim( $self, '/' );
            }

            // The requested permalink is in $pathinfo for path info requests and
            // $req_uri for other requests.
            if ( ! empty( $pathinfo ) && ! preg_match( '|^.*' . $wp_rewrite->index . '$|', $pathinfo ) ) {
                $requested_path = $pathinfo;
            } else {
                // If the request uri is the index, blank it out so that we don't try to match it against a rule.
                if ( $req_uri == $wp_rewrite->index ) {
                    $req_uri = '';
                }
                $requested_path = $req_uri;
            }
            $requested_file = $req_uri;

            $this->request = $requested_path;

            // Look for matches.
            $request_match = $requested_path;
            if ( empty( $request_match ) ) {
                // An empty request could only match against ^$ regex.
                if ( isset( $rewrite['$'] ) ) {
                    $this->matched_rule = '$';
                    $query              = $rewrite['$'];
                    $matches            = array( '' );
                }
            } else {
                foreach ( (array) $rewrite as $match => $query ) {
                    // If the requested file is the anchor of the match, prepend it to the path info.
                    if ( ! empty( $requested_file ) && strpos( $match, $requested_file ) === 0 && $requested_file != $requested_path ) {
                        $request_match = $requested_file . '/' . $requested_path;
                    }

                    if ( preg_match( "#^$match#", $request_match, $matches ) ||
                        preg_match( "#^$match#", urldecode( $request_match ), $matches ) ) {

                        if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
                            // This is a verbose page match, let's check to be sure about it.
                            $page = get_page_by_path( $matches[ $varmatch[1] ] );
                            if ( ! $page ) {
                                continue;
                            }

                            $post_status_obj = get_post_status_object( $page->post_status );
                            if ( ! $post_status_obj->public && ! $post_status_obj->protected
                                && ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
                                continue;
                            }
                        }

                        // Got a match.
                        $this->matched_rule = $match;
                        break;
                    }
                }
            }

            if ( ! empty( $this->matched_rule ) ) {
                // Trim the query of everything up to the '?'.
                $query = preg_replace( '!^.+\?!', '', $query );

                // Substitute the substring matches into the query.
                $query = addslashes( WP_MatchesMapRegex::apply( $query, $matches ) );

                $this->matched_query = $query;

                // Parse the query.
                parse_str( $query, $perma_query_vars );

                // If we're processing a 404 request, clear the error var since we found something.
                if ( '404' == $error ) {
                    unset( $error, $_GET['error'] );
                }
            }

            // If req_uri is empty or if it is a request for ourself, unset error.
            if ( empty( $requested_path ) || $requested_file == $self || strpos( $_SERVER['PHP_SELF'], 'wp-admin/' ) !== false ) {
                unset( $error, $_GET['error'] );

                if ( isset( $perma_query_vars ) && strpos( $_SERVER['PHP_SELF'], 'wp-admin/' ) !== false ) {
                    unset( $perma_query_vars );
                }

                $this->did_permalink = false;
            }
        }

        /**
         * Filters the query variables allowed before processing.
         *
         * Allows (publicly allowed) query vars to be added, removed, or changed prior
         * to executing the query. Needed to allow custom rewrite rules using your own arguments
         * to work, or any other custom query variables you want to be publicly available.
         *
         * @since 1.5.0
         *
         * @param string[] $public_query_vars The array of allowed query variable names.
         */
        $this->public_query_vars = apply_filters( 'query_vars', $this->public_query_vars );

        foreach ( get_post_types( array(), 'objects' ) as $post_type => $t ) {
            if ( is_post_type_viewable( $t ) && $t->query_var ) {
                $post_type_query_vars[ $t->query_var ] = $post_type;
            }
        }

        foreach ( $this->public_query_vars as $wpvar ) {
            if ( isset( $this->extra_query_vars[ $wpvar ] ) ) {
                $this->query_vars[ $wpvar ] = $this->extra_query_vars[ $wpvar ];
            } elseif ( isset( $_GET[ $wpvar ] ) && isset( $_POST[ $wpvar ] ) && $_GET[ $wpvar ] !== $_POST[ $wpvar ] ) {
                wp_die( __( 'A variable mismatch has been detected.' ), __( 'Sorry, you are not allowed to view this item.' ), 400 );
            } elseif ( isset( $_POST[ $wpvar ] ) ) {
                $this->query_vars[ $wpvar ] = $_POST[ $wpvar ];
            } elseif ( isset( $_GET[ $wpvar ] ) ) {
                $this->query_vars[ $wpvar ] = $_GET[ $wpvar ];
            } elseif ( isset( $perma_query_vars[ $wpvar ] ) ) {
                $this->query_vars[ $wpvar ] = $perma_query_vars[ $wpvar ];
            }

            if ( ! empty( $this->query_vars[ $wpvar ] ) ) {
                if ( ! is_array( $this->query_vars[ $wpvar ] ) ) {
                    $this->query_vars[ $wpvar ] = (string) $this->query_vars[ $wpvar ];
                } else {
                    foreach ( $this->query_vars[ $wpvar ] as $vkey => $v ) {
                        if ( is_scalar( $v ) ) {
                            $this->query_vars[ $wpvar ][ $vkey ] = (string) $v;
                        }
                    }
                }

                if ( isset( $post_type_query_vars[ $wpvar ] ) ) {
                    $this->query_vars['post_type'] = $post_type_query_vars[ $wpvar ];
                    $this->query_vars['name']      = $this->query_vars[ $wpvar ];
                }
            }
        }

        // Convert urldecoded spaces back into '+'.
        foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy => $t ) {
            if ( $t->query_var && isset( $this->query_vars[ $t->query_var ] ) ) {
                $this->query_vars[ $t->query_var ] = str_replace( ' ', '+', $this->query_vars[ $t->query_var ] );
            }
        }

        // Don't allow non-publicly queryable taxonomies to be queried from the front end.
        if ( ! is_admin() ) {
            foreach ( get_taxonomies( array( 'publicly_queryable' => false ), 'objects' ) as $taxonomy => $t ) {
                /*
                 * Disallow when set to the 'taxonomy' query var.
                 * Non-publicly queryable taxonomies cannot register custom query vars. See register_taxonomy().
                 */
                if ( isset( $this->query_vars['taxonomy'] ) && $taxonomy === $this->query_vars['taxonomy'] ) {
                    unset( $this->query_vars['taxonomy'], $this->query_vars['term'] );
                }
            }
        }

        // Limit publicly queried post_types to those that are 'publicly_queryable'.
        if ( isset( $this->query_vars['post_type'] ) ) {
            $queryable_post_types = get_post_types( array( 'publicly_queryable' => true ) );
            if ( ! is_array( $this->query_vars['post_type'] ) ) {
                if ( ! in_array( $this->query_vars['post_type'], $queryable_post_types, true ) ) {
                    unset( $this->query_vars['post_type'] );
                }
            } else {
                $this->query_vars['post_type'] = array_intersect( $this->query_vars['post_type'], $queryable_post_types );
            }
        }

        // Resolve conflicts between posts with numeric slugs and date archive queries.
        $this->query_vars = wp_resolve_numeric_slug_conflicts( $this->query_vars );

        foreach ( (array) $this->private_query_vars as $var ) {
            if ( isset( $this->extra_query_vars[ $var ] ) ) {
                $this->query_vars[ $var ] = $this->extra_query_vars[ $var ];
            }
        }

        if ( isset( $error ) ) {
            $this->query_vars['error'] = $error;
        }

        /**
         * Filters the array of parsed query variables.
         *
         * @since 2.1.0
         *
         * @param array $query_vars The array of requested query variables.
         */
        $this->query_vars = apply_filters( 'request', $this->query_vars );

        /**
         * Fires once all query variables for the current request have been parsed.
         *
         * @since 2.1.0
         *
         * @param WP $wp Current WordPress environment instance (passed by reference).
         */
        do_action_ref_array( 'parse_request', array( &$this ) );

        return true;
    }

这里获取请求路径保存到$requested_path,再和wp_writer获取的$rewrite进行进行匹配。最终把请求参数保存到$this->query_vars
回到main()函数,$this->query_posts();查询了文章列表。$this->handle_404()根据$wp_query->posts是否返回0个以上的文章,设置适当的404或200标题。$this->register_globals() 将查询变量提取回全局命名空间,并注册了$query_string、$posts、$post和$request。

分析template-loader.php

代码

if ( wp_using_themes() ) {
    /**
     * Fires before determining which template to load.
     *
     * @since 1.5.0
     */
    do_action( 'template_redirect' );
}

/**
 * Filters whether to allow 'HEAD' requests to generate content.
 *
 * Provides a significant performance bump by exiting before the page
 * content loads for 'HEAD' requests. See #14348.
 *
 * @since 3.5.0
 *
 * @param bool $exit Whether to exit without generating any content for 'HEAD' requests. Default true.
 */
if ( 'HEAD' === $_SERVER['REQUEST_METHOD'] && apply_filters( 'exit_on_http_head', true ) ) {
    exit;
}

// Process feeds and trackbacks even if not using themes.
if ( is_robots() ) {
    /**
     * Fired when the template loader determines a robots.txt request.
     *
     * @since 2.1.0
     */
    do_action( 'do_robots' );
    return;
} elseif ( is_favicon() ) {
    /**
     * Fired when the template loader determines a favicon.ico request.
     *
     * @since 5.4.0
     */
    do_action( 'do_favicon' );
    return;
} elseif ( is_feed() ) {
    do_feed();
    return;
} elseif ( is_trackback() ) {
    require ABSPATH . 'wp-trackback.php';
    return;
}

if ( wp_using_themes() ) {

    $tag_templates = array(
        'is_embed'             => 'get_embed_template',
        'is_404'               => 'get_404_template',
        'is_search'            => 'get_search_template',
        'is_front_page'        => 'get_front_page_template',
        'is_home'              => 'get_home_template',
        'is_privacy_policy'    => 'get_privacy_policy_template',
        'is_post_type_archive' => 'get_post_type_archive_template',
        'is_tax'               => 'get_taxonomy_template',
        'is_attachment'        => 'get_attachment_template',
        'is_single'            => 'get_single_template',
        'is_page'              => 'get_page_template',
        'is_singular'          => 'get_singular_template',
        'is_category'          => 'get_category_template',
        'is_tag'               => 'get_tag_template',
        'is_author'            => 'get_author_template',
        'is_date'              => 'get_date_template',
        'is_archive'           => 'get_archive_template',
    );
    $template      = false;

    // Loop through each of the template conditionals, and find the appropriate template file.
    foreach ( $tag_templates as $tag => $template_getter ) {
        if ( call_user_func( $tag ) ) {
            $template = call_user_func( $template_getter );
        }

        if ( $template ) {
            if ( 'is_attachment' === $tag ) {
                remove_filter( 'the_content', 'prepend_attachment' );
            }

            break;
        }
    }

    if ( ! $template ) {
        $template = get_index_template();
    }

    /**
     * Filters the path of the current template before including it.
     *
     * @since 3.0.0
     *
     * @param string $template The path of the template to include.
     */
    $template = apply_filters( 'template_include', $template );
    if ( $template ) {
        include $template;
    } elseif ( current_user_can( 'switch_themes' ) ) {
        $theme = wp_get_theme();
        if ( $theme->errors() ) {
            wp_die( $theme->errors() );
        }
    }
    return;
}

首先先执行do_action( 'template_redirect' );钩子让插件有机会在主题加载前做一些事情,可能会退出,这样在不需要显示主题的时候,可以节省40%左右的主题加载时间。对利用XHTTPRequests或处理RPC请求的插件很有用。接下来,如果请求是is_robots()、is_feed()或is_trackback(),将采取适当的行动,并返回模板加载器。接下来,如果WP_USETHEMES(在index.php中首次定义)被设置为 "true",则通过使用get???template()函数获得对应的模板,这些函数都是在wp-includes/theme.php中定义的。一个长的if树使用各种is????()函数(如is_single(), is_page())检查各种条件,并试图获得适当的模板。这些函数确保最高优先级的模板被加载,同时考虑到子主题、丢失的文件、过滤器等。如果$template存在,则模板文件最终被加载进来显示,否则会显示错误信息。
回到开始我们说访问路由是/archives/110,当我们访问/archives/110时,经过foreach ( $tag_templates as $tag => $template_getter ) 这个代码片段的操作,$template最终被定义到themes/twentytwentyone/single.php,最后wordpress把这个模板文件include进来,最终页面呈现出来,当然页面最终的样子取决于模板文件怎么编写,从上面分析可知,wordpress大量的函数都可以用到模板里面

总结

wordpress启动流程大概如下表示
点击查看【processon】

正文完
 

公众号