虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终有一批人不离不弃,其中不乏一些主题和插件开发者,还有一些忠实的粉丝,但是就是这样一个blog系统还是由于缺乏相关文档和主题插件使很多想要摆脱wp的用户难以跨出最后一步,很多想要学习插件开发的人也因为对机制不了解找不到头绪,今天就简单介绍一下typecho的执行流程和插件机制,并配合一些简单的例子来帮助那些找不到头绪的人,大牛可以路过了!
简要流程分析
Typecho是单入口程序,一切访问都通过index.php,所以还要从index.php的代码开始。(以0.9正式版为例,代码只贴流程必须部分)
if (!@include_once 'config.inc.php') {
file_exists('./install.php') ? header('Location: install.php') : print('Missing Config File');
exit;
}
/** 初始化组件 */
Typecho_Widget::widget('Widget_Init');
/** 注册一个初始化插件 */
Typecho_Plugin::factory('index.php')->begin();
/** 开始路由分发 */
Typecho_Router::dispatch();
/** 注册一个结束插件 */
Typecho_Plugin::factory('index.php')->end();
整个index.php只做了三件事
- 加载config.inc.php(config.inc.php设置了相关目录、包含路径、加载系统基础类库、 程序初始化
Typecho_Common::init();
、最后设置数据库连接参数,此处自行查看) - 执行
Typecho_Widget::widget('Widget_Init');
- 执行
Typecho_Router::dispatch();
那么程序初始化又做了些什么呢?
function __autoLoad($className)
{
/**
* 自动载入函数并不判断此类的文件是否存在, 我们认为当你显式的调用它时, 你已经确认它存在了
* 如果真的无法被加载, 那么系统将出现一个严重错误(Fetal Error)
* 如果你需要判断一个类能否被加载, 请使用 Typecho_Common::isAvailableClass 方法
*/
@include_once str_replace('_', '/', $className) . '.php';
}
设置自动载入,将Typecho_Widget
的_
替换为/
,并加上.php
即Typecho/Widget.php
,并include_once 'Typecho/Widget.php';
执行Typecho_Widght
类的Widget
静态函数,参数为Widget_Init
public static function widget($alias, $params = NULL, $request = NULL, $enableResponse = true)
{
list($className) = explode('@', $alias);
if (!isset(self::$_widgetPool[$alias])) {
$fileName = str_replace('_', '/', $className) . '.php';
require_once $fileName;
/** 如果类不存在 */
if (!class_exists($className)) {
/** Typecho_Exception */
require_once 'Typecho/Widget/Exception.php';
throw new Typecho_Widget_Exception($className);
}
/** 初始化request */
if (!empty($request)) {
$requestObject = new Typecho_Request();
$requestObject->setParams($request);
} else {
$requestObject = Typecho_Request::getInstance();
}
/** 初始化response */
$responseObject = $enableResponse ? Typecho_Response::getInstance()
: Typecho_Widget_Helper_Empty::getInstance();
/** 初始化组件 */
$widget = new $className($requestObject, $responseObject, $params);
$widget->execute();
self::$_widgetPool[$alias] = $widget;
}
return self::$_widgetPool[$alias];
}
同样,将Widget_Init
的_
替换为/
,并加上.php
即Widget/Init.php
,并require_once 'Widget/Init.php';
并且执行Widget_Init
的execute
函数
widget('Widget_Options');
/** cookie初始化 */
Typecho_Cookie::setPrefix($options->siteUrl);
/** 初始化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);
/** 默认时区 */
if (function_exists("ini_get") && !ini_get("date.timezone") && function_exists("date_default_timezone_set")) {
@date_default_timezone_set('UTC');
}
/** 初始化时区 */
Typecho_Date::setTimezoneOffset($options->timezone);
/** 开始会话, 减小负载只针对后台打开session支持 */
if ($this->widget('Widget_User')->hasLogin()) {
@session_start();
}
/** 监听缓冲区 */
ob_start();
}
}
真正的初始化是在Typecho_Init,其中最重要的两项任务就是
- 获取 pathinfo (比如访问文章
http://localhost/archives/1/
,则 pathinfo 为/archives/1/
) - 从数据库读取系统路由表(
option
表routingTable
字段)
举例系统安装完成默认路由
[index] => Array
(
[url] => /
[widget] => Widget_Archive
[action] => render
[regx] => |^[/]?$|
[format] => /
[params] => Array
(
)
)
[archive] => Array
(
[url] => /blog/
[widget] => Widget_Archive
[action] => render
[regx] => |^/blog[/]?$|
[format] => /blog/
[params] => Array
(
)
)
[do] => Array
(
[url] => /action/[action:alpha]
[widget] => Widget_Do
[action] => action
[regx] => |^/action/([_0-9a-zA-Z-]+)[/]?$|
[format] => /action/%s
[params] => Array
(
[0] => action
)
)
[post] => Array
(
[url] => /archives/[cid:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/archives/([0-9]+)[/]?$|
[format] => /archives/%s/
[params] => Array
(
[0] => cid
)
)
[attachment] => Array
(
[url] => /attachment/[cid:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/attachment/([0-9]+)[/]?$|
[format] => /attachment/%s/
[params] => Array
(
[0] => cid
)
)
[category] => Array
(
[url] => /category/[slug]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/category/([^/]+)[/]?$|
[format] => /category/%s/
[params] => Array
(
[0] => slug
)
)
[tag] => Array
(
[url] => /tag/[slug]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/tag/([^/]+)[/]?$|
[format] => /tag/%s/
[params] => Array
(
[0] => slug
)
)
[author] => Array
(
[url] => /author/[uid:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/author/([0-9]+)[/]?$|
[format] => /author/%s/
[params] => Array
(
[0] => uid
)
)
[search] => Array
(
[url] => /search/[keywords]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/search/([^/]+)[/]?$|
[format] => /search/%s/
[params] => Array
(
[0] => keywords
)
)
[index_page] => Array
(
[url] => /page/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/page/([0-9]+)[/]?$|
[format] => /page/%s/
[params] => Array
(
[0] => page
)
)
[archive_page] => Array
(
[url] => /blog/page/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/blog/page/([0-9]+)[/]?$|
[format] => /blog/page/%s/
[params] => Array
(
[0] => page
)
)
[category_page] => Array
(
[url] => /category/[slug]/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/category/([^/]+)/([0-9]+)[/]?$|
[format] => /category/%s/%s/
[params] => Array
(
[0] => slug
[1] => page
)
)
[tag_page] => Array
(
[url] => /tag/[slug]/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/tag/([^/]+)/([0-9]+)[/]?$|
[format] => /tag/%s/%s/
[params] => Array
(
[0] => slug
[1] => page
)
)
[author_page] => Array
(
[url] => /author/[uid:digital]/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/author/([0-9]+)/([0-9]+)[/]?$|
[format] => /author/%s/%s/
[params] => Array
(
[0] => uid
[1] => page
)
)
[search_page] => Array
(
[url] => /search/[keywords]/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/search/([^/]+)/([0-9]+)[/]?$|
[format] => /search/%s/%s/
[params] => Array
(
[0] => keywords
[1] => page
)
)
[archive_year] => Array
(
[url] => /[year:digital:4]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/([0-9]{4})[/]?$|
[format] => /%s/
[params] => Array
(
[0] => year
)
)
[archive_month] => Array
(
[url] => /[year:digital:4]/[month:digital:2]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/([0-9]{4})/([0-9]{2})[/]?$|
[format] => /%s/%s/
[params] => Array
(
[0] => year
[1] => month
)
)
[archive_day] => Array
(
[url] => /[year:digital:4]/[month:digital:2]/[day:digital:2]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/([0-9]{4})/([0-9]{2})/([0-9]{2})[/]?$|
[format] => /%s/%s/%s/
[params] => Array
(
[0] => year
[1] => month
[2] => day
)
)
[archive_year_page] => Array
(
[url] => /[year:digital:4]/page/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/([0-9]{4})/page/([0-9]+)[/]?$|
[format] => /%s/page/%s/
[params] => Array
(
[0] => year
[1] => page
)
)
[archive_month_page] => Array
(
[url] => /[year:digital:4]/[month:digital:2]/page/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/([0-9]{4})/([0-9]{2})/page/([0-9]+)[/]?$|
[format] => /%s/%s/page/%s/
[params] => Array
(
[0] => year
[1] => month
[2] => page
)
)
[archive_day_page] => Array
(
[url] => /[year:digital:4]/[month:digital:2]/[day:digital:2]/page/[page:digital]/
[widget] => Widget_Archive
[action] => render
[regx] => |^/([0-9]{4})/([0-9]{2})/([0-9]{2})/page/([0-9]+)[/]?$|
[format] => /%s/%s/%s/page/%s/
[params] => Array
(
[0] => year
[1] => month
[2] => day
[3] => page
)
)
[comment_page] => Array
(
[url] => [permalink:string]/comment-page-[commentPage:digital]
[widget] => Widget_Archive
[action] => render
[regx] => |^(.+)/comment\-page\-([0-9]+)[/]?$|
[format] => %s/comment-page-%s
[params] => Array
(
[0] => permalink
[1] => commentPage
)
)
[feed] => Array
(
[url] => /feed[feed:string:0]
[widget] => Widget_Archive
[action] => feed
[regx] => |^/feed(.*)[/]?$|
[format] => /feed%s
[params] => Array
(
[0] => feed
)
)
[feedback] => Array
(
[url] => [permalink:string]/[type:alpha]
[widget] => Widget_Feedback
[action] => action
[regx] => |^(.+)/([_0-9a-zA-Z-]+)[/]?$|
[format] => %s/%s
[params] => Array
(
[0] => permalink
[1] => type
)
)
[page] => Array
(
[url] => /[slug].html
[widget] => Widget_Archive
[action] => render
[regx] => |^/([^/]+)\.html[/]?$|
[format] => /%s.html
[params] => Array
(
[0] => slug
)
这个在代码里标为routingTable
的东西其实就是程序对不同pathinfo
的解析规则。
剩下的活就交给index.php
里的Typecho_Router::dispatch();
了
这句的注释是/** 开始路由分发 */
接着上边 pathinfo
=/archives/1/
的例子
dispatch
函数将路由表里的[regx]
里的正则表达式一一进行正则匹配,发现与[post]
的匹配,并1
提取作为参数cid
的值,并将参数cid=1
作为参数提交给Widget_Archive
类的render
函数,Widget_Archive
类根据类型post
、cid=1
进行数据查询和模板渲染发送给客户端,至此一个简单的执行流程就完成了!
细心的读者可能会发现,为什么系统路由里的怎么都是
[widget] => Widget_Archive
[action] => render
这是因为typecho在类Widget_Archive
里还根据不同的访问类型调用了不同的查询函数,这个以后再说!