typecho 源码解读(二)
typecho 源码解读(二)
接之前的源码分析,继续对其进行分析。
首页是如何显示出来的
需要经具体的路由呢。
Router.php中的注释(TODO 增加cache缓存)依然没有实现。
dispatch方法:
循环路由表,根据匹配到的值,匹配到出口,然后执行try中的语句。跟match方法很像。
/**
* 路由分发函数
*
* @return void
* @throws Exception
*/
public static function dispatch()
{
/** 获取PATHINFO */
$pathInfo = self::getPathInfo();
foreach (self::$_routingTable as $key => $route) {
if (preg_match($route['regx'], $pathInfo, $matches)) {
self::$current = $key;
try {
/** 载入参数 */
$params = NULL;
if (!empty($route['params'])) {
unset($matches[0]);
$params = array_combine($route['params'], $matches);
}
$widget = Typecho_Widget::widget($route['widget'], NULL, $params);
if (isset($route['action'])) {
$widget->{$route['action']}();
}
Typecho_Response::callback();
return;
} catch (Exception $e) {
if (404 == $e->getCode()) {
Typecho_Widget::destory($route['widget']);
continue;
}
throw $e;
}
}
}
/** 载入路由异常支持 */
throw new Typecho_Router_Exception("Path '{$pathInfo}' not found", 404);
}
在路由分发之前,其实已经获取到了路由表。可是从哪一步获取到的呢?正常加载,config.inc.php然后到index.php。加载是在Typecho_Widget::widget(‘Widget_Init’);过程中有了路由表,那么全局Router::setRoutes只调用了一次。而其作用是设置路由表的。那么其又是如何被调用的?通过全局搜索,找到Init.php中有如下代码:
/** 初始化路由器 */
Typecho_Router::setRoutes($options->routingTable);
如果上述初始化代码被注释,有如下报错,而且还有404页面:
Notice: Undefined index: feed in D:\phpStudy\PHPTutorial\zend\typecho\var\Typecho\Router.php on line 176
Warning: Invalid argument supplied for foreach() in D:\phpStudy\PHPTutorial\zend\typecho\var\Typecho\Router.php on line 180
重点来了**Typecho_Widget::widget(‘Widget_Init’);**作用是,初始化,Widget文件夹下面的Init.php。并执行execute方法体。(稍后研究)
index.php中,调试路由表的过程:
//index.php
/** 开始路由分发 */
var_dump(Typecho_Router::getTable());
Typecho_Router::dispatch();
//在Router.php中增加方法,输出私有变量
public static function getTable()
{
return self::$_routingTable;
}
路径获取,类似于,有限从类的缓存,静态变量中获取,如果没有,则重新获取(实际上是默认参数),代码如下(略过):
/**
* 设置全路径
*
* @access public
* @param string $pathInfo
* @return void
*/
public static function setPathInfo($pathInfo = '/')
{
self::$_pathInfo = $pathInfo;
}
/**
* 获取全路径
*
* @access public
* @return string
*/
public static function getPathInfo()
{
//优先从“缓冲”中读取,如果没有,则先设置“缓存”再读取
if (NULL === self::$_pathInfo) {
self::setPathInfo();
}
return self::$_pathInfo;
}
实例化Widget组件
在dispatch中,其实有这样的代码:
$widget = Typecho_Widget::widget($route['widget'], NULL, $params);
if (isset($route['action'])) {
$widget->{$route['action']}();
}
即实例化路由表中的组件,已经调用action。打印(Router::dispatch中var_dump(self::$_routingTable);)得到路由表,如下:
["index"]=>
array(6) {
["url"]=>
string(1) "/"
["widget"]=>
string(14) "Widget_Archive"
["action"]=>
string(6) "render"
["regx"]=>
string(8) "|^[/]?$|"
["format"]=>
string(1) "/"
["params"]=>
array(0) {
}
}
所以,首页会渲染Widget_Archive组件,并调用render方法。那么根据Typecho_Widget的定义,widget方法会检测,如果没有实例化这个变量,则实例化这个变量,然后调用execute方法。
Widget_Archive组件的继承关系:Widget_Archive→Widget_Abstract_Contents→Widget_Abstract→Typecho_Widget。所以首页的文章是这样的出来的。具体细节待再分析。
Widget_Init类
先贴代码:
/**
* 初始化模块
*
* @package Widget
*/
class Widget_Init extends Typecho_Widget
{
/**
* 入口函数,初始化路由器
*
* @access public
* @return void
*/
public function execute()
{
/** 对变量赋值 */
$options = $this->widget('Widget_Options');
/** 检查安装状态 */
if (!$options->installed) {
$options->update(array('value' => 1), Typecho_Db::get()->sql()->where('name = ?', 'installed'));
}
/** 语言包初始化 */
if ($options->lang && $options->lang != 'zh_CN') {
$dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
Typecho_I18n::setLang($dir . '/' . $options->lang . '.mo');
}
/** 备份文件目录初始化 */
if (!defined('__TYPECHO_BACKUP_DIR__')) {
define('__TYPECHO_BACKUP_DIR__', __TYPECHO_ROOT_DIR__ . '/usr/backups');
}
/** cookie初始化 */
Typecho_Cookie::setPrefix($options->rootUrl);
/** 初始化charset */
Typecho_Common::$charset = $options->charset;
/** 初始化exception */
Typecho_Common::$exceptionHandle = 'Widget_ExceptionHandle';
/** 设置路径 */
if (defined('__TYPECHO_PATHINFO_ENCODING__')) {
$pathInfo = $this->request->getPathInfo(__TYPECHO_PATHINFO_ENCODING__, $options->charset);
} else {
$pathInfo = $this->request->getPathInfo();
}
Typecho_Router::setPathInfo($pathInfo);
/** 初始化路由器 */
Typecho_Router::setRoutes($options->routingTable);
/** 初始化插件 */
Typecho_Plugin::init($options->plugins);
/** 初始化回执 */
$this->response->setCharset($options->charset);
$this->response->setContentType($options->contentType);
/** 初始化时区 */
Typecho_Date::setTimezoneOffset($options->timezone);
/** 开始会话, 减小负载只针对后台打开session支持 */
if ($this->widget('Widget_User')->hasLogin()) {
@session_start();
}
/** 监听缓冲区 */
ob_start();//只有两处,另外一处是在install.php中
}
ob_start缓存区开了,但是找不到关的地方。backup.php中确实有,但好像并不是总会调用。
ajax请求
查看,ajax请求,是如何响应的。那么第一个问题是,怎么鉴定一个请求是不是ajax请求?从network中,过滤xhr发现并没有,而登录好像并不是ajax请求?那么这个框架,能实现ajax请求吗?暂略。
Widget_Ajax这个好像是跟ajax相关的。但是这个类的方法比较少。那么猜想,这个Widget组件是如何被调用的呢?全局搜索Widget_Ajax只在Widget_Do extends Typecho_Widget中发现。这个类,有个映射关系变量,另外实现了一个execute方法。此文件的原理,有待考究。有一处重点代码,如下:
if (isset($widgetName) && class_exists($widgetName)) {
//类反射
$reflectionWidget = new ReflectionClass($widgetName);
//如果这个类实现了Widget_Interface_Do接口,则执行
if ($reflectionWidget->implementsInterface('Widget_Interface_Do')) {
$this->widget($widgetName)->action();
return;
}
}
Widget_Do这个只在Upgrade中使用。升级文件洋洋洒洒2千行,主要功能是对数据库文件进行转换格式等,确保数据库的格式兼容。以后有用到升级相关的,考究一下。
数据库的深层机理
一些特别的代码
switch
位于Archive.php中。switch一般都是一个变量,case是一个常量。而下面的true即为case表达式中的返回值情况,如果未真,则执行。不知道其他语言,有没有这个特点。
/** 判断聚合类型 */
switch (true) {
case 0 === strpos($this->request->feed, '/rss/') || '/rss' == $this->request->feed:
/** 如果是RSS1标准 */
$this->request->feed = substr($this->request->feed, 4);
$this->_feedType = Typecho_Feed::RSS1;
$this->_currentFeedUrl = $this->options->feedRssUrl;
$this->_feedContentType = 'application/rdf+xml';
break;
case 0 === strpos($this->request->feed, '/atom/') || '/atom' == $this->request->feed:
/** 如果是ATOM标准 */
$this->request->feed = substr($this->request->feed, 5);
$this->_feedType = Typecho_Feed::ATOM1;
$this->_currentFeedUrl = $this->options->feedAtomUrl;
$this->_feedContentType = 'application/atom+xml';
break;
default:
$this->_feedType = Typecho_Feed::RSS2;
$this->_currentFeedUrl = $this->options->feedUrl;
$this->_feedContentType = 'application/rss+xml';
break;
}