WordPress架构简单剖析
前言
最近在搭建自己的博客站点时, 选择了网站使用较多的WordPress, 随着慢慢的使用, 它灵活的插件和主题令我折服. 基本上任何想要实现的功能, 都可以在上面通过插件的形式进行添加. 无论是在访问前的缓存、访问后的统计、访问中的过滤、各种流程的修改等等, 几乎都能够以插件的形式进行修改. 我觉得这太酷了, 如果在我平常业务上能够将架构写成这样, 还有什么需求变化能难倒我?
基于这个原因, 我对WordPress进行了简单的分析, 这就是开源的好处嘛. 我从index.php文件一步步跟踪了整个请求的开始到结束. 因为能力有限, 这可能是最笨的办法了.
解析
执行流程#
index.php文件很简单, 就一句:
require __DIR__ . '/wp-blog-header.php';
而wp-blog-header.php文件呢, 也很简单:
if ( ! isset( $wp_did_header ) ) {$wp_did_header = true;require_once __DIR__ . '/wp-load.php';wp();require_once ABSPATH . WPINC . '/template-loader.php';}
而这, 已经将WordPress的执行流程体现出来了.
1.防止重复加载#
! isset( $wp_did_header ) 判断, 是为了防止文件被重复加载的, 直接跳过
2.加载 库/主题/插件#
第二步引入了wp-load.php文件, 然后又引入了wp-config.php文件, 再然后又引入了wp-settings.php文件, 实际的加载过程, 就在wp-settings.php文件中. 此文件做了下面几件事
- 引入初始化文件
- 常量定义
- 引入库
- 加载插件
- 加载主题
到这里, 还没有针对当前页面数据的查询, 仅完成了初始化过程.
3.查询页面数据#
wp()函数是执行页面数据加载的方法, 会根据当前页面, 到数据库中查询需要显示的数据, 将需要展示的数据准备好.
4.页面展示#
最终引入的template-loader.php文件, 其作用是将数据进行可视化展示.
5.完成#
至此, 整个页面的展示流程就走完了. 按照这个步骤看下来, 整个流程还是比较清晰的.
但是还是没有回答最开始的问题啊, 它灵活在哪里呢? 上面只是简单描述了一下整体的加载流程, 但具体细节还没有提到.
页面展示#
WordPress加载页面的地方, 就是最后的template-loader.php这个文件了.
其根据当前页面, 加载不同的文件进行展示. 至于页面为什么这么灵活, 随便找个页面看一下就知道了. index.php:
拼图式生成页面. 可针对每一个位置进行定制, 并将其进行组装. 所以每个主题都有很高的灵活性, 可以自己设置页面, 也可以选择丢弃某些内容而不展示.
另外, HTML在加载页面的时候, 会对几个模板进行查找, 如在访问: 计算机是如何进行时间同步的 这篇文章的时候, get_single_template 方法会依次查找下面几个文件:
- single-post-计算机是如何进行时间同步的.php
- single-post-%e8%ae%a1%e7%ae%97%e6%9c%ba%e6%98%af%e5%a6%82%e4%bd%95%e8%bf%9b%e8%a1%8c%e6%97%b6%e9%97%b4%e5%90%8c%e6%ad%a5%e7%9a%84.php
- single-post.php
- single.php
若某个文件存在, 就会直接加载. 有没有悟到什么. 这玩意不就可以做缓存嘛. 但是, 不好意思, 在执行这步操作之前, 该查询的数据就已经查过了, 所以这个缓存加了等于没加, 没什么卵用.
钩子函数#
如果WordPress只是能够拼图式组装页面, 那还不够灵活, 因为只能对页面进行操作, 而无法影响执行流程. 对执行流程的影响, 就是它的各种钩子函数了. WordPress的钩子函数通过do_action和apply_filters两个方法进行调用,
看过方法add_action发现, 它就是简单的调用了add_filter方法. 也就是说这两个方法内部是同一个方法. 个人理解, do_action注重与流程的插入, 既向主流程中加入一段逻辑, 没有返回值. 而 apply_filters方法有返回值, 更注重对数据的处理吧.
在WordPress中, 随处可见各种钩子的调用, 初始化的时候、加载插件、插件加载完成、加载主题等等等等.
举个例子, 有一个缓存插件, 就是通过在添加init钩子函数, 将页面内容 echo之后, 直接执行die函数, 以达到快速返回的效果.
不过在查看源码的过程中, 有一个问题, 所有钩子函数的调用, 都是直接使用字符串调用的, 如 do_action('init'). 这种通用的变量, 不应该写个常量列表的么?
不过好在官方维护了一份钩子函数的列表, 列出了所有的钩子, 同时进行了说明并指出调用的具体地址. 需要的时候可以看一下. 我数了一下, 目前一共1470个钩子.
https://developer.wordpress.org/reference/hooks/
可以说, WordPress就是通过各种钩子以及拼图式页面, 分别实现展示和流程的个性化定制. 而这个钩子函数倒也不是什么新鲜玩意, 接口的监听器、各种beforeAction afterAction等等, 在平常开发过程中也经常用到. 只是没有用到这么极致罢.
其他细节#
配置加载#
WordPress的配置是存储在MySQL中的, 而请求加载配置文件的方式是执行sql查询:
SELECT option_name, option_value FROM $wpdb->options WHERE autoload = 'yes'
直接将表中的所有配置, 一次性读出来, 而且, 取出来的数据还不少嘞, 给你个直观感受, 我将结果保存到txt文件, 文件大小1.4mb.
如果说这个查询可以增加缓存, 或者通过配置文件引入的话, 能够省去一些消耗. 但是, 如果想通过插件的方式修改配置读取, 不好意思, 这个不可以. 因为 配置的首次读取是在调用wp_not_installed()函数时, 而此时插件还没加载呢. 如果想修改的话, 貌似只能修改源码了,
在加载配置的时候, 在请求缓存中先读了一次:
故可以预先将配置放到请求缓存中. 在调用方法wp_start_object_cache()加载缓存之后, 立刻调用了wp_cache_add( 'alloptions', $alloptions, 'options' );方法, 可以将全局配置预先放到缓存中, 实验了一下, 确实可行. 如果追求性能极致的话, 可以考虑.
配置存储#
看到数据库配置表wp_options中启用插件的值时, 我完全摸不到头脑, 存储的内容是这样的:
a:7:{i:0;s:49:'easy-table-of-contents/easy-table-of-contents.php';i:1;s:47:'simple-yearly-archive/simple-yearly-archive.php';i:2;s:30:'wp-githuber-md/githuber-md.php';i:3;s:29:'wp-mail-smtp/wp_mail_smtp.php';i:5;s:27:'wp-super-cache/wp-cache.php';i:6;s:31:'wpdiscuz/class.WpdiscuzCore.php';i:7;s:32:'xml-sitemap-feed/xml-sitemap.php';}
这这这, 这是啥? 看不懂, 但又好像能看懂. 于是我追踪了这个值的解析, 就是下面这个函数:
解析后的数据是:
{ '0': 'easy-table-of-contents/easy-table-of-contents.php', '1': 'simple-yearly-archive/simple-yearly-archive.php', '2': 'wp-githuber-md/githuber-md.php', '3': 'wp-mail-smtp/wp_mail_smtp.php', '5': 'wp-super-cache/wp-cache.php', '6': 'wpdiscuz/class.WpdiscuzCore.php', '7': 'xml-sitemap-feed/xml-sitemap.php'}
是不是一下就懂了? 存储的是通过serialize函数进行对象序列化之后的值, 于是, 弱弱的问一下, 直接存json字符串不好么?
全局变量定义#
在WordPress中到处都充斥着各种全局变量. 我在查看缓存文件的时候, 看到了这段代码:
但奇怪的是, 我全局搜索变量$wp_object_cache, 却没有找到定义的地方. 最终我一点一点找到了它定义的地方.
而这种功能风格到处都是, 如果想找到一个变量都有哪些地方使用了, 很不好找. 而且, 直接引用全局变量的方式, 也导致变量之后很难修改. 在源码中就看到了这么一个活生生的例子:
这种风格导致一个后果, 一个变量一旦定义, 就摘不掉了.
数据库查询记录#
在查看数据库查询的时候, 看到了这样的代码:
也就是说, 如果定义了SAVEQUERIES常量, 且为true, 那么就会将查询的sql记录下来. 在log_query方法中, 记录到了queries变量中.
这个操作对于数据库的调优还是比较方便的. 在配置文件中定义常量, 在最终拿到所有的sql及执行时间
总结
对于这种充斥着全局变量和钩子函数的内容, 阅读起来有一丢丢的疲惫, 经常看着看着就看丢了. 不过还是发现了很多有意思的地方.
本来是想看看它为什么这么灵活, 结果发现其实在平常的开发过程中已经用到了, 不过WordPress对一些内容的处理还是给了我一些启发.
比如这种拼图式的页面组成, 可以将页面的展示和数据处理分离. 而在开发接口的时候, 是不是也可以借鉴类似的思路. 这种方式有一个问题, 就是即使页面没有用到的数据, 在查询的时候也都查询出来了, 对于接口这种追求性能的情况, 肯定是不能忍受的. 或者可以将需要使用的数据让展示方给出配置? 不过这样的话, 耦合度又高了, 灵活度也下降了, 难搞.
不过最重要的是, 这破玩意就是我现在在用的呀, 不好好了解一下怎么行. 以后如果有定制化需求, 咱也不至于无从下手了.