x1184的小站
x1184的小站
初探wordpress源码(一)-加载过程
初探wordpress源码(一)-加载过程

阅读说明

本文适合于有一定PHP基础的开发者阅读,包括但不限于OOP,Cookie,变量作用域,Hook等知识点,同时若具有数据库,HTML中Form,HTTP协议相关知识阅读体验更佳。

序言

之前就一直有计划说深入学习下经典项目的源码为自己项目的部署及技术选型做准备,加上也有哥们反映说我现在这博客访问速度太差了,让我抽空好好优化下,遂下定决心从零开始完整阅读wordpress源代码(虽然我知道这主要是服务器和国外cdn的锅),但我也知道以目前的项目经验和代码积累看全部肯定是会头大的,只能说先找最核心部分和做好长期攻坚的准备了😁。

WordPress介绍

WordPress 是目前全世界最流行的內容管理系統(Content Management System),目前全世界超過 33% 以上的網站都是使用 WordPress 建立的,同時在所有 CMS 系統當中,WordPress 足足佔了 60%的市佔率。
因此如今當你要建立一個新網站時,WordPress 絕對是一個不錯的選擇。
WordPress 採用 GPL v2 的開源協議,是一款開放式的內容管理系統,因此全世界每個人都能夠免費的修改與編輯 WordPress 這套系統,就像是 Linux 一樣。
WordPress 作為一套內容管理系統,意味著你能夠輕鬆的透過它來管理網站、發表文章…等等功能,最重要的是,你不需要自己造輪子、不需要會寫程式,也能夠輕鬆地利用 WordPress 來快速建立網站。
如果你是程式開發人員,那麼 WordPress 的自由度與靈活度,也能好好的讓你大展身手,透過開發外掛,你能夠在 WordPress 網站當中實現各種想要的特殊功能,只要你寫的出來,就絕對能夠應用與實現在 WordPress 網站當中。

从开发者角度来说,php在如今的9102年确实是市场份额在被不断蚕食( 当年说php是世界上最好语言的学长你在哪里😅 ),而wordpress便是少数目前还在用php主力开发的产品之一,还有的可能就是各类论坛如Discuz!,老一些的cms,pt面板这些在用了。

引用研究成果

前期准备工作可不能少,这里我是希望看到一些结构比较完整或是讲解比较细致的前人的研究成果的,然后我可以在整理好后多做些创造性的工作,甚至能反过来回馈开源项目是最好的,然而当我google相关文章及项目后。。。

https://img.ocasis.cn/image/5e0843e53f822
搜索结果

第一个2012年太旧了,就两篇文章,一个是调用顺序有点过时了,还有一个数据表结构还有一个数据库关系图,这个挺好的,包括最新版的其实最核心的也就那10个数据库。然后其他几个都是变着法转载第一篇这个哥们的,那哥们画了两个图也被反复的用。再就是那个github的note-wordpress项目,源代码里面加注释也有自己的研究。这个是受益蛮多的,但是这个项目最新commit是2017了,好多文件还是没写完的,并且wp最新版文件结构已经有了变化。所以主要部分和细节还是要自己去看,看源码的整个过程可能就是wordpress本身的注释和note项目追加的注释了,难的文件没有头绪时可能就是他们的点拨让我能继续👏。

分析源码结构

下载好最新版源码后(2019/12/26)解压后可看到如下结构

https://img.ocasis.cn/image/5e07691d5d858
文件结构

idea为jb系软件的配置文件夹,剩下的都是会用到的文件夹,简单查看下文件内容还有之前做操作时url的显示可整理得出第一个是管理员面板的逻辑,第二个是之后自己安装的主题和插件所在的文件夹,第三个是全局配置和全局库,库又包括前端文件和证书和api和混淆逻辑。之后同级文件里,都是最公共和基础的独立文件和发邮件激活定时追踪等相关的。

看下使用手册

个人觉得想分析底层结构或原理,最起码从用户角度说要用的熟。反正我是使用产品时 都是会打开关闭设置里的各个内容看看有什么效果的,不知道你们有没有这个习惯。这里就在源码里就有readme.html,当然这里没有github里也会有wiki或是查官方document。

https://img.ocasis.cn/image/5e076f95504b5
原版教程

精简理解:

System Requirements

PHP version 5.6.20 or higher.
MySQL version 5.0 or higher.

Recommendations

PHP version 7.3 or higher.
MySQL version 5.6 or higher.
The mod_rewrite Apache module.
HTTPS support.
A link to wordpress.org on your site.

wp-admin/update-core.php 自动升级
wp-admin/upgrade.php. 手动升级

初次运行wp-admin/install.php,将会有图形化界面帮助设置wp-config.php ,也可手动依据wp-config-sample创建wp-config.php。也就是说:

  • install.php 安装入口文件
  • wp-config.php 核心配置文件 没有这个无法启动wordpress 这里面也要填写数据库的相关信息
  • index.php 入口文件

选择不同线路去追踪逻辑

这里就选择从根目录里的index.php入口文件这里往下走,查看了发现该文件内容倒是很简单。

index.php

//变量的注释说明
/**
* Tells WordPress to load the WordPress theme and output it.
*
* @var bool
*/
define()全局常量关于引入主题WP_USE_THEMES
require( dirname( __FILE__ ) . '/wp-blog-header.php' ); //引入公有头文件

wp-blog-header.php

if ( ! isset( $wp_did_header ) ) //判断是否初次设置
/**
引入
/wp-load.php
/template-loader.php

第一个文件我们下面详细说,第二个属于主题记载器属于内置库放在之后文章说
其作用为:
载入相应的模板文件,根据前面对url参数的剖析结果, 载入相应的模板(theme)文件, 在模板文件中可以直接取用前面从db中取出的数据, 也可以根据需要重新从db中拉取自己想要显示的数据。 
*/

wp-load.php

/**
* Bootstrap file for setting the ABSPATH constant
* and loading the wp-config.php file. The wp-config.php
* file will then load the wp-settings.php file, which
* will then set up the WordPress environment.
设置ABSPATH常量等启动文件,引入两个配置文件

相当于先引入wp-config.php 再引入wp-settings.php 共同完成初始化设置
设置绝对路径代码段
*/
if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', dirname( __FILE__ ) . '/' );
}

/*
补充:这种利用预定义常量的方式挺方便的,还可以是
`include $_SERVER['DOCUMENT_ROOT']."/lib/sample.lib.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).

检查两个配置文件是否存在并创建,配置文件为什么有两个?config是全局配置可改,setting像是全局常量不会直接修改,并且考虑到了嵌套站点的问题会查找上级的config和setting有的话就导入上级的config,相当于多个子级可以公用父级配置。
*/
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 {

// 不存在时重定向至配置页面,引入公有库里functions和load
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?"
防止infinite loop加验证uri这个环节
*/
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();
//定义content文件夹后导入版本库并检查版本和前期本地化

wp_die( $die, __( 'WordPress › Error' ) ); //Die with an error message 最终带报错终止进程。

wp-load.php 扩展

// 这个属于加载的核心文件,想要扩展外部的东西可以在初始化定义常量时添加
// 比如if ( ! defined( 'ABSPATH' ) )的逻辑之后
// 如果要支持composer的autoload, 加上此名
if (file_exists(ABSPATH . 'vendor/autoload.php')) {
	require_once(ABSPATH . 'vendor/autoload.php');
}
//加入wx提供的jsapi
$wx_options = [
    'debug'  => true,
    'app_id'  => 'wxd1806e66fe96a00c',
    'secret'  => '17ac86b02a204254b4a563cd6a3c05af',
    'token'   => 'vDHg6heBH3m6OM6F7D3638EObEZEDm3b',
    'aes_key'   => 'v6tMUhKs59m936g8G8M3869hIC1SSC8C8QI6IgTMZim',            
    'log' => [
        'level' => 'debug', // level: debug/info/notice/warning/error/critical/alert/emergency
        'file'  => 'easywechat.log',
    ],
    'oauth' => [
        'scopes' => ['snsapi_base'], // scopes: snsapi_userinfo /snsapi_base/snsapi_login
        'callback' => '/examples/oauth_callback.php',
    ],
    'payment' => [
        'merchant_id' => '11111',
        'key' => '222222',
        'cert_path' => 'path/to/your/cert.pem',
        'key_path' => 'path/to/your/key', // XXX: absolute path!!!!
    ],
];

$app = new \EasyWeChat\Foundation\Application($wx_options);
$menu = $app->menu;
$menus = $menu->all();
$menus = $menus['menu']['button'];
var_dump($menus);

wp-config.php

/**
wp-config-sample.php
可以手动改名为wp-config.php作为配置,也可用install.php进行图形化配置安装

里面可以define各种安全验证的盐,设置数据表的统一前缀
定义常量 debug模式是否开启 abspath设置当前运行路径
*/
define( 'DB_NAME', 'database_name_here' );
define( 'DB_USER', 'username_here' );
define( 'DB_PASSWORD', 'password_here' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8' );
define( 'DB_COLLATE', '' );
define( 'AUTH_KEY',         'put your unique phrase here' );
define( 'SECURE_AUTH_KEY',  'put your unique phrase here' );
define( 'LOGGED_IN_KEY',    'put your unique phrase here' );
define( 'NONCE_KEY',        'put your unique phrase here' );
define( 'AUTH_SALT',        'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT',   'put your unique phrase here' );
define( 'NONCE_SALT',       'put your unique phrase here' );

/**
* 如果您有在同一数据库内安装多个WordPress的需求,请为每个WordPress设置
* 不同的数据表前缀。前缀名只能为数字、字母加下划线。
$table_prefix  = 'wp_';

* 将这个值改为true,WordPress将显示所有用于开发的提示。
* 强烈建议插件开发者在开发环境中启用WP_DEBUG。
* 要获取其他能用于调试的信息,请访问Codex。
* @link https://codex.wordpress.org/Debugging_in_WordPress
*/
define('WP_DEBUG', false);

//zh_CN本地化设置:启用ICP备案号显示
define('WP_ZH_CN_ICP_NUM', true);

//这里也是有路径常量的设定,前面load也有应该是双重保险,并且指定了settings的名
/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', dirname( __FILE__ ) . '/' );
}
/** Sets up WordPress vars and included files. */
require_once( ABSPATH . 'wp-settings.php' );

wp-settings.php

/** 
* Used to set up and fix common variables and include
* the WordPress procedural and class library.
设置通用变量和引用以及类库,根据不同位置引入大量文件并调用方法初始化或检查

* These can't be directly globalized in version.php. When updating,
* we're including version.php from another installation and don't want
* these values to be overridden if already set.
*/
global $wp_version, $wp_db_version, $tinymce_version, $required_php_version, $required_mysql_version, $wp_local_package;
require( ABSPATH . WPINC . '/version.php' );
require( ABSPATH . WPINC . '/load.php' );
// 为了更新时能及时更新该变量,没有直接global引入

// Check for the required PHP version and for the MySQL extension or a database drop-in.检查必须的php和mysql版本
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' ); //引入了hook机制的主文件

/**
如控制引入advanced-cache.php这个控制缓存增强功能的 
if ( WP_CACHE && apply_filters( 'enable_loading_advanced_cache_dropin', true ) && file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ) )
这里是检查WP_CACHE常量及过滤器设置还有这个文件是否存在,来决定是否引入这功能
*/

// 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' );

// Include the wpdb class and, if present, a db.php database drop-in.
global $wpdb; 全局的wpdb对象在这里定义,数据库对象
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' );  //注册程序退出时(包括exit,die)的钩子

// Load the L10n library.引入本地化库
require_once( ABSPATH . WPINC . '/l10n.php' );
require_once( ABSPATH . WPINC . '/class-wp-locale.php' );
require_once( ABSPATH . WPINC . '/class-wp-locale-switcher.php' );

// Run the installer if WordPress is not installed.
wp_not_installed();

require( ABSPATH . WPINC . '/class-wp-walker.php' );
require( ABSPATH . WPINC . '/class-wp-ajax-response.php' );
...
require( ABSPATH . WPINC . '/blocks/tag-cloud.php' );//目前版本是有118行的引入,推测版本更新功能越来越完善还会更多,包括一些检查更新相关的,这里其实要优化也是大有文章的

// Load multisite-specific files.
if ( is_multisite() ) {
	require( ABSPATH . WPINC . '/ms-functions.php' );
	require( ABSPATH . WPINC . '/ms-default-filters.php' );
	require( ABSPATH . WPINC . '/ms-deprecated.php' );
}//之后就是引入各种多站点相关的扩展和变量及缓存设置

// Load must-use plugins.引入多站点公用插件并执行加载钩子
foreach ( wp_get_mu_plugins() as $mu_plugin ) {
	include_once( $mu_plugin );
	do_action( 'mu_plugin_loaded', $mu_plugin );
}
unset( $mu_plugin );
// Load network activated plugins.引入多站点的网络插件,多站点间分开
if ( is_multisite() ) {
	foreach ( wp_get_active_network_plugins() as $network_plugin ) {
		wp_register_plugin_realpath( $network_plugin );
		include_once( $network_plugin );
		do_action( 'network_plugin_loaded', $network_plugin );
	}
	unset( $network_plugin );
}
do_action( 'muplugins_loaded' );//多站点的两种plugin加载完成后触发钩子

// Define constants after multisite is loaded.
wp_cookie_constants();
// Define and enforce our SSL constants
wp_ssl_constants();
// Create common globals.
require( ABSPATH . WPINC . '/vars.php' ); //加载适用于全局的变量
// Make taxonomies and posts available to plugins and themes.
// @plugin authors: warning: these get registered again on the init hook.
create_initial_taxonomies();
create_initial_post_types(); //初始化taxnomies节点类型和post_type文章类型的值

if ( ! is_multisite() ) {
	// Handle users requesting a recovery mode link and initiating recovery mode.执行检查可能进入恢复模式
	wp_recovery_mode()->initialize();
}

// Load active plugins.把所有wp-content中确定启用的插件遍历引入并加入钩子
foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
	wp_register_plugin_realpath( $plugin );
	include_once( $plugin );
	do_action( 'plugin_loaded', $plugin );
}
unset( $plugin );

// Load pluggable functions.意味着在插件中可以先定义一些pluggable.php中的函数,以改变worpress的行为
require( ABSPATH . WPINC . '/pluggable.php' );
require( ABSPATH . WPINC . '/pluggable-deprecated.php' );

// Run wp_cache_postload() if object cache is enabled and the function exists.启动前面有提到的对象存储如果该功能开启的话
if ( WP_CACHE && function_exists( 'wp_cache_postload' ) ) {
	wp_cache_postload();
}

do_action( 'plugins_loaded' );
do_action( 'sanitize_comment_cookies' ); //加评论cookie和plugins加载完成的钩子

开始初始化WP系列对象如WP::wp, WP_Query::wp_the_query, WP_Roles::wp_roles, WP_Rewrite::wp_rewrite
WP 代表请求
WP_Query 代表db query
WP_Rewrite代表url中参数解析时用到的permalink, rewrite规则...

$GLOBALS['wp_the_query'] = new WP_Query();
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];  //备份一个
$GLOBALS['wp_rewrite'] = new WP_Rewrite();
$GLOBALS['wp'] = new WP(); //WP对象
$GLOBALS['wp_widget_factory'] = new WP_Widget_Factory(); //挂件的工厂对象
$GLOBALS['wp_roles'] = new WP_Roles();

//开始加载主题,也是在wp-content里的
do_action( 'setup_theme' );
// Define the template related constants. 指定模板路径常量
wp_templating_constants();
// Load the default text localization domain.加载默认本地化字典 如中文wp-content\languages\zh_CN.mo
load_default_textdomain();

$locale      = get_locale(); //获取比如'zh_CN'
$locale_file = WP_LANG_DIR . "/$locale.php"; // 形如 wp-content\languages\zh_CN.php
if ( ( 0 === validate_file( $locale ) ) && is_readable( $locale_file ) ) {
	require( $locale_file );// 验证文件及可读性并加载本地化文件
}
unset( $locale_file );

*WordPress Locale object for loading locale domain date and various strings.
$GLOBALS['wp_locale'] = new WP_Locale();
$GLOBALS['wp_locale_switcher'] = new WP_Locale_Switcher();
$GLOBALS['wp_locale_switcher']->init();//实例本地化对象及本地化切换器对象并完成初始化

//和前边加载plugin一样,把所有wp-content中确定启用的主题遍历引入
//并且这个支持子主题的 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' ); //模板加载前的动作完毕, 真正加载模板的代码还在后面,在template-loader.php中

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

/**
Fires after WordPress has finished loading but before any headers are sent.
Most of WP is loaded at this stage, and the user is authenticated. WP continues to load on the {@see 'init'} hook that follows (e.g. widgets), and many plugins instantiate themselves on it for all sorts of reasons (e.g. they need a user, a taxonomy, etc.).
If you wish to plug an action once WP is loaded, use the {@see 'wp_loaded'} hook below.
这个钩子在WordPress已完成加载,但未发送任何标头时发送,这里wp可以继续加载些小钩子,也推荐写在这里,如果是要全部加载完成后启动的逻辑需要写在wp_loaded钩子后面。
*/
do_action( 'init' );
// Check site status
if ( is_multisite() ) {
	$file = ms_site_check();
	if ( true !== $file ) {
		require( $file );
		die();
	}
	unset( $file );
}
/**
* This hook is fired once WP, all plugins, and the theme are fully loaded and instantiated.
* Ajax requests should use wp-admin/admin-ajax.php. admin-ajax.php can handle requests for users not logged in.
* @link https://codex.wordpress.org/AJAX_in_Plugins
*/
do_action( 'wp_loaded' );

wp-settings.php 扩展

// 初始化$wpdb = new wpdb(...);
require_wp_db();
// 该行之前可以加显示数据库信息的代码和设置钩子,示例写法:
// 打印所有数据库操作
add_action('shutdown', function($arg) {
	global $wpdb;
	//error_log(print_r( $wpdb->queries, true ) ); 
});	

// 到底当前请求最后使用的是哪个模板文件
add_filter( 'template_include', function($template) {
	error_log( 'my template=' . $template );
	return $template;
}, 9999);

add_action('update_option', function($option, $old_value, $value) {
	//if ( is_array( $value ) || is_object( $value ) ) 
	if (in_array( $option, ['sidebars_widgets'] ) )
	{	
		//error_log(print_r( [$option, $old_value, $value], true ) ); 
	}
}, 10, 3);	

总结

理清了这些之后,我们发现此时根目录里的文件里几个比较困难的已经解决了,剩下的都是单个功能为主的,基本的登录注册等模块还有通用的traceback等模块了。

从技术角度说,引入公用显示模板后执行load逻辑,通过引入配置文件和定义路径结合双配置文件的设计,一个适用全局和数据库相关还可以公用,另一个是单个使用,把具体操作逻辑放在hook里面,把外置文件全部在这里引用,变量的初始化声明也都在这里。

加载过程总结一下

https://ae01.alicdn.com/kf/Uff42d764d0a74a629880b09b5ae731e6W.png

引用列表

介绍wordpress
wordpress源码笔记项目
赞赏
欢迎留言交流讨论,留言必回。

admin

文章作者

一个平凡的追梦人

发表评论

textsms
account_circle
email

x1184的小站

初探wordpress源码(一)-加载过程
从零开始查阅资料做好准备工作之后开始分析
扫描二维码继续阅读
2019-12-28