标签 typecho 下的文章

首先恭喜typecho v1.0的上线!

其次,这篇文章同thinkphp的源代码解析一样都是老高谋划了很久的文章,国庆节由于单位加班没有时间写,今天终于等来了轮休,果断放开了写。希望大家多多支持!

最后老高想说的是,如果大家有兴趣研究源码,那么问题来了,如何高效的学习研究源代码?

老高的建议是:

  1. 一定要熟悉MVC模式(针对WEB开发)
  2. 先看看文档再动手
  3. 分辨什么是好的坏的代码,不要搞盲目崇拜
  4. 做笔记

以下:

文档

如果有什么不明白的,文档里也许会找到答案。

typecho开发文档

版本

再研究源代码前,如果知道自己使用的typecho的版本呢?

答案写在var/Typecho/Common.php里,Typecho_Common类中的常量VERSION

例子:

class Typecho_Common
{
    /** 程序版本 */
    const VERSION = '1.0/14.10.9';
    ...
}

开启DEBUG模式

有些同学可能已经发现,typecho默认对外隐藏了PHP的错误信息,如果我们想要看到真正的报错信息,需要开启typecho的DEBUG模式,也可以叫做开发模式。当我们开启了这个模式后,在开发插件或者了解系统原理的时候就能够得到可视化的错误信息了。

开启方法:

# 修改/config.inc.php
# 在代码的第一行加入
/**开启debug模式*/
define('__TYPECHO_DEBUG__',1);

ps.调试的时候如果你的服务器没有安装xdebug,那么你的var_dump()信息会没有格式。

在此老高推荐使用TP框架内的方法dump(),以后我们就可以使用dump()打印变量信息了。

# 修改/config.inc.php
# 在debug后加入

/**
 * 浏览器友好的变量输出
 * @param mixed $var 变量
 * @param boolean $echo 是否输出 默认为True 如果为false 则返回输出字符串
 * @param string $label 标签 默认为空
 * @param boolean $strict 是否严谨 默认为true
 * @return void|string
 */
function dump($var, $echo=true, $label=null, $strict=true) {
    $label = ($label === null) ? '' : rtrim($label) . ' ';
    if (!$strict) {
        if (ini_get('html_errors')) {
            $output = print_r($var, true);
            $output = '
' . $label . htmlspecialchars($output, ENT_QUOTES) . '
'; } else { $output = $label . print_r($var, true); } } else { ob_start(); var_dump($var); $output = ob_get_clean(); if (!extension_loaded('xdebug')) { $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); $output = '
' . $label . htmlspecialchars($output, ENT_QUOTES) . '
'; } } if ($echo) { echo($output); return null; }else return $output; }

入口

入口文件为index.php,国际惯例贴代码。

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();

typecho也采用了流行的单一入口,在此会引出一个伪静态的问题,此处先留个坑,以后补上。

index.php文件流程:

config.inc.php文件如果存在,就引入;如果不存在,就跳转到install.php进行程序安装,请原谅老高在此跳过install.php流程,如果读者有问题,请留言交流。

config.inc.php在安装程序的时候会自动生成,其主要工作是:引入程序常量,设定包含路径,引入各个核心文件(注意此时没有使用__autoLoad,所以只能require),执行Typecho_Common::init()初始化工作,保存数据库连接信息并初始化数据库对象。

Typecho_Common::init()位于var/Typecho/Common.php,方法代码:

public static function init()
    {
        ini_set( 'display_errors', 'On' );
        /** 设置自动载入函数 */
        if (function_exists('spl_autoload_register')) {
            spl_autoload_register(array('Typecho_Common', '__autoLoad'));
        } else {
            function __autoLoad($className) {
                Typecho_Common::__autoLoad($className);
            }
        }

        /** 兼容php6 */
        if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
            $_GET = self::stripslashesDeep($_GET);
            $_POST = self::stripslashesDeep($_POST);
            $_COOKIE = self::stripslashesDeep($_COOKIE);

            reset($_GET);
            reset($_POST);
            reset($_COOKIE);
        }

        /** 设置异常截获函数 */
        set_exception_handler(array('Typecho_Common', 'exceptionHandle'));
    }

代码中ini_set( 'display_errors', 'On' );是老高自己加的,便于debug,请无视。此方法的主要工作是注册autoload,如果系统打开了magic_quotes_gpc,就使用stripslashesDeep处理输入数据,去除系统自动加入的/,最后接管了系统的异常截获。

config.inc.php完成使命后,控制权又回到了index.php文件。

紧接着执行了Typecho_Widget::widget('Widget_Init'),Typecho_Widget哪里来的?请研究一下init阶段的autoload方法,把类名中的_\替换为/,最后加上.php,这样类名就被映射到了一个确定的文件了。以Typecho_Widget为例,系统将得到Typecho/Widget.php,由于系统核心文件都在var下,那么最终的路径即var/Typecho/Widget.php。所以我们就得到了这样的规律,类名被_分开,类一般会以Typecho或Widget开头,分别对应着文件夹Typecho或Widget,之后就根据剩下的片段去找最终的文件名即可!

@include_once str_replace(array('\\', '_'), '/', $className) . '.php';

Widget类是整个系统的核心类(抽象类),他抽象出了几个很不错的功能,这一点Widget内部方法与接口有介绍,需要注意的是魔术方法__get会在保护属性$row里查找变量,$row也让Widget类增色不少,__callpush方法也需要留意。

下面介绍的是Typecho_Widget::widget方法,widget方法其实是一个工厂方法,他接受一个类名,初始化输入输出对象,一并传入目标类名,执行了目标类的构造方法和execute方法,并将实例化的类保存在self::$_widgetPool数组中。execute十分重要,在thinkphp中有一个异曲同工的方法,叫_initalize,位于controller父类中。

以index.php文件中Typecho_Widget::widget('Widget_Init')为例,Typecho_Widget的widget接收到'Widget_Init',初始化var/Widget/Init.php中的Widget_Init类后,执行了其execute方法。该方法又调用了$this->widget('Widget_Options'),即自己又初始化了Widget_Options类,Widget_Options类读出了user = 0的所有系统配置,并将Widget_Options保存在局部变量$options里,进行了一系列的初始化工作。值得留意的地方:

# $options包含了路由信息
# 系统在这一步才执行了解析PathInfo动作
$this->request->getPathInfo();
# 路由器在此初始化
Typecho_Router::setRoutes
# 所有插件在此初始化
Typecho_Plugin::init($options->plugins);
# 下面还有初始化回执,设定了返回的编码和类型
$this->response->setCharset($options->charset);
$this->response->setContentType($options->contentType);
# 时区设置还有session初始化
# 最后打开缓冲

至此Typecho_Widget::widget('Widget_Init')执行完毕!这时系统初始化正式完毕,开始真正的'工作'!

2015年01月12日更新:

开启开发者模式

下一节

码字不易,转载请注明出处。

博客的文章渐渐多了起来,之前随意的分类就需要好好想整理一下。

为了图方便,直接在数据库里做了替换查询,不料typecho和WP一样,都会在metas表里存着分类和tag的统计信息,统计了这个分类下有多少篇文章,这个统计信息显示在分类和TAG管理页面。

也就是说,如果正常再文章编辑里修改分类并保存,统计信息会走一加一减这个过程,而暴力数据库修改不会触发。

而这种混乱导致老高的瞬间变身不搞不舒服斯基,下面我们一步一步来修复这个统计信息。

首先,找到所有分类(标签同理)

SELECT mid FROM typecho_metas AS m WHERE m.type = 'category';

然后,找到分类关系表,统计文章数

SELECT r.mid,
       count(cid)
FROM typecho_relationships as r
WHERE r.mid IN
    (SELECT mid
     FROM typecho_metas AS m
     WHERE m.type='category')
GROUP BY r.mid;

再次,我们提取出分类名称

SELECT r.mid,
       m.name,
       count(cid)
FROM typecho_relationships as r
LEFT JOIN typecho_metas as m ON m.mid=r.mid
WHERE r.mid IN
    (SELECT mid
     FROM typecho_metas AS m
     WHERE m.type='category')
GROUP BY r.mid;

最后,以上一个查询为基础修改原始表metas

UPDATE typecho_metas,
  (SELECT r.mid AS mid,
          count(cid) AS COUNT
   FROM typecho_relationships AS r
   WHERE r.mid IN
       (SELECT mid
        FROM typecho_metas AS m
        WHERE m.type='category')
   GROUP BY r.mid) AS tmp
SET typecho_metas.COUNT = tmp.COUNT
WHERE tmp.mid = typecho_metas.mid;

上面的SQL也表明了如果使用两张表跟新数据。

突然发现使用分类管理的合并到功能也可以,但是没我这个快 B)

什么是toc?

[toc]

table of contents 即文章目录

toc有什么用

废话,你说目录有啥用,方便查找呗

使用typecho的TX,一定会一点markdown吧,在文章中如果出现##this's h2 tag##,会被程序转换为

this's h2 tag

一个h标签就好比一本书的各个章节,如果我们能把他们清点一下,组成一个目录输出,岂不是妙哉!

TOC如何使用

简单到不能再简单,在你想插入目录的地方放一个[toc][TOC]即可(推荐大写)!

[TOC]必须处于顶格

typecho中的markdown

typecho的源代码中已经使用了激进的MarkdownExtraExtended类来转化md文件,为什么说他很激进呢?因为他扩展了标准的markdown,添加了很多个性化的语法,如直接给元素添加id或者class,还有脚注、缩写词等,用起来确实很爽!但需要注意的是:太多的非标准语法可能会带来移植性差的问题

具体的语法参考请看这里php Markdown Extra

如何使typecho支持toc

要使typecho支持toc需要替换位于源程序中的/wwwroot/var/文件夹下的MarkdownExtraExtended.phpMarkdownExtraExtended.php

原理

这个文件的作用就是为typecho提供md2html的作用,我修改了MarkdownExtraExtended类的__construct方法,为block_gamut数组添加了 doToc 处理模块,并把优先级降到最低。

转换细节位于doToc_doToc_callback中,在此不作赘述。

不完美的地方

  • 要是能加入一个锚点之间平滑滚动的效果就更完美了,改天写个插件弄一弄
  • 在首页也能看到目录,改天看看源代码研究一下,看能不能解决
  • 希望后台加一个选项,自动生成目录,这个比较麻烦,以后看情况解决