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文件中. 此文件做了下面几件事

  1. 引入初始化文件
  2. 常量定义
  3. 引入库
  4. 加载插件
  5. 加载主题

到这里, 还没有针对当前页面数据的查询, 仅完成了初始化过程.

3.查询页面数据

wp()函数是执行页面数据加载的方法, 会根据当前页面, 到数据库中查询需要显示的数据, 将需要展示的数据准备好.

4.页面展示

最终引入的template-loader.php文件, 其作用是将数据进行可视化展示.

5.完成

至此, 整个页面的展示流程就走完了. 按照这个步骤看下来, 整个流程还是比较清晰的.

但是还是没有回答最开始的问题啊, 它灵活在哪里呢? 上面只是简单描述了一下整体的加载流程, 但具体细节还没有提到.

页面展示

WordPress加载页面的地方, 就是最后的template-loader.php这个文件了.

image-20210806204234754

其根据当前页面, 加载不同的文件进行展示. 至于页面为什么这么灵活, 随便找个页面看一下就知道了. index.php:

image-20210807114551647

拼图式生成页面. 可针对每一个位置进行定制, 并将其进行组装. 所以每个主题都有很高的灵活性, 可以自己设置页面, 也可以选择丢弃某些内容而不展示.

另外, 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_actionapply_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()函数时, 而此时插件还没加载呢. 如果想修改的话, 貌似只能修改源码了,

在加载配置的时候, 在请求缓存中先读了一次:

image-20210807103715008

故可以预先将配置放到请求缓存中. 在调用方法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";}

这这这, 这是啥? 看不懂, 但又好像能看懂. 于是我追踪了这个值的解析, 就是下面这个函数:

image-20210806230134501

解析后的数据是:

{
  "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中到处都充斥着各种全局变量. 我在查看缓存文件的时候, 看到了这段代码:

image-20210806230810146

但奇怪的是, 我全局搜索变量$wp_object_cache, 却没有找到定义的地方. 最终我一点一点找到了它定义的地方.

image-20210806230935842

而这种功能风格到处都是, 如果想找到一个变量都有哪些地方使用了, 很不好找. 而且, 直接引用全局变量的方式, 也导致变量之后很难修改. 在源码中就看到了这么一个活生生的例子:

image-20210806231146550

这种风格导致一个后果, 一个变量一旦定义, 就摘不掉了.

数据库查询记录

在查看数据库查询的时候, 看到了这样的代码:

image-20210807014422380

也就是说, 如果定义了SAVEQUERIES常量, 且为true, 那么就会将查询的sql记录下来. 在log_query方法中, 记录到了queries变量中.

这个操作对于数据库的调优还是比较方便的. 在配置文件中定义常量, 在最终拿到所有的sql及执行时间

总结

对于这种充斥着全局变量和钩子函数的内容, 阅读起来有一丢丢的疲惫, 经常看着看着就看丢了. 不过还是发现了很多有意思的地方.

本来是想看看它为什么这么灵活, 结果发现其实在平常的开发过程中已经用到了, 不过WordPress对一些内容的处理还是给了我一些启发.

比如这种拼图式的页面组成, 可以将页面的展示和数据处理分离. 而在开发接口的时候, 是不是也可以借鉴类似的思路. 这种方式有一个问题, 就是即使页面没有用到的数据, 在查询的时候也都查询出来了, 对于接口这种追求性能的情况, 肯定是不能忍受的. 或者可以将需要使用的数据让展示方给出配置? 不过这样的话, 耦合度又高了, 灵活度也下降了, 难搞.

不过最重要的是, 这破玩意就是我现在在用的呀, 不好好了解一下怎么行. 以后如果有定制化需求, 咱也不至于无从下手了.

------------

原文地址 https://hujingnb.com/archives/633

转载请保留原文连接: WordPress架构简单剖析 | 烟草的香味

guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请发表评论。x