Wordpress启动流程分析

393 阅读5分钟

假设我们要访问xxxxxx.com/archives/11… 这篇文章,并且已经设置好了伪静态了,伪静态规则都指向了入口index.php
image.png
那么我们开始从入口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 &rsaquo; 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,<br/>foreach(wpgetactiveandvalidplugins()aspagenow,<br />**foreach ( wp_get_active_and_valid_plugins() as plugin )这里是重点,这里载入了插件文件,从而使插件里面定义的各种钩子能够生效**
然后这里创建了各种全局对象WP_Query是重点,用来找出WordPress目前正在处理的请求类型。is属性被设计用来保存这些信息WPRewrite用来处理伪静态,WPWidgetFactory用于处理后台小组件,WPRoles用于处理角色相关,接下去就是处理本地化多语言相关操作<br/>foreach(wpgetactiveandvalidthemes()asis_*属性被设计用来保存这些信息**。WP_Rewrite用来处理伪静态,WP_Widget_Factory用于处理后台小组件,WP_Roles用于处理角色相关,接下去就是处理本地化多语言相关操作<br />**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]=newWP();我们知道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=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;
	}

这里获取请求路径保存到requestedpath,再和wpwriter获取的requested_path,再和wp_writer获取的rewrite进行进行匹配。**最终把请求参数保存到this>queryvars<br/>回到main()函数,this->query_vars**。<br />回到main()函数,this->query_posts();查询了文章列表。this>handle404()根据this->handle_404()根据wp_query->posts是否返回0个以上的文章,设置适当的404或200标题。this>registerglobals()将查询变量提取回全局命名空间,并注册了this->register_globals() 将查询变量提取回全局命名空间,并注册了query_string、postsposts、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_USE_THEMES(在index.php中首次定义)被设置为 "true",则通过使用get_???template()函数获得对应的模板,这些函数都是在wp-includes/theme.php中定义的。一个长的if树使用各种is????()函数(如is_single(), is_page())检查各种条件,并试图获得适当的模板。这些函数确保最高优先级的模板被加载,同时考虑到子主题、丢失的文件、过滤器等。如果template存在,则模板文件最终被加载进来显示,否则会显示错误信息。<br/>回到开始我们说访问路由是/archives/110,当我们访问/archives/110时,经过foreach(template存在,则模板文件最终被加载进来显示,否则会显示错误信息。<br />回到开始我们说访问路由是/archives/110,当我们访问/archives/110时,经过foreach ( tag_templates as tag=>tag => template_getter ) 这个代码片段的操作,$template最终被定义到themes/twentytwentyone/single.php,最后wordpress把这个模板文件include进来,最终页面呈现出来,当然页面最终的样子取决于模板文件怎么编写,从上面分析可知,wordpress大量的函数都可以用到模板里面

总结

wordpress启动流程大概如下表示

image.png

image.png