最近在做一些和 PHPCMS 相关的二次开发,记录一下收获。
目录结构
1 |
|
生命周期
index.php 入口文件
1 |
|
我们看到入口文件定义了一个常量 PHPCMS_PATH
为当前路径, 同时加载 phpcms
目录下的 base.php
文件,随后又调用 pc_base::create_app()
。 非常类似 Yii,创建一个运行的 app,也是常见的一种入口文件模式。
base.php 核心文件
接下来我们来看一下比较重要的一个文件 base.php
, 位于 phpcms/base.php
。
文件阅读主要分为两个部分
第一部分定义了一堆的路径常量,这在框架的初始化过程中也很常见。
剩下的主要是一个 pc_base
的类定义,这个是我们重点要关注的类。
先来看一下第一部分,常量定义。
首先第一行我们注意到 define('IN_PHPCMS', true);
定义了一个 IN_PHPCMS 的常量,其实目的是确保从入口文件进入的,你会在模块中经常见到如下定义:
defined('IN_PHPCMS') or exit('No permission resources.');
如果我们通过 URL 直接访问这个目录下的文件,由于没有定义 IN_PHPCMS 常量,便无法访问。在一些老的代码里我们经常会看到这样的用法,比如 CI、Discuz。其实我们采用程序与入口分离即可,把入口文件以及静态资源单独放一个 www 作为根目录,而把框架核心放到一个其他目录,分配好权限即可。也不用每个文件头部这么蛋疼的写这么一句了。
接着往下看,就是一些路径,时间,字符集等的一些常量设定了。
注意到如下这句:
1 | //输出页面字符集 |
这里默认输出的就是 text/html
类型。 不过5.4 以上就不用手动写这句了,会默认发送 utf-8。
期间会调用 pc_base
的一些静态方法。比如
1 | //加载公用函数库 |
我们来一个一个整理。
pc_base::load_sys_func
pc_base::load_sys_func
这个方法主要用于加载系统函数, 位于 phpcms/libs/functions
下。
命名规则是 xx.func.php
load_sys_func
实际上是一个代理方法,本质调用的是 pc_base 类的 _load_func
私有方法,会使用静态变量数组来缓存结果,key 则是 md5 过后的 path 值。
pc_base::auto_load_func
我们在来看一下 pc_base::auto_load_func
看名字像是会自动加载一些函数类库,类似 composer
中的 classmap
, 看下具体实现
1 | private static function _auto_load_func($path = '') { |
原来是会自动加载 phpcms/libs/functions/autoload
目录下的所有类,使用 glob
函数获取所有文件后遍历加载。
至此,我们知道一开始会载入一个全局的函数库(global.func.php)和一个自定义的函数库(extension.func.php),然后自动加载 autoload
目录下的类库。
加载公用函数库部分结束。
pc_base::load_config
我们接着看一下加载配置文件的函数 pc_base::load_config('文件','key')
, 我们查找一下 caches/configs/system.php
发现这个文件中返回的就是一个二维数组。在很多框架中,config 的配置都是类似的实现,比如 laravel 中 Config::get('database.default')
就代表查找配置目录中 database.php 文件中的 default 对应的配置。所以 pc_base::load_config('system', 'errorlog')
的意思就是获取 caches/configs/system.php
中的 errorlog
键值。
来看一下 pc_base::load_config
的实现, 也是用一个静态变量数组来存储。
逻辑大概如下:
- 如果静态变量中已经有对应的 key 值,直接返回。
- 设定读取的配置目录 位于
caches/configs
下。 - 如果没有指定 key ,那么会返回整个文件数组。
- 可以指定额外的 default 值, 当获取的 key 对应的值不存在的时候会返回 default 值。
- 把对应的键值写入到静态变量中方便下一次调用。
纵观几个方法,无一不是通过这种静态变量的形式缓存的,同时也把载入路径写死在函数中,或者通过参数传递,已经不是 DRY 的做法了。这可能是具有一定代表意义的时代性的写法,也影响了不少人,可以从当时流行的其他代码中窥见。
其实在现在来看,PHP 5.3 以后带来的命名空间特性,以及 composer 的流行,已经不需要再这样手动载入了,这些方法本质都只是载入对应的类库文件,composer
完全可以满足,而只需要一行 require 'vendor/autoload.php';
。
我们总结一下 pc_base
的一些常见静态方法
pc_base::load_sys_class
加载系统类,位于 phpcms/libs/classes
pc_base::load_app_class
加载模块中的类,位于 modules/模块目录/classes
pc_base::load_model
加载模型类,位于 phpcms/model
pc_base::load_sys_func
加载系统函数,位于 phpcms/libs/functions
pc_base::auto_load_func
自动载入系统函数库,位于 phpcms/libs/functions/autoload
pc_base::load_app_func
加载模块函数,位于 modules/模块目录/functions
pc_base::load_config
加载配置文件,位于 caches/configs/xxx.php
pc_base::create_app
我们回到 index.php
的最后一行的方法 pc_base::create_app()
,这也是刚刚没有分析的一个方法,我们来看一下定义。
1 | /** |
可以看到本质是调用 load_sys_class
方法,通过前面的分析我们知道其实是会去加载 phpcms/libs/classes/application.class.php
文件,这里在提一点,如果是载入类,支持使用 MY_xxxx
的形式来扩展自定义的类。关键部分如下:
1 | if (file_exists(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) { |
假设我们访问的是 modules/content/index.php
,这也是默认的首页路由,我们可以通过创建一个 MY_index.php
来覆盖原有的 index.php
,这也是 PHPCMS 的一种扩展机制。
因此,pc_base::create_app()
本质就是实例化 phpcms/libs/classes/application.class.php
application.class.php
现在我们来看一下 application.class.php
1 | /** |
实例化的时候注入了一个 param
类 ,猜想是解析地址成 c、m、a 之类的,然后调用 init
初始化。
params.class.php
我们来看一下 params
类
比较关键的几个地方:
1 |
|
主要是根据当前的主机地址来获取 route_config
如果没有的话采用 default
默认路由配置。
配置文件位于 caches/config/route.php
1 | return array( |
这也就意味着默认情况下会访问 modules/content/index.php
的 init 方法。
而route_* 系列方法则会把当前 URL 参数的 m、c、a 通过安全处理后返回对应的值。
我们回到 application.class.php
的构造函数,可以看到通过调用 route_*
系列,可以把对应的module,controller 和 action 绑定到 ROUTE_*
常量中。
init
终于到了最后的 init
方法了
1 | /** |
首先吐槽下,调用件事
这个注释,这不是我写错了,而是源码中确实存在的,也是醉了,原谅我语死早。
第一行调用 load_controller
获取控制器实例,这个控制器位于一个具体模块下也就是 m=?
中 m 对应的值,同时也支持 MY_xx
的形式覆盖。注意到这里会判断以_开头的方法,会认为是受保护的方法,不允许访问,命名的时候需要注意。
最后调用 call_user_func()
至此框架流程就算是结束了。是一个比较典型的传统 MVC 的框架形式,而现代化的那些框架则抽象了 HTTP 的整个流程,比如 symfony
的 http component
,带来了更多的可能性以及扩展性,值得我们学习跟上脚步。
至于其他的重头戏都在 modules
目录中,由于和业务相关就不重点分析了。了解了这整一个流程,在做相关的开发相信就会更得心应手。
–EOF–