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;
}