Fork me on GitHub

PHP字符串中的转义

最近用字符串拼装正则表达式的时候,发现转义时加一条反斜杠与两条的效果是一样的

$pattern = "\d";
$pattern = "\\d";
preg_match('/'.$pattern.'/', '123', $matches);

非常不解,在查阅资料和多次测试后得出结论如下:

反斜杠在PHP的两种字符串(单引号定界符、双引号定界符)中,都具有转义效果,但是它们支持的转义字符是不同的。

单引号为定界符的php字符串,支持两个字符的转义

    \' 单引号 ( $var = '\''; )

    \\ 反斜杠 ( $var = '\\'; )

双引号为定界符的php字符串,支持下列字符的转义:

    \\ 反斜杠 ( $var = "\\"; )

    \" 双引号 ( $var = "\""; )

    \
 换行(LF 或 ASCII 字符 0x0A(10))  
    \
 回车(CR 或 ASCII 字符 0x0D(13))  
    \t 水平制表符(HT 或 ASCII 字符 0x09(9))  

    \$ 美元符号 

在字符串中,当未被转义的反斜杠后面的字符支持转义时,这个反斜杠后面的字符就会被转义。

例如:

<?php
    $str = '\\';    //var_dump($str)将输出:string(1) "\"
    $str = '\a';    //var_dump($str)将输出:string(2) "\a"
    $str = '\\a';    //var_dump($str)将输出:string(2) "\a"
    $str = '\\\a';    //var_dump($str)将输出:string(3) "\\a"

深入理解PHP之匿名函数

(转自: http://www.laruence.com/2010/06/20/1602.html)

PHP中, 传递Callback的方式, 一直很丑陋. 在PHP5.3以前, 我们只有俩种选择:

  1. 字符串的函数名
  2. 使用create_function的返回值

在PHP5.3以后, 我们多了一个选择, 也就是Closure,

$func = function () { ... };
array_walk($arr, $func);

从实现上来说, 第一种方式: 传递函数名字符串是最简单的.

而第二种方式create_function, 其实和第一种方式本质上一样的, create_function返回一个字符串的函数名, 这个函数名的格式是:

"00_lambda_" . count(anonymous_functions)++;

我们来看看create_function的实现步骤:

  1. 获取参数, 函数体
  2. 拼凑一个"function __lambda_func (参数) { 函数体;} "的字符串
  3. eval之
  4. 通过__lambda_func在函数表中找到eval后得到的函数体, 找不到就出错
  5. 定义一个函数名:"00_lambda_" . count(anonymous_functions)++
  6. 用新的函数名替换__lambda_func7. 返回新的函数名

我们来验证下:

<?php
    create_function("", 'echo __FUNCTION__;');
    call_user_func("00lambda_1", 1);?>
    //输出
    __lambda_func

因为在eval的时候, 函数名是”__lambda_func”, 所以匿名函数内会输出__lambda_func, 而因为最后用”00_lambda_” . count(anonymous_functions)++重命名了函数表中的”__lambda_func”函数, 所以可通过”00_lambda_” . count(anonymous_functions)++调用这个匿名函数.

为了证实这一点, 可以将create_function的返回值dump出来查看.

而在PHP5.3发布的时候, 其中有一条new feature就是支持闭包/Lambda Function, 我第一反应是以为zval新增了一个IS_FUNCTION, 但实际上是构造了一个PHP5.3引入的Closure”类”的实例, Closure类的构造函数是私有的, 所以不能被直接实例化, 另外Closure类是Final类, 所以也不能做为基类派生子类.

//php-5.3.0
    $class = new ReflectionClass("Closure");
    var_dump($class->isInternal());
    var_dump($class->isAbstract() );
    var_dump($class->isFinal());
    var_dump($class->isInterface());
    //输出:
    bool(true)
    bool(false)
    bool(true)
    bool(false)
    ?>

而PHP5.3中对闭包的支持, 也仅仅是把要保持的外部变量, 做为Closure对象的”Static属性”(并不是普通意义上的可遍历/访问的属性).

//php-5.3.0
$b = "laruence";
$func = function($a) use($b) {};
var_dump($func);
/* 输出:
object(Closure)#1 (2) {  
    ["static"]=>  array(1) {
        ["b"]=>    string(8) "laruence"  
    }  
    ["parameter"]=>  array(1) {
        ["$a"]=>    string(10) "<required>"  
    }
}
*/

这个实现, 个人认为和JS对闭包的支持比起来, 还是有些太简陋了~

yii2.0 路由(Route)的实现原理

原文地址

路由(Route)

Web开发中不可避免的要使用到URL。用得最多的,就是生成一个指向应用中其他某个页面的URL了。 开发者需要一个简洁的、集中的、统一的方法来完成这一过程。

否则的话,在代码中写入大量的诸如http://www.digpage.com/post/view/100 的代码,一是过于冗长,二是易出错且难排查, 三是日后修改起来容易有遗漏。因此,从开发角度来讲,需要一种更简洁、可以统一管理、又能排查错误的解决方案。

同时,我们在 :ref:install 部分讲解了如何为Yii配置Web服务器,从中可以发现, 所有的用户请求都是发送给入口脚本 index.php 来处理的。那么,开发者需要一种高效的判断请求应当采用哪个 controller 哪个 action 进行处理的方法。

结合以上2点需求,Yii提供了路由和URL管理组件。

所谓路由是指URL中用于标识用于处理用户请求的module, controller, action的部分,一般情况下由 r 查询参数来指定。 如 http://www.digpage.com/index.php?r=post/view&id=100 ,表示这个请求将由PostController 的 actionView来处理。

同时,Yii也提供了一种美化URL的功能,使得上面的URL可以用一个比较整洁、美观的形式表现出来, 如 http://www.digpage.com/post/view/100 。这个功能的实现是依赖于一个称为 urlManager 的应用组件。

使用 urlManager 开发者可以解析用户的请求,并指派相应的module, controller和action来进行处理, 还可以根据预义的路由规则,生成需要的URL返回给用户使用。 简而言之,urlManger具有解析请求以便确定指派谁来处理请求和根据路由规则生成URL 2个功能。

美化URL

一般情况下,Yii应用生成和接受形如 http://www.digpage.com/index.php?r=post/view&id=100 的URL。 这个URL分成几个部分:

  • 表示主机信息的 http://www.digapge.com
  • 表示入口脚本的 index.php
  • 表示路由的 r=post/view
  • 表示普通查询参数的 id=100

其中,主机信息部分从URL来讲,一般是不能少的。当然内部链接可以使用相对路径,这种情况下可以省略, 只是User Agent最终发出Request时,也是包含有主机信息的。 而入口脚本 index.php 我们知道,所有的请求都是要经过它来处理的。也就是说,所有的URL,其实都是指向他的。 那么我们可以通过默认把这个 index.php 从URL中去掉,反正指向他的、不是指向他的,都要交给他来处理。

其次,路由信息对于Yii应用而言也必不可少,表明应当使用哪个controller和action来处理请求, 否则Yii只能使用默认的路由来处理请求。这个形式比较固定,一般是 module/controller/action 形式。

看起来像路径吧?要是能使用路径就好了。那么,如果我们能够将上面的URL转换成http://www.digpage.com/post/view?id=100 , 省略了入口脚本,并将路由信息转换成路径,是不是看起来舒服很多?

这样的链接看起来简洁美观,对于用户比较友好。同时,也比较适合搜索引擎的胃口,据说是SEO的手段之一。

但到了这里还没完,对于查询参数 id=100 而言,这个URL请求的是编号为100的一个POST,并执行view操作。 那么我们可以再进一步改成 http://www.digpage.com/post/view/100 。这样是不是更爽?

有处女座的说了,这个编号100跟前面的字母们放一起显得另类呀,要是都是字母的就更好了。 假如所请求的是标题为 Route的文章,那么不妨使用用http://www.digpage.com/post/view/Route

这样的话,干脆再加上 .html 好了。 变成 http://www.digpage.com/post/view/Route.html 岂不是连处女座也满意了?

我们把 URL http://www.digpage.comindex.php?r=post/view&id=100 变成http://www.digpage.com/post/view/Route.html 的过程就称为URL美化。

Yii有专门的 yiiwebUrlManager 来进行处理,其中:

  • 隐藏入口脚本可以通过 yiiwebUrlManager::showScriptName = false 来实现
  • 路由的路径化可以通过 yiiwebUrlManager::enablePrettyUrl = true 来实现
  • 参数的路径化可以通过路由规则来实现
  • 加入假后缀(fake suffix) .html可以通过yiiwebUrlManager::suffix = '.html' 来实现

这里点一点,有个印象就可以下,在 :ref:urlmanager 部分就会讲到了。

路由规则

所谓孤掌难鸣,urlManager要发挥功能靠单打独斗是不行的,还要有另外一个的东东来配合。 这就是我们本篇要讲的:路由规则。

路由规则是指 urlManager 用于解析请求或生成URL的规则。 一个路由规则必须实现 yiiwebUrlRuleInterface 接口,这个接口定义了两个方法:

  • 用于解析请求的 yiiwebUrlRuleInterface::parseRequest()
  • 用于生成URL的 yiiwebUrlRuleInterface::createUrl()

Yii中,使用 yiiwebUrlRule 来表示路由规则,一般这个类是足够开发者使用的。 但是,如果开发者想自己实现解析请求或生成URL的逻辑,可以以这个类为基类进行派生, 并重载 parseRuquest()和createUrl()

以下是配置文件中urlManager组件的路由规则配置部分,以几个相对简单、典型的路由规则的为例,先有个感性认识::

'rules' => [
    'posts' => 'post/index', 
    'post/<id:d+>' => 'post/view',
    '<controller:(post|comment)>/<id:d+>/<action:(create|update|delete)>' 
        => '<controller>/<action>',
    'DELETE <controller:w+>/<id:d+>' => '<controller>/delete',
    'http://<user:w+>.digpage.com/<lang:w+>/profile' => 'user/profile',
]

上面的例子并没有穷尽路由规则的例子,可以玩的花样还有很多。至于这些例子所表达的规则, 读者朋友们可以发挥想像去猜测,相信你们绝对可以猜个八九不离十。

目前不需要了解太多,只需大致了解数组的键相当于请求(需要解析的或将要生成的),而元素的值则是路由。 请求部分可称为pattern,路由部分则可称为route。对于这2个部分的形式,大致上可以这么看:

  • pattern 是从正则表达式变形而来。去除了两端的 / # 等分隔符。 特别注意别在pattern两端画蛇添足加上分隔符。
  • pattern 中可以使用正则表达式的命名参数,以供route部分引用。这个命名参数也是变形了的。 对于原来 (?P<name>pattern)的命名参数,要变形成<name:pattern>
  • route 不应含有正则表达式,但是可以按 <name> 的形式引用命名参数。

至于具体实现过程,我们马上就会讲。

首先是 yiiwebUrlRule 的代码,让我们来大致看一看::

class UrlRule extends Object implements UrlRuleInterface
{
    // 用于 $mode 表示路由规则的2种工作模式:仅用于解析请求和仅用于生成URL。
    // 任意不为1或2的值均表示两种模式同时适用,
    // 一般未设定或为0时即表示两种模式均适用。
    const PARSING_ONLY = 1;
    const CREATION_ONLY = 2;

    // 路由规则名称
    public $name;

    // 用于解析请求或生成URL的模式,通常是正则表达式
    public $pattern;

    // 用于解析或创建URL时,处理主机信息的部分,如 http://www.digpage.com
    public $host;

    // 指向controller 和 action 的路由
    public $route;

    // 以一组键值对数组指定若干GET参数,在当前规则用于解析请求时,
    // 这些GET参数会被注入到 $_GET 中去
    public $defaults = [];

    // 指定URL的后缀,通常是诸如 ".html" 等,
    // 使得一个URL看起来好像指向一个静态页面。
    // 如果这个值未设定,使用 UrlManager::suffix 的值。
    public $suffix;

    // 指定当前规则适用的HTTP方法,如 GET, POST, DELETE 等。
    // 可以使用数组表示同时适用于多个方法。
    // 如果未设定,表明当前规则适用于所有方法。
    // 当然,这个属性仅在解析请求时有效,在生成URL时是无效的。
    public $verb;

    // 表明当前规则的工作模式,取值可以是 0, PARSING_ONLY, CREATION_ONLY。
    // 未设定时等同于0。
    public $mode;

    // 表明URL中的参数是否需要进行url编码,默认是进行。
    public $encodeParams = true;
    
    // 用于生成新URL的模板
    private $_template;
    
    // 一个用于匹配路由部分的正则表达式,用于生成URL
    private $_routeRule;

    // 用于保存一组匹配参数的正则表达式,用于生成URL
    private $_paramRules = [];

    // 保存一组路由中使用的参数
    private $_routeParams = [];

    // 初始化
    public function init() {...}

    // 用于解析请求,由UrlRequestInterface接口要求
    public function parseRequest($manager, $request) {...}

    // 用于生成URL,由UrlRequestInterface接口要求
    public function createUrl($manager, $route, $params) {...}
}

从上面代码看, UrlRule 的属性(可配置项)比较多。各属性的意义在注释中已经写清楚了,这里就不再复述。 但是我们要着重分析一下初始化函数 yiiwebUrlRule::init() ,来加深对这些属性的理解::

public function init()
{
    // 一个路由规则必定要有 pattern ,否则是没有意义的,
    // 一个什么都没规定的规定,要来何用?
    if ($this->pattern === null) {
        throw new InvalidConfigException('UrlRule::pattern must be set.');
    }

    // 不指定规则匹配后所要指派的路由,Yii怎么知道将请求交给谁来处理?
    // 不指定路由,Yii怎么知道这个规则可以为谁创建URL?
    if ($this->route === null) {
        throw new InvalidConfigException('UrlRule::route must be set.');
    }

    // 如果定义了一个或多个verb,说明规则仅适用于特定的HTTP方法。
    // 既然是HTTP方法,那就要全部大写。
    // verb的定义可以是字符串(单一的verb)或数组(单一或多个verb)。
    if ($this->verb !== null) {
        if (is_array($this->verb)) {
            foreach ($this->verb as $i => $verb) {
                $this->verb[$i] = strtoupper($verb);
            }
        } else {
            $this->verb = [strtoupper($this->verb)];
        }
    }

    // 若未指定规则的名称,那么使用最能区别于其他规则的 $pattern 
    // 作为规则的名称
    if ($this->name === null) {
        $this->name = $this->pattern;
    }

    // 删除 pattern 两端的 "/",特别是重复的 "/",
    // 在写 pattern 时,虽然有正则的成分,但不需要在两端加上 "/",
    // 更不能加上 "#" 等其他分隔符
    $this->pattern = trim($this->pattern, '/');

    // 如果定义了 host ,将 host 部分加在 pattern 前面,作为新的 pattern
    if ($this->host !== null) {
        // 写入的host末尾如果已经包含有 "/" 则去掉,特别是重复的 "/"
        $this->host = rtrim($this->host, '/');
        $this->pattern = rtrim($this->host . '/' . $this->pattern, '/');

    // 既未定义 host ,pattern 又是空的,那么 pattern 匹配任意字符串。
    // 而基于这个pattern的,用于生成的URL的template就是空的,
    // 意味着使用该规则生成所有URL都是空的。
    // 后续也无需再作其他初始化工作了。
    } elseif ($this->pattern === '') {
        $this->_template = '';
        $this->pattern = '#^$#u';
        return;

    // pattern 不是空串,且包含有 '://',以此认定该pattern包含主机信息
    } elseif (($pos = strpos($this->pattern, '://')) !== false) {
        // 除 '://' 外,第一个 '/' 之前的内容就是主机信息
        if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) {
            $this->host = substr($this->pattern, 0, $pos2);

        // '://' 后再无其他 '/',那么整个 pattern 其实就是主机信息
        } else {
            $this->host = $this->pattern;
        }

    // pattern 不是空串,且不包含主机信息,两端加上 '/' ,形成一个正则
    } else {
        $this->pattern = '/' . $this->pattern . '/';
    }

    // route 也要去掉两头的 '/'
    $this->route = trim($this->route, '/');

    // 从这里往下,请结合流程图来看

    // route 中含有 <参数> ,则将所有参数提取成 [参数 => <参数>] 
    // 存入 _routeParams[],
    // 如 ['controller' => '<controller>', 'action' => '<action>'],
    // 留意这里的短路判断,先使用 strpos(),快速排除无需使用正则的情况
    if (strpos($this->route, '<') !== false &&
        preg_match_all('/<(w+)>/', $this->route, $matches)) {
        foreach ($matches[1] as $name) {
            $this->_routeParams[$name] = "<$name>";
        }
    }

    // 这个 $tr[] 和 $tr2[] 用于字符串的转换
    $tr = [
        '.' => '\.',
        '*' => '\*',
        '$' => '\$',
        '[' => '\[',
        ']' => '\]',
        '(' => '\(',
        ')' => '\)',
    ];
    $tr2 = [];

    // pattern 中含有 <参数名:参数pattern> ,
    // 其中 ':参数pattern' 部分是可选的。
    if (preg_match_all('/<(w+):?([^>]+)?>/', $this->pattern, $matches,
        PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
        foreach ($matches as $match) {
            // 获取 “参数名”
            $name = $match[1][0];

            // 获取 “参数pattern” ,如果未指定,使用 '[^/]' ,
            // 表示匹配除 '/' 外的所有字符
            $pattern = isset($match[2][0]) ? $match[2][0] : '[^/]+';

            // 如果 defaults[] 中有同名参数,
            if (array_key_exists($name, $this->defaults)) {
                // $match[0][0] 是整个 <参数名:参数pattern> 串
                $length = strlen($match[0][0]);
                $offset = $match[0][1];

                // pattern 中 <参数名:参数pattern> 两头都有 '/'
                if ($offset > 1 && $this->pattern[$offset - 1] === '/'
                    && $this->pattern[$offset + $length] === '/') {
                    // 留意这个 (?P<name>pattern) 正则,这是一个命名分组。
                    // 仅冠以一个命名供后续引用,使用上与直接的 (pattern) 没有区别
                    // 见:http://php.net/manual/en/regexp.reference.subpatterns.php
                    $tr["/<$name>"] = "(/(?P<$name>$pattern))?";
                } else {
                    $tr["<$name>"] = "(?P<$name>$pattern)?";
                }

            // defaults[]中没有同名参数
            } else {
                $tr["<$name>"] = "(?P<$name>$pattern)";
            }

            // routeParams[]中有同名参数
            if (isset($this->_routeParams[$name])) {
                $tr2["<$name>"] = "(?P<$name>$pattern)";

            // routeParams[]中没有同名参数,则将 参数pattern 存入 _paramRules[] 中。
            // 留意这里是怎么对  参数pattern  进行处理后再保存的。
            } else {
                $this->_paramRules[$name] = $pattern === '[^/]+' ? '' :
                    "#^$pattern$#u";
            }
        }
    }
    
    // 将 pattern 中所有的 <参数名:参数pattern> 替换成 <参数名> 后作为 _template
    $this->_template = preg_replace('/<(w+):?([^>]+)?>/', '<$1>', $this->pattern);

    // 将 _template 中的特殊字符及字符串使用 tr[] 进行转换,并作为最终的pattern
    $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';

    // 如果指定了 routePrams 还要使用 tr2[] 对 route 进行转换,
    // 并作为最终的 _routeRule
    if (!empty($this->_routeParams)) {
        $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
    }
}

上面的代码难点在于pattern等的转换过程,有点翻来覆去,转换过去、转换回来的感觉,这里我们先放一放, 秋后再找他们来算帐,注意力先放在 init() 的前半部分,这些代码提醒我们:

  • 规则的 $pattern和$route 是必须配置的。
  • 规则的名称 $name和主机信息$host在未配置的情况下,可以从$pattern 来获取。
  • $pattern虽然含有正则的成分,但不需要在两端加入/,更不能使用# 等其他分隔符。 Yii会自动为我们加上。
  • 指定 $pattern 为空串,可以使该规则匹配任意的URL。此时基于该规则所生成的所有URL也都是空串。
  • $pattern中含有:\ 时,Yii会认为其中包含了主机信息。此时就不应当再指定 host 。 否则,Yii会将 host 接在这个 pattern 前,作为新的pattern。这会造成该pattern 两段 :\ , 而这显然不是我们要的。

接下来要啃稍硬点的骨头了,就是 init() 的后半段, 我们以一个普通的 ['post/<action:w+>/<id:d+>' => 'post/<action>'] 为例。 同时,我们假设这个路由规则默认有 $defaults['id'] = 100 ,表示在未指定 post 的 id 时, 使用100作为默认的id。那么这个UrlRule的初始过程如 :ref:img-urlrule-init 所示。

.. _img-urlrule-init:

.. figure:: _static/UrlRule_init.png :alt: UrlRule路由规则初始化过程示意图 :height: 60em

UrlRule路由规则初始化过程示意图

后续的初始化过程具体如下:

['post/<action:w+>/<id:d+>' => 'post/<action>'] , 我们有 $pattern = '/post/<action:w+>/<id:d+>/'$route = 'post/<action>'

  1. 首先从 $route中提取出由< >所包含的部分。这里可以得到['action' => '<action>']
    将其存入 $_routeParams[ ] 中。
  2. 再从 $pattern中提取出由< >所包含的部分,这里匹配2个部分,1个<action:w+> 和1个 <idd+> 。下面对这2个部分进行分别处理。
  3. 对于 <action:w+>由于$defaults[ ]中不存在下标为action的元素,于是向$tr[ ] 写入 (?P<$name>$pattern)形式的元素,得到$tr['<action>'] = '(?P<$name>w+)' 。 而对于 <id:d+>,由于$defaults['id'] = 100,所以写入$tr[ ] 的元素形式有所不同, 变成 (/(?P<$name>$pattern))?。于是有$tr['<id>'] = (/(?P<$id>d+))?
  4. 由于在第1步只有 $_routeParams['action'] = '<actoin>',而没有下标为id 的元素。 所以对于 <action:w+>,往tr2[ ]中写入['<action>' => '(?P<action>w+)'] , 而对于 <id:d+>则往$_paramRules[ ]中写入['id' => '#^d+$#u']
  5. 上面只是准备工作,接下来开始各种替换。首先将 $pattern中所有<name:pattern> 替换成 <name>并作为$_template。因此,$_template = '/post/<action>/<id>/'
  6. 接下来用 $tr[ ]对$_template进行替换,并在两端加上分隔符作为$pattern 。 于是有 $pattern = '#^post/(?P<action>w+)(/(?P<id>d+))?$#u'
  7. 最后,由于第1步中 $_routeParams不为空,所以需要使用$tr2[ ]对$route 进行替换, 并在两端加上分隔符后,作为 $_routeRule,于是有$_routeRule = '#^post/(?P<action>w+)$#'

这些替换的意义在于方便开发者以简洁的方式在配置文件中书写路由规则,然后将这些简洁的规则, 再替换成规范的正则表达式。让我们来看看这个 init() 的成果吧。仍然以上面的['post/<action:w+>/<id:d+>' => 'post/<action>']为例,经过init() 处理后,我们最终得到了::

$urlRule->route = 'post/<action>';
$urlRule->pattern = '#^post/(?P<action>w+)(/(?P<id>d+))?$#u';
$urlRule->_template = '/post/<action>/<id>/';
$urlRule->_routeRule = '#^post/(?P<action>w+)$#';
$urlRule->_routeParams = ['action' => '<action>'];
$urlRule->_paramRules = ['id' => '#^d+$#u'];
// $tr 和 $tr2 作为局部变量已经完成历史使命光荣退伍了

下面我们来讲讲 UrlRule 是如何创建和解析URL的。

创建URL

URL的创建就 UrlRule 层面来讲,是由 yiiwebUrlRule::createUrl() 负责的, 这个方法可以根据传入的路由和参数创建一个相应的URL来。具体代码如下::

public function createUrl($manager, $route, $params)
{
    // 判断规则是否仅限于解析请求,而不适用于创建URL
    if ($this->mode === self::PARSING_ONLY) {
        return false;
    }
    $tr = [];

    // 如果传入的路由与规则定义的路由不一致,
    // 如 post/view 与 post/<action> 并不一致
    if ($route !== $this->route) {

        // 使用 $_routeRule 对 $route 作匹配测试
        if ($this->_routeRule !== null && preg_match($this->_routeRule,
            $route, $matches)) {
            
            // 遍历所有的 _routeParams
            foreach ($this->_routeParams as $name => $token) {
                // 如果该路由规则提供了默认的路由参数,
                // 且该参数值与传入的路由相同,则可以省略
                if (isset($this->defaults[$name]) &&
                    strcmp($this->defaults[$name], $matches[$name]) === 0) {
                    $tr[$token] = '';
                } else {
                    $tr[$token] = $matches[$name];
                }
            }
        // 传入的路由完全不能匹配该规则,返回
        } else {
            return false;
        }
    }

    // 遍历所有的默认参数
    foreach ($this->defaults as $name => $value) {
        // 如果默认参数是路由参数,如 <action>
        if (isset($this->_routeParams[$name])) {
            continue;
        }

        // 默认参数并非路由参数,那么看看传入的 $params 里是否提供该参数的值。
        // 如果未提供,说明这个规则不适用,直接返回。
        if (!isset($params[$name])) {
            return false;

        // 如果 $params 提供了该参数,且参数值一致,则 $params 可省略该参数
        } elseif (strcmp($params[$name], $value) === 0) { 
            unset($params[$name]);

            // 且如果有该参数的转换规则,也可置为空。等下一转换就消除了。
            if (isset($this->_paramRules[$name])) {
                $tr["<$name>"] = '';
            }

        // 如果 $params 提供了该参数,但又与默认参数值不一致,
        // 且规则也未定义该参数的正则,那么规则无法处理这个参数。
        } elseif (!isset($this->_paramRules[$name])) {
            return false;
        }
    }

    // 遍历所有的参数匹配规则
    foreach ($this->_paramRules as $name => $rule) {

        // 如果 $params 传入了同名参数,且该参数不是数组,且该参数匹配规则,
        // 则使用该参数匹配规则作为转换规则,并从 $params 中去掉该参数
        if (isset($params[$name]) && !is_array($params[$name]) 
            && ($rule === '' || preg_match($rule, $params[$name]))) {
            $tr["<$name>"] = $this->encodeParams ?
                urlencode($params[$name]) : $params[$name];
            unset($params[$name]);
        
        // 否则一旦没有设置该参数的默认值或 $params 提供了该参数,
        // 说明规则又不匹配了
        } elseif (!isset($this->defaults[$name]) || isset($params[$name])) {
            return false;
        }
    }

    // 使用 $tr 对 $_template 时行转换,并去除多余的 '/'
    $url = trim(strtr($this->_template, $tr), '/');

    // 将 $url 中的多个 '/' 变成一个
    if ($this->host !== null) {
        // 再短的 host 也不会短于 8 
        $pos = strpos($url, '/', 8);
        if ($pos !== false) {
            $url = substr($url, 0, $pos) . preg_replace('#/+#', '/',
                substr($url, $pos));
        }
    } elseif (strpos($url, '//') !== false) {
        $url = preg_replace('#/+#', '/', $url);
    }

    // 加上 .html 之类的假后缀
    if ($url !== '') {
        $url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
    }

    // 加上查询参数们
    if (!empty($params) && ($query = http_build_query($params)) !== '') {
        $url .= '?' . $query;
    }
    return $url;
}

我们以上面提到 ['post/<action:w+>/<id:d+>' => 'post/<action>'] 路由规则来创建一个URL, 就假设要创建路由为 `post/view,id=100 的URL吧。

URL的创建过程大体上分4个阶段:

第一阶段

调用 createUrl(Yii::$app->urlManager, 'post/view', ['id'=>101])

传入的路由为 post/view与规则定义的路由post/<action> 不同。 但是, post/view可以匹配路由规则的$_routeRule = '#^post/(?P<action>w+)$#' 。 所以,认为该规则是适用的,可以接着处理。而如果连正则也匹配不上,那就说明该规则不适用,返回 false

遍历路由规则的所有 $_routeParams,这个例子中,$_routeParams['action' => '<action>'] 。 这个我们称为路由参数规则,即出现在路由部分的参数。

对于这个路由参数规则,我们并未为其设置默认值。但实际使用中,有的时候可能会提供默认的路由参数, 比如对于形如 post/index之类的,我们经常想省略掉index,那么就可以为<action> 提供一个默认值 index

对于有默认值的情况,我们的头脑要清醒,目前是要用路由规则来创建URL, 规则所定义的默认值并非意味着可以处理不提供这个路由参数值的路由, 而是说在处理路由参数值与默认值相等的路由时,最终生成的URL中可以省略该默认值。

即默认是体现在最终的URL上,是体现在URL解析过程中的默认,而不是体现在创建URL的过程中。 也就是说,对于 post/index类的路由,如果默认index,则生成的URL中可以不带有index

这里没有默认值,相对简单。由于 $_routeRule正则中,使用了命名分组,即(?P<action>...) 。 所以,可以很方便地使用 $matches['action']来捕获w+所匹配的部分,这里匹配的是view 。 故写入 $tr['<action>'] = view

第二阶段

接下来遍历所有的默认参数,当然不包含路由参数部分,因为这个在前面已经处理过了。这里只处理余下的参数。 注意这个默认值的含义,如同我们前面提到的,这里是创建时必须提供,而生成出来的URL可以省略的意思。 因此,对于 $params 中未提供相应参数的,或提供了参数值但与默认值不一致,且规则没定义参数的正则的, 均说明规则不适用。 只有在 $params 提供相应参数,且参数值与默认值一致或匹配规则时方可进行后续处理。

这里我们有一个默认参数 ['id' => 100]。传入的$params['id'] => 100 。两者一致,规则适用。

于将该参数从 $params 中删去。

接下来,看看是否为参数 id定义了匹配规则。还真有$_paramRules['id'] => '#^d+$#u' 。 但这也用不上了,因为这是在创建URL,该参数与默认值一致,等下的 id 是要从URL中去除的。 因此,写入 $tr['<id>'] = ''

第三阶段

再接下来,就是遍历所有参数匹配规则 $_paramRules 了。对于这个例子, 只有 $_paramRules = ['id' => '#^d+$#u']

如果 $params中并未定义该id 参数,那么这一步什么也不用做,因为没有东西要写到URL中去。

而一旦定义了 id ,那么就需要看看当前路由规则是否适用了。 判断的标准是所提供的参数不是数组,且匹配 $_paramRules 所定义的规则。 而如果 $parasm['id']是数组,或不与规则匹配,或定义了id 的参数规则却没有定义其默认值而 $params['id'] 又未提供,则规则不适用。

这里,我们在是在前面处理默认参数时,已经将 id从$params 中删去。 但判断到规则为 id 定义了默认值的,所以认为规则仍然适用。只是,这里实际上不用做任何处理。 如果需要处理的情况,也是将该参数从 $params中删去,然后写入$tr['<id>'] = 100

第四阶段

上面一切准备就绪之后,就可以着手生成URL了。主要用 $tr对路由规则的$_template 进行转换。 这里, $_template = '/post/<action>/<id>/',因此,转换后再去除多余的/ 就变成了 $url = 'post/view'。其中id=100 被省略掉了。

最后再分别接上 .html的后缀和查询参数,一个URLpost/view.html 就生成了。 其中,查询参数串是 $params中剩下的内容,使用PHP的http_build_query( ) 生成的。

从创建URL的过程来看,重点是完成这么几项工作:

  • 看规则是否适用,主要标准是路由与规则定义的是否匹配,规则通过默认值或正则所定义的参数,是否都提供了。
  • 看看当前要创建的URL是否与规则定义的默认的路由参数和查询参数一致,对于一致的,可以省略。
  • 看将这些与默认值一致的,规则已经定义了的参数从 $params 删除,余下的,转换成最终URL的查询参数串。

解析URL

说完了路由规则生成URL的过程,再来看看其逻辑上的逆过程,即URL的解析。

先从路由规则 yiiwebUrlRule::parseRequest() 的代码入手::

public function parseRequest($manager, $request)
{
    // 当前路由规则仅限于创建URL,直接返回 false。
    // 该方法返回false表示当前规则不适用于当前的URL。
    if ($this->mode === self::CREATION_ONLY) {
        return false;
    }
    
    // 如果规则定义了适用的HTTP方法,则要看当前请求采用的方法是否可以接受
    if (!empty($this->verb) && !in_array($request->getMethod(),
        $this->verb, true)) {
        return false;
    }

    // 获取URL中入口脚本之后、查询参数 ? 号之前的全部内容,即为PATH_INFO
    $pathInfo = $request->getPathInfo();
    // 取得配置的 .html 等假后缀,留意 (string)null 转成空串
    $suffix = (string) ($this->suffix === null ? $manager->suffix :
        $this->suffix);

    // 有假后缀且有PATH_INFO
    if ($suffix !== '' && $pathInfo !== '') {
        $n = strlen($suffix);

        // 当前请求的 PATH_INFO 以该假后缀结尾,留意 -$n 的用法
        if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) {
            $pathInfo = substr($pathInfo, 0, -$n);

            // 整个PATH_INFO 仅包含一个假后缀,这是无效的。
            if ($pathInfo === '') {
                return false;
            }

        // 应用配置了假后缀,但是当前URL却不包含该后缀,返回false
        } else {
            return false;
        }
    }

    // 规则定义了主机信息,即 http://www.digpage.com 之类,那要把主机信息接回去。
    if ($this->host !== null) {
        $pathInfo = strtolower($request->getHostInfo()) . 
            ($pathInfo === '' ? '' : '/' . $pathInfo);
    }

    // 当前URL是否匹配规则,留意这个pattern是经过 init() 转换的
    if (!preg_match($this->pattern, $pathInfo, $matches)) {
        return false;
    }

    // 遍历规则定义的默认参数,如果当前URL中没有,则加入到 $matches 中待统一处理,
    // 默认值在这里发挥作用了,虽然没有,但仍视为捕获到了。
    foreach ($this->defaults as $name => $value) {
        if (!isset($matches[$name]) || $matches[$name] === '') {
            $matches[$name] = $value;
        }
    }
    $params = $this->defaults;
    $tr = [];

    // 遍历所有匹配项,注意这个 $name 的由来是 (?P<name>...) 的功劳
    foreach ($matches as $name => $value) {
        // 如果是匹配一个路由参数
        if (isset($this->_routeParams[$name])) {
            $tr[$this->_routeParams[$name]] = $value;
            unset($params[$name]);

        // 如果是匹配一个查询参数
        } elseif (isset($this->_paramRules[$name])) {
            // 这里可能会覆盖掉 $defaults 定义的默认值
            $params[$name] = $value;
        }
    }

    // 使用 $tr 进行转换
    if ($this->_routeRule !== null) {
        $route = strtr($this->route, $tr);
    } else {
        $route = $this->route;
    }
    Yii::trace("Request parsed with URL rule: {$this->name}", __METHOD__);
    return [$route, $params];
}

我们以 http://www.digpage.com/post/view.html 为例,来看看上面的代码是如何解析成路由['post/view', ['id'=>100]]的。注意,这里我们仍然假设路由规则提供了id=100 默认值。 而如果路由规则未提供该默认值,则请求形式要变成 http://www.digapge.com/post/view/100.html 。 同时,规则的 $pattern 也会不同::

// 未提供默认值,id 必须提供,否则匹配不上
$pattern = '#^post/(?P<action>w+)/(?P<id>d+)?$#u';

// 提供了默认值,id 可以不提供,照样可以匹配上
$pattern = '#^post/(?P<action>w+)(/(?P<id>d+))?$#u';

这个不同的原因在于 UrlRule::init() ,读者朋友们可以回头看看。

在讲URL的解析前,让我们先从请求的第一个经手人Web Server说起,在 :ref:install 中讲到Web Server的配置时, 我们将所有未命中的请求转交给入口脚本来处理:

.. code-block:: nginx

  location / {
      try_files $uri $uri/ /index.php?$args;
  }

  # fastcgi.conf
  fastcgi_split_path_info ^(.+?.php)(/.*)$;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

以Nginx为例, try_files 会依次尝试处理:

  1. /post/view.html ,这个如果真有一个也就罢了,但其实多半不存在。
  2. /post/view.html/ ,这个目录一般也不存在。
  3. /index.php ,这个正是入口脚本,可以处理。至此,Nginx与Yii顺利交接。
    由于请求最终交给入口脚本来处理,且我们隐藏了URL中入口脚本名,上述请求还原回来的话, 应该是 http://www.digapge.com/index.php/post/view.html 。 自然,这 post/view.html 就是PATH_INFO了。 有关PATH_INFO的更多知识,请看 :ref:web_request 部分的内容。

好了,在Yii从Web Server取得控制权之后,就是我们大显身手的时候了。在解析过程中,UrlRule主要做了这么几件事:

  • 通过 PATH_INFO 还原请求,如去除假后缀,开头接上主机信息等。还原后的请求为 post/view
  • 看看当前请求是否匹配规则,这个匹配包含了主机、路由、参数等各方面的匹配。如不匹配,说明规则不适用, 返回 false 。在这个例子中,规则并未定义主机信息方面的规则, 规则中 $pattern = '#^post/(?P<action>w+)(/(?P<id>d+))?$#u' 。这与还原后的请求完全匹配。 如果URL没有使用默认值 id = 100,如post/view/101.html ,也是同样匹配的。
  • 看看请求是否提供了规则已定义了默认值的所有参数,如果未提供,视为请求提供了这些参数,且他的值为默认值。 这里URL中并未提供 id参数,所以,视为他提供了id = 100 的参数。简单粗暴而有效。
  • 使用规则定义的路由进行转换,生成新的路由。 再把上一步及当前所有参数作为路由的参数,共同组装成一个完整路由。

具体的转换流程可以看看 :
请输入图片描述
UrlRule路由规则解析URL的过程示意图

Markdown语法

Markdown 语法说明

官方文档


目录

  • 概述

    ####[宗旨](#philosophy) | [行内HTML](#inlineHTML) | [自动转换特殊字符](#automaticEscaping)
    
  • 块元素

    ####[段落和换行](#paragraphs) | [标题](#headers) | [区块引用](#blockQuotes) | [列表](#lists) | [代码区块](#codeBlocks) | [分隔线](#horizontal)
    
  • 范围元素

    ####[链接](#links) | [强调](#emphasis) | [代码](#code) | [图片](#images)
    
  • 其它

    ####[反斜杠](#backslash) | [自动链接](#automaticLinks) | [工具](#tools)
    

<m id="overview">概述</m>

<m id="philosophy">宗旨</m>

Markdown 的目标是实现「易读易写」。

可读性,无论如何,都是最重要的。一份使用 Markdown 格式撰写的文件应该可以直接以纯文本发布,并且看起来不会像是由许多标签或是格式指令所构成。Markdown 语法受到一些既有 text-to-HTML 格式的影响,包括 SetextatxTextilereStructuredTextGrutatextEtText,而最大灵感来源其实是纯文本电子邮件的格式。

总之, Markdown 的语法全由一些符号所组成,这些符号经过精挑细选,其作用一目了然。比如:在文字两旁加上星号,看起来就像强调。Markdown 的列表看起来,嗯,就是列表。Markdown 的区块引用看起来就真的像是引用一段文字,就像你曾在电子邮件中见过的那样。

<m id="inlineHTML">行内HTML</m>

Markdown 语法的目标是:成为一种适用于网络的书写语言。

Markdown 不是想要取代 HTML,甚至也没有要和它相近,它的语法种类很少,只对应 HTML 标记的一小部分。Markdown 的构想不是要使得 HTML 文档更容易书写。在我看来, HTML 已经很容易写了。Markdown 的理念是,能让文档更容易读、写和随意改。HTML 是一种发布的格式,Markdown 是一种书写的格式。就这样,Markdown 的格式语法只涵盖纯文本可以涵盖的范围。

不在 Markdown 涵盖范围之内的标签,都可以直接在文档里面用 HTML 撰写。不需要额外标注这是 HTML 或是 Markdown;只要直接加标签就可以了。

要制约的只有一些 HTML 区块元素――比如 <div><table><pre><p> 等标签,必须在前后加上空行与其它内容区隔开,还要求它们的开始标签与结尾标签不能用制表符或空格来缩进。Markdown 的生成器有足够智能,不会在 HTML 区块标签外加上不必要的 <p> 标签。

例子如下,在 Markdown 文件里加上一段 HTML 表格:

这是一个普通段落。

<table>
    <tr>
        <td>Foo</td>
    </tr>
</table>

这是另一个普通段落。

请注意,在 HTML 区块标签间的 Markdown 格式语法将不会被处理。比如,你在 HTML 区块内使用 Markdown 样式的强调会没有效果。

HTML 的区段(行内)标签如 <span><cite><del> 可以在 Markdown 的段落、列表或是标题里随意使用。依照个人习惯,甚至可以不用 Markdown 格式,而直接采用 HTML 标签来格式化。举例说明:如果比较喜欢 HTML 的 <a><img> 标签,可以直接使用这些标签,而不用 Markdown 提供的链接或是图像标签语法。

和处在 HTML 区块标签间不同,Markdown 语法在 HTML 区段标签间是有效的。

<m id="automaticEscaping">自动转换特殊字符</m>

在 HTML 文件中,有两个字符需要特殊处理: <&< 符号用于起始标签,& 符号则用于标记 HTML 实体,如果你只是想要显示这些字符的原型,你必须要使用实体的形式,像是 &lt;&amp;。

& 字符尤其让网络文档编写者受折磨,如果你要打「AT&T」 ,你必须要写成「AT&amp;T」。而网址中的 & 字符也要转换。比如你要链接到:

http://images.google.com/images?num=30&q=larry+bird

你必须要把网址转换写为:

http://images.google.com/images?num=30&amp;q=larry+bird

才能放到链接标签的 href 属性里。不用说也知道这很容易忽略,这也可能是 HTML 标准检验所检查到的错误中,数量最多的。

Markdown 让你可以自然地书写字符,需要转换的由它来处理好了。如果你使用的 & 字符是 HTML 字符实体的一部分,它会保留原状,否则它会被转换成 &amp;。

所以你如果要在文档中插入一个版权符号 ©,你可以这样写:

&copy;

Markdown 会保留它不动。而若你写:

AT&T

Markdown 就会将它转为:

AT&amp;T

类似的状况也会发生在 < 符号上,因为 Markdown 允许 兼容 HTML ,如果你是把 < 符号作为 HTML 标签的定界符使用,那 Markdown 也不会对它做任何转换,但是如果你写:

4 < 5

Markdown 将会把它转换为:

4 &lt; 5

不过需要注意的是,code 范围内,不论是行内还是区块, <& 两个符号都一定会被转换成 HTML 实体,这项特性让你可以很容易地用 Markdown 写 HTML code (和 HTML 相对而言, HTML 语法中,你要把所有的 <& 都转换为 HTML 实体,才能在 HTML 文件里面写出 HTML code。)


<m id="block">块元素</m>

<m id="paragraphs">段落和换行</m>

一个 Markdown 段落是由一个或多个连续的文本行组成,它的前后要有一个以上的空行(空行的定义是显示上看起来像是空的,便会被视为空行。比方说,若某一行只包含空格和制表符,则该行也会被视为空行)。普通段落不该用空格或制表符来缩进。

「由一个或多个连续的文本行组成」这句话其实暗示了 Markdown 允许段落内的强迫换行(插入换行符),这个特性和其他大部分的 text-to-HTML 格式不一样(包括 Movable Type 的「Convert Line Breaks」选项),其它的格式会把每个换行符都转成 <br /> 标签。

如果你确实想要依赖 Markdown 来插入 <br /> 标签的话,在插入处先按入两个以上的空格然后回车。

的确,需要多费点事(多加空格)来产生 <br /> ,但是简单地「每个换行都转换为 <br />」的方法在 Markdown 中并不适合, Markdown 中 email 式的 区块引用 和多段落的 列表 在使用换行来排版的时候,不但更好用,还更方便阅读。

<m id="headers">标题</m>

Markdown 支持两种标题的语法,类 Setext 和类 atx 形式。

类 Setext 形式是用底线的形式,利用 = (最高阶标题)和 - (第二阶标题),例如:

This is an H1
=============

This is an H2
-------------

任何数量的 = 和 - 都可以有效果。

类 Atx 形式则是在行首插入 1 到 6 个 # ,对应到标题 1 到 6 阶,例如:

# 这是 H1

## 这是 H2

###### 这是 H6

你可以选择性地「闭合」类 atx 样式的标题,这纯粹只是美观用的,若是觉得这样看起来比较舒适,你就可以在行尾加上 #,而行尾的 # 数量也不用和开头一样(行首的井字符数量决定标题的阶数):

# 这是 H1 #

## 这是 H2 ##

### 这是 H3 ######

<m id="blockQuotes">区块引用 Blockquotes</m>

Markdown 标记区块引用是使用类似 email 中用 > 的引用方式。如果你还熟悉在 email 信件中的引言部分,你就知道怎么在 Markdown 文件中建立一个区块引用,那会看起来像是你自己先断好行,然后在每行的最前面加上 >

> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
> 
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
> id sem consectetuer libero luctus adipiscing.

Markdown 也允许你偷懒只在整个段落的第一行最前面加上 >

> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.

区块引用可以嵌套(例如:引用内的引用),只要根据层次加上不同数量的 >

> This is the first level of quoting.
>
> > This is nested blockquote.
>
> Back to the first level.

引用的区块内也可以使用其他的 Markdown 语法,包括标题、列表、代码区块等:

> ## 这是一个标题。
> 
> 1.   这是第一行列表项。
> 2.   这是第二行列表项。
> 
> 给出一些例子代码:
> 
>     return shell_exec("echo $input | $markdown_script");

任何像样的文本编辑器都能轻松地建立 email 型的引用。例如在 BBEdit 中,你可以选取文字后然后从选单中选择增加引用阶层。

<m id="lists">列表</m>

Markdown 支持有序列表和无序列表。

无序列表使用星号、加号或是减号作为列表标记:

*   Red
*   Green
*   Blue

等同于:

+   Red
+   Green
+   Blue

也等同于:

-   Red
-   Green
-   Blue

有序列表则使用数字接着一个英文句点:

1.  Bird
2.  McHale
3.  Parish

很重要的一点是,你在列表标记上使用的数字并不会影响输出的 HTML 结果,上面的列表所产生的 HTML 标记为:

<ol>
<li>Bird</li>
<li>McHale</li>
<li>Parish</li>
</ol>

如果你的列表标记写成:

1.  Bird
1.  McHale
1.  Parish

或甚至是:

3. Bird
1. McHale
8. Parish

你都会得到完全相同的 HTML 输出。重点在于,你可以让 Markdown 文件的列表数字和输出的结果相同,或是你懒一点,你可以完全不用在意数字的正确性。

如果你使用懒惰的写法,建议第一个项目最好还是从 1. 开始,因为 Markdown 未来可能会支持有序列表的 start 属性。

列表项目标记通常是放在最左边,但是其实也可以缩进,最多 3 个空格,项目标记后面则一定要接着至少一个空格或制表符。

要让列表看起来更漂亮,你可以把内容用固定的缩进整理好:

*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
    viverra nec, fringilla in, laoreet vitae, risus.
*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
    Suspendisse id sem consectetuer libero luctus adipiscing.

但是如果你懒,那也行:

*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
viverra nec, fringilla in, laoreet vitae, risus.
*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
Suspendisse id sem consectetuer libero luctus adipiscing.

如果列表项目间用空行分开,在输出 HTML 时 Markdown 就会将项目内容用 <p> 标签包起来,举例来说:

*   Bird
*   Magic

会被转换为:

<ul>
<li>Bird</li>
<li>Magic</li>
</ul>

但是这个:

*   Bird

*   Magic

会被转换为:

<ul>
<li><p>Bird</p></li>
<li><p>Magic</p></li>
</ul>

列表项目可以包含多个段落,每个项目下的段落都必须缩进 4 个空格或是 1 个制表符:

1.  This is a list item with two paragraphs. Lorem ipsum dolor
    sit amet, consectetuer adipiscing elit. Aliquam hendrerit
    mi posuere lectus.

    Vestibulum enim wisi, viverra nec, fringilla in, laoreet
    vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
    sit amet velit.

2.  Suspendisse id sem consectetuer libero luctus adipiscing.

如果你每行都有缩进,看起来会看好很多,当然,再次地,如果你很懒惰,Markdown 也允许:

*   This is a list item with two paragraphs.

    This is the second paragraph in the list item. You're
only required to indent the first line. Lorem ipsum dolor
sit amet, consectetuer adipiscing elit.

*   Another item in the same list.

如果要在列表项目内放进引用,那 > 就需要缩进:

*   A list item with a blockquote:

    > This is a blockquote
    > inside a list item.

如果要放代码区块的话,该区块就需要缩进两次,也就是 8 个空格或是 2 个制表符:

*   一列表项包含一个列表区块:

        <代码写在这>

当然,项目列表很可能会不小心产生,像是下面这样的写法:

1986. What a great season.

换句话说,也就是在行首出现数字-句点-空白,要避免这样的状况,你可以在句点前面加上反斜杠。

1986. What a great season.

<m id="codeBlocks">代码区块</m>

和程序相关的写作或是标签语言原始码通常会有已经排版好的代码区块,通常这些区块我们并不希望它以一般段落文件的方式去排版,而是照原来的样子显示,Markdown 会用 <pre><code> 标签来把代码区块包起来。

要在 Markdown 中建立代码区块很简单,只要简单地缩进 4 个空格或是 1 个制表符就可以,例如,下面的输入:

这是一个普通段落:

    这是一个代码区块。

Markdown 会转换成:

<p>这是一个普通段落:</p>

<pre><code>这是一个代码区块。</code></pre>

这个每行一阶的缩进(4 个空格或是 1 个制表符),都会被移除,例如:

Here is an example of AppleScript:

    tell application "Foo"
        beep
    end tell

会被转换为:

<p>Here is an example of AppleScript:</p>

<pre><code>tell application "Foo"
    beep
end tell
</code></pre>

一个代码区块会一直持续到没有缩进的那一行(或是文件结尾)。

在代码区块里面, &<> 会自动转成 HTML 实体,这样的方式让你非常容易使用 Markdown 插入范例用的 HTML 原始码,只需要复制贴上,再加上缩进就可以了,剩下的 Markdown 都会帮你处理,例如:

    <div class="footer">
        &copy; 2004 Foo Corporation
    </div>

会被转换为:

<pre><code>&lt;div class="footer"&gt;
    &amp;copy; 2004 Foo Corporation
&lt;/div&gt;
</code></pre>

代码区块中,一般的 Markdown 语法不会被转换,像是星号便只是星号,这表示你可以很容易地以 Markdown 语法撰写 Markdown 语法相关的文件。

<m id="horizontal">分隔线</m>

你可以在一行中用三个以上的星号、减号、底线来建立一个分隔线,行内不能有其他东西。你也可以在星号或是减号中间插入空格。下面每种写法都可以建立分隔线:

* * *

***

*****

- - -

---------------------------------------

<m id="span">范围元素</m>

<m id="links">链接</m>

Markdown 支持两种形式的链接语法: 行内式和参考式两种形式。

不管是哪一种,链接文字都是用 [方括号] 来标记。

要建立一个行内式的链接,只要在方块括号后面紧接着圆括号并插入网址链接即可,如果你还想要加上链接的 title 文字,只要在网址后面,用双引号把 title 文字包起来即可,例如:

This is [an example](http://example.com/ "Title") inline link.

[This link](http://example.net/) has no title attribute.

会产生:

<p>This is <a href="http://example.com/" title="Title">
an example</a> inline link.</p>

<p><a href="http://example.net/">This link</a> has no
title attribute.</p>

如果你是要链接到同样主机的资源,你可以使用相对路径:

See my [About](/about/) page for details.

参考式的链接是在链接文字的括号后面再接上另一个方括号,而在第二个方括号里面要填入用以辨识链接的标记:

This is [an example][id] reference-style link.

你也可以选择性地在两个方括号中间加上一个空格:

This is [an example] [id] reference-style link.

接着,在文件的任意处,你可以把这个标记的链接内容定义出来:

[id]: http://example.com/  "Optional Title Here"

链接内容定义的形式为:

  • 方括号(前面可以选择性地加上至多三个空格来缩进),里面输入链接文字
  • 接着一个冒号
  • 接着一个以上的空格或制表符
  • 接着链接的网址
  • 选择性地接着 title 内容,可以用单引号、双引号或是括弧包着

下面这三种链接的定义都是相同:

[foo]: http://example.com/  "Optional Title Here"
[foo]: http://example.com/  'Optional Title Here'
[foo]: http://example.com/  (Optional Title Here)

请注意:有一个已知的问题是 Markdown.pl 1.0.1 会忽略单引号包起来的链接 title。

链接网址也可以用尖括号包起来:

[id]: <http://example.com/>  "Optional Title Here"

你也可以把 title 属性放到下一行,也可以加一些缩进,若网址太长的话,这样会比较好看:

[id]: http://example.com/longish/path/to/resource/here
    "Optional Title Here"

网址定义只有在产生链接的时候用到,并不会直接出现在文件之中。

链接辨别标签可以有字母、数字、空白和标点符号,但是并不区分大小写,因此下面两个链接是一样的:

[link text][a]
[link text][A]

隐式链接标记功能让你可以省略指定链接标记,这种情形下,链接标记会视为等同于链接文字,要用隐式链接标记只要在链接文字后面加上一个空的方括号,如果你要让 "Google" 链接到 google.com,你可以简化成:

[Google][]

然后定义链接内容:

[Google]: http://google.com/

由于链接文字可能包含空白,所以这种简化型的标记内也许包含多个单词:

Visit [Daring Fireball][] for more information.

然后接着定义链接:

[Daring Fireball]: http://daringfireball.net/

链接的定义可以放在文件中的任何一个地方,我比较偏好直接放在链接出现段落的后面,你也可以把它放在文件最后面,就像是注解一样。

下面是一个参考式链接的范例:

I get 10 times more traffic from [Google] [1] than from
[Yahoo] [2] or [MSN] [3].

  [1]: http://google.com/        "Google"
  [2]: http://search.yahoo.com/  "Yahoo Search"
  [3]: http://search.msn.com/    "MSN Search"

如果改成用链接名称的方式写:

I get 10 times more traffic from [Google][] than from
[Yahoo][] or [MSN][].

  [google]: http://google.com/        "Google"
  [yahoo]:  http://search.yahoo.com/  "Yahoo Search"
  [msn]:    http://search.msn.com/    "MSN Search"

上面两种写法都会产生下面的 HTML。

<p>I get 10 times more traffic from <a href="http://google.com/"
title="Google">Google</a> than from
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>

下面是用行内式写的同样一段内容的 Markdown 文件,提供作为比较之用:

I get 10 times more traffic from [Google](http://google.com/ "Google")
than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
[MSN](http://search.msn.com/ "MSN Search").

参考式的链接其实重点不在于它比较好写,而是它比较好读,比较一下上面的范例,使用参考式的文章本身只有 81 个字符,但是用行内形式的却会增加到 176 个字元,如果是用纯 HTML 格式来写,会有 234 个字元,在 HTML 格式中,标签比文本还要多。

使用 Markdown 的参考式链接,可以让文件更像是浏览器最后产生的结果,让你可以把一些标记相关的元数据移到段落文字之外,你就可以增加链接而不让文章的阅读感觉被打断。

<m id="emphasis">强调</m>

Markdown 使用星号(*)和底线(_)作为标记强调字词的符号,被 *_ 包围的字词会被转成用 <em> 标签包围,用两个 *_ 包起来的话,则会被转成 <strong>,例如:

*single asterisks*

_single underscores_

**double asterisks**

__double underscores__

会转成:

<em>single asterisks</em>

<em>single underscores</em>

<strong>double asterisks</strong>

<strong>double underscores</strong>

你可以随便用你喜欢的样式,唯一的限制是,你用什么符号开启标签,就要用什么符号结束。

强调也可以直接插在文字中间:

un*frigging*believable

但是如果你的 *_ 两边都有空白的话,它们就只会被当成普通的符号。

如果要在文字前后直接插入普通的星号或底线,你可以用反斜线:

*this text is surrounded by literal asterisks*

<m id="code">代码</m>

如果要标记一小段行内代码,你可以用反引号把它包起来(`),例如:

Use the `printf()` function.

会产生:

<p>Use the <code>printf()</code> function.</p>

如果要在代码区段内插入反引号,你可以用多个反引号来开启和结束代码区段:

``There is a literal backtick (`) here.``

这段语法会产生:

<p><code>There is a literal backtick (`) here.</code></p>

代码区段的起始和结束端都可以放入一个空白,起始端后面一个,结束端前面一个,这样你就可以在区段的一开始就插入反引号:

A single backtick in a code span: `` ` ``

A backtick-delimited string in a code span: `` `foo` ``

会产生:

<p>A single backtick in a code span: <code>`</code></p>

<p>A backtick-delimited string in a code span: <code>`foo`</code></p>

在代码区段内,& 和尖括号都会被自动地转成 HTML 实体,这使得插入 HTML 原始码变得很容易,Markdown 会把下面这段:

Please don't use any `<blink>` tags.

转为:

<p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>

你也可以这样写:

`&#8212;` is the decimal-encoded equivalent of `&mdash;`.

以产生:

<p><code>&amp;#8212;</code> is the decimal-encoded
equivalent of <code>&amp;mdash;</code>.</p>

<m id="images">图片</m>

很明显地,要在纯文字应用中设计一个「自然」的语法来插入图片是有一定难度的。

Markdown 使用一种和链接很相似的语法来标记图片,同样也允许两种样式: 行内式和参考式。

行内式的图片语法看起来像是:

![Alt text](/path/to/img.jpg)

![Alt text](/path/to/img.jpg "Optional title")

详细叙述如下:

  • 一个惊叹号 !
  • 接着一个方括号,里面放上图片的替代文字
  • 接着一个普通括号,里面放上图片的网址,最后还可以用引号包住并加上 选择性的 'title' 文字。

参考式的图片语法则长得像这样:

![Alt text][id]

「id」是图片参考的名称,图片参考的定义方式则和连结参考一样:

[id]: url/to/image  "Optional title attribute"

到目前为止, Markdown 还没有办法指定图片的宽高,如果你需要的话,你可以使用普通的 <img> 标签。


<m id="miscellaneous">其它</m>

<m id="backslash">反斜杠</m>

Markdown 可以利用反斜杠来插入一些在语法中有其它意义的符号,例如:如果你想要用星号加在文字旁边的方式来做出强调效果(但不用 <em> 标签),你可以在星号的前面加上反斜杠:

*literal asterisks*

Markdown 支持以下这些符号前面加上反斜杠来帮助插入普通的符号:

   反斜线
`   反引号
*   星号
_   底线
{}  花括号
[]  方括号
()  括弧
#   井字号
+   加号
-   减号
.   英文句点
!   惊叹号

<m id="automaticLinks">自动链接</m>

Markdown 支持以比较简短的自动链接形式来处理网址和电子邮件信箱,只要是用尖括号包起来, Markdown 就会自动把它转成链接。一般网址的链接文字就和链接地址一样,例如:

<http://example.com/>

Markdown 会转为:

<a href="http://example.com/">http://example.com/</a>

邮址的自动链接也很类似,只是 Markdown 会先做一个编码转换的过程,把文字字符转成 16 进位码的 HTML 实体,这样的格式可以糊弄一些不好的邮址收集机器人,例如:

<address@example.com>

Markdown 会转成:

<a href="&#x6D;&#x61;i&#x6C;&#x74;&#x6F;:&#x61;&#x64;&#x64;&#x72;&#x65;
&#115;&#115;&#64;&#101;&#120;&#x61;&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;
&#109;">&#x61;&#x64;&#x64;&#x72;&#x65;&#115;&#115;&#64;&#101;&#120;&#x61;
&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;&#109;</a>

在浏览器里面,这段字串(其实是 <a href="mailto:address@example.com">address@example.com</a>)会变成一个可以点击的「address@example.com」链接。

(这种作法虽然可以糊弄不少的机器人,但并不能全部挡下来,不过总比什么都不做好些。不管怎样,公开你的信箱终究会引来广告信件的。)

<m id="tools">工具</m>

Windows 平台

Linux 平台

Mac 平台

在线编辑器

浏览器插件

高级应用

离线文档工具Zeal

推荐一个开源的离线文档工具Zeal:

Zeal是一款轻量的离线文档工具,可运行在windows或者linux上,灵感源于OSX上文档工具Dash

  • Zeal可以通过快捷键(默认alt+space)快速召唤
  • 可以同时从多个文档中搜索
  • 不依赖网络连接
  • 可以整合到Emacs, Sublime Text, Vim等编辑器中

Zeal官网

PHP的Curl类

经常会用到PHP cURL 模拟一些http请求,每次都写一大段很不方便,

所以在github上找到一个PHP Curl类,https://github.com/php-curl-class/php-curl-class

文档地址:http://www.crarun.com/docs/curl/curl.html

Quick Start and Examples

require 'Curl.php';
use CurlCurl;
 
//GET请求
$curl = new Curl();
$curl->get('
 
//POST请求
$curl = new Curl();
$curl->post('http://www.example.com/login/', array(
    'username' => 'myusername',
    'password' => 'mypassword',));
 
//带参数
$curl = new Curl();
$curl->setBasicAuthentication('username', 'password');
$curl->setUserAgent('');$curl->setReferrer('');
$curl->setHeader('X-Requested-With', 'XMLHttpRequest');
$curl->setCookie('key', 'value');
$curl->get(' 
if ($curl->error) {
    echo 'Error: ' . $curl->error_code . ': ' . $curl->error_message;
}else {
    echo $curl->response;
}
var_dump($curl->request_headers);
var_dump($curl->response_headers);

在Yii中使用Captcha验证码

在Yii自带demo里,site/contact中使用了验证码,用法如下

Controller

在SiteController加入映射动作CCaptchaAction,映射到SiteController的目的是获取验证码图片,以及表单校验都会用到这个action的类,因为Yii的验证码的session_key是根据controller生成的(也就是说每个controller的验证码的session_key都不同),所以验证码的生成与验证必须在同一个controller中进行。

    /**
     * Declares class-based actions.
     */
    public function actions()
    {
        return array(
            // captcha action renders the CAPTCHA image displayed on the contact page
            'captcha'=>array(
                'class'=>'CCaptchaAction',
                'backColor'=>0xFFFFFF,
            ),
            // page action renders "static" pages stored under 'protected/views/site/pages'
            // They can be accessed via: index.php?r=site/page&view=FileName
            'page'=>array(
                'class'=>'CViewAction',
            ),
        );
    }
     
    /**
     * Displays the contact page
     */
    public function actionContact()
    {
        $model=new ContactForm;
        if(isset($_POST['ContactForm']))
        {
            $model->attributes=$_POST['ContactForm'];
            if($model->validate())
            {
                $name='=?UTF-8?B?'.base64_encode($model->name).'?=';
                $subject='=?UTF-8?B?'.base64_encode($model->subject).'?=';
                $headers="From: $name <{$model->email}>

".
                    "Reply-To: {$model->email}

".
                    "MIME-Version: 1.0

".
                    "Content-Type: text/plain; charset=UTF-8";
 
                mail(Yii::app()->params['adminEmail'],$subject,$model->body,$headers);
                Yii::app()->user->setFlash('contact','Thank you for contacting us. We will respond to you as soon as possible.');
                $this->refresh();
            }
        }
        $this->render('contact',array('model'=>$model));
    }

Model

将验证码属性添加到model里,并设置验证方法,这样调用model的验证方法时将自动验证验证码是否正确

    //验证码
    public $verifyCode;
 
    /**
     * 定义验证规则
     */
    public function rules()
    {
        return array(
            // name, email, subject and body are required
            array('name, email, subject, body', 'required'),
            ...
            //验证码验证规则
            array('verifyCode', 'captcha', 'allowEmpty'=>!CCaptcha::checkRequirements()),
        );
    }

View

上面两个步骤完成后,就可以在view里加载小部件了

<div class="captcha_box">    
    <?php $this->widget('CCaptcha'); ?>
    <?php echo $form->textField($model,'verifyCode'); ?>
</div>

这样就可以在提交表单的时候使用验证码了

Yii的CSRF验证

在Yii框架中,为了防止csrf攻击,封装了CSRF令牌验证。
只需要在主配置文件中进行简单的配置,就可以实现CSRF的验证。

    'components'=>array(
        'request'=>array(
            // Enable Yii Validate CSRF Token
            'enableCsrfValidation' => true,
        ),
    ),

将enableCsrfValidation设置为true了之后,使用Yii表单生成页面的时候,如果表单的提交方式为POST,是都会在页面中添加一个隐藏字段

<div style="display:none">
    <input type="hidden" value="a429b6c0f4468db23a5661d1682db537fe2672c7" name="YII_CSRF_TOKEN" />
</div>

自己写的表单需要手动添加隐藏字段

<input type="hidden" value="<?php echo Yii::app()->getRequest()->getCsrfToken(); ?>" name="YII_CSRF_TOKEN" />

用户在提交表单的同时,将该字段提交给服务器端,Yii框架会将该有客户端提交过来的隐藏字段和客户端提交过来的Cookie中的YII_CSRF_TOKEN值进行比较。相同则通过继续执行,不相同则会抛出400异常:"The CSRF token could not be verified."。

上面的方法是将客户端提交过来的值和客户端的Cookie中的值进行比较,并不是最为安全的方法。

目前更为安全的方式,是将客户端提交过来的值和Session中的值进行比较,这就需要重写CHttpRequest类了。具体步骤如下:

  1. 重写CHttpRequest:
    创建一个类HttpRequest继承于CHttpRequest,并将该类存放在 protected/components 下。

重写CHttpRequest的 getCsrfToken() 和 validateCsrfToken($event) 方法。

private $_csrfToken;
// 
public function getCsrfToken()
{
    if($this->_csrfToken===null)
    {
        $session = Yii::app()->session;
        $csrfToken=$session->itemAt($this->csrfTokenName);
        if($csrfToken===null)
        {
            $csrfToken = sha1(uniqid(mt_rand(),true));
            $session->add($this->csrfTokenName, $csrfToken);
        }
        $this->_csrfToken = $csrfToken;
    }
  
    return $this->_csrfToken;
}
// 
public function validateCsrfToken($event)
{
    if($this->getIsPostRequest())
    {
        // only validate POST requests
        $session=Yii::app()->session;
        if($session->contains($this->csrfTokenName) && isset($_POST[$this->csrfTokenName]))
        {
            $tokenFromSession=$session->itemAt($this->csrfTokenName);
            $tokenFromPost=$_POST[$this->csrfTokenName];
            $valid=$tokenFromSession===$tokenFromPost;
        }
        else
            $valid=false;
        if(!$valid)
            throw new CHttpException(400,Yii::t('yii','The CSRF token could not be verified.'));
    }
}
  1. 修改配置文件main.php:
    'components' => array(
        'request' => array(
            'class' => 'application.components.HttpRequest',
            'enableCsrfValidation' => true,
        ),
    ),

开启Nginx的gzip压缩

gzip(GNU-ZIP)是一种压缩技术。经过gzip压缩后页面大小可以变为原来的30%甚至更小,这样,用户浏览页面的时候速度会块得多。gzip的压缩页面需要浏览器和服务器双方都支持,实际上就是服务器端压缩,传到浏览器后浏览器解压并解析。浏览器那里不需要我们担心,因为目前的巨大多数浏览器都支持解析gzip过的页面。

将代码放置到nginx.conf的http块中:

    gzip on;    
 
    gzip_vary on;
    gzip_disable "msie6";
    gzip_proxied any;
    gzip_comp_level 8;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

配置

gzip on;

该指令用于开启或关闭gzip模块(on/off)

gzip_min_length 1k;

/设置允许压缩的页面最小字节数,页面字节数从header头得content-length中进行获取。默认值是0,不管页面多大都压缩。建议设置成大于1k的字节数,小于1k可能会越压越大。

gzip_buffers 4 16k;

设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。4 16k代表以16k为单位,安装原始数据大小以16k为单位的4倍申请内存。

gzip_http_version 1.1;

识别http的协议版本(1.0/1.1)

gzip_comp_level 8;

gzip压缩比,1压缩比最小处理速度最快,9压缩比最大但处理速度最慢(传输快但比较消耗cpu)

gzip_types text/plain application/x-javascript text/css application/xml

匹配mime类型进行压缩,无论是否指定,”text/html”类型总是会被压缩的。

gzip_vary on;

和http头有关系,加个vary头,给代理服务器用的,有的浏览器支持压缩,有的不支持,所以避免浪费不支持的也压缩,所以根据客户端的HTTP头来判断,是否需要压缩。

tagcloud的使用

tagcloud一共两个文件

  1. tagcloud.swf:是标签云显示的主flash程序
  2. swfobject.js: flash的控制程序
    下载地址:[[csdn]][1]

有两种使用方式,第一种是通过在网页上使用函数实时生成标签云的标签;第二种是通过将标签写入xml文件,flash读取xml文件显示标签云

基础

参考Demo程序3DTagCloudWithoutXML,在网页文件里的<head>标签中加入swfobject.js的引用

<head>
<title>3D Tag Cloud without XML example</title>
<meta http-equiv="Content-Type" content="text/html" />
<!-- SWFObject embed by Geoff Stearns geoff@deconcept.com http://blog.deconcept.com/swfobject/ -->
<script type="text/javascript" src="swfobject.js"></script>
<style type="text/css">
    body { background-color: #eee; padding: 20px; }
</style>
</head>

方式一

在网页<body>标签中插入以下代码

<body>
    <div id="flashcontent">This will be shown to users with no Flash or Javascript.</div>
    <script type="text/javascript">

              var so = new SWFObject("tagcloud.swf", "tagcloud", "600", "400", "7", "#ffffff");

              // uncomment next line to enable transparency

              so.addParam("wmode", "transparent");

              so.addVariable("tcolor", "0x333333");

              so.addVariable("mode", "tags");

              so.addVariable("distr", "true");

              so.addVariable("tspeed", "100");

              so.addVariable("tagcloud", "<tags><a  href='http://www.google.com' style='22' color='0xff0000'  hicolor='0x00cc00'>Google</a><a href='http://www.baidu.com'  style='12'>Baidu</a><a href='http://www.sina.com.cn'  style='16'>Sina</a><a href='http://www.apple.com.cn'  style='14'>Apple</a><a href='http://wsi.gucas.ac.cn'  style='12'>WSI</a><a href='http://www.bit.edu.cn'  style='12'>BIT</a><a href='http://www.sony.com.cn'  style='9'>SONY</a><a href='http://www.gucas.ac.cn'  style='10'>GUCAS</a><a href='http://www.sohu.com.cn'  style='10'>Sohu</a><a href='http://www.renren.com'  style='12'>renren</a><a href='http://www.qq.com'  style='12'>QQ</a></tags>");

              so.write("flashcontent");

    </script>
</body>

方式二

在网页<body>标签中插入以下代码

<body>
  <div id="flashcontent">This will be shown to users with no Flash or Javascript.</div>
  <script type="text/javascript">
           var so = new SWFObject("tagcloud.swf", "tagcloud", "600", "400", "7", "#ffffff");
           // uncomment next line to enable transparency
           so.addParam("wmode", "transparent");
           so.addVariable("tcolor", "0x333333");
           so.addVariable("tcolor2", "0x009900");
           so.addVariable("hicolor", "0x000000");
           so.addVariable("tspeed", "100");
           so.addVariable("distr", "true");
           so.addVariable("xmlpath", "tagcloud.xml");
           so.write("flashcontent");
  </script>
 </body>

分页功能的实现

分页功能的实现需要三个基本要素:

pageCount(总页数),

currentPage(当前页码),

maxButtons(最大显示按钮数量)

根据这三个基本要素计算出要显示的页码范围,要求页码范围是有效的取值(假如页码从0开始计数,那页码范围就是区间[0,pageCount-1]的子集),php实现如下:


/**
 * 计算页码的范围(页码从0开始)
 */
protected function getPageRange()
{
    $currentPage=$this->getCurrentPage();
    $pageCount=$this->getPageCount();

    //先计算出有效的起点
    $beginPage=max(0, $currentPage-(int)($this->max_button_number/2));
    //再依据有效起点和最大显示按钮数量算出终点
    $endPage=$beginPage+$this->max_button_number-1;
    //验证终点的有效性
    if($endPage>=$pageCount)
    {
        //如果右边终点无效,则重新获取有效的终点
        $endPage=$pageCount-1;
        //根据有效终点和最大显示按钮算出有效起点
        $beginPage=max(0,$endPage-$this->max_button_number+1);
    }
    return array(
        'beginPage'=>$beginPage,
        'endPage'=>$endP
    );
}

这样就能保证maxButtons有效性。

上一页/下一页的显示逻辑:如果当前页不是第一页就显示上一页按钮,如果当前页不是最后一页就显示下一页按钮

升级UEditor的SyntaxHighlighter

博客文本编辑器使用的是UEditor1.4.3,和Bootstrap还有不兼容,语法高亮效果也很差,于是看了下自带SyntaxHighlighter的版本,竟然是1.5.1的,而且js文件有160kb。官网SyntaxHighlighter最新版本已经更新到3.0.83了,于是准备给UEditor的SyntaxHighlighter升下级。

升级SynTaxHighlighter

一、下载

  • 首先下载SyntaxHighlighter最新版,解压。
  • 将scripts目录下的js压缩成一个文件(注意:shCore.js这个核心文件要放在最前面),我这里压缩为shCore.min.js。
  • styles目录中的文件包含基本样式shCore.css,主题基本样式shCoreXX.css以及其对应主题样式shThemeXX.css,我把每个主题与基本样式压缩成一个主题样式shCoreXX.min.css,8个主题,这里生成了8个文件。

二、修改UEditor

  • 将上面生成的9个文件复制到UEditor目录下third-party/SyntaxHighlighter/目录中,并修改UEditor目录下的 ueditor.parse.js 文件,将 sh_js属性 设置为 shCore.min.js 增加语法高亮设置:
    /*!
     * UEditor
     * version: ueditor
     * build: Thu May 29 2014 16:47:49 GMT+0800 (中国标准时间)
     */
     
    (function(){
        (function(){
        UE = window.UE || {};
        //语法高亮设置
        UE.sh_config = {
            sh_js : "shCore.min.js",
            sh_theme : "Default"
        };
        var isIE = !!window.ActiveXObject;
        //定义utils工具
       var utils = {
  • 并修改加载SyntaxHighlighter的文件名:
UE.parse.register('insertcode',function(utils){
    var pres = this.root.getElementsByTagName('pre');
    if(pres.length){
        if(typeof XRegExp == "undefined"){
            var jsurl,cssurl;
            if(this.rootPath !== undefined){
                jsurl = utils.removeLastbs(this.rootPath)  + '/third-party/SyntaxHighlighter/'+UE.sh_config.sh_js;
                cssurl = utils.removeLastbs(this.rootPath) + '/third-party/SyntaxHighlighter/shCore'+UE.sh_config.sh_theme+'.min.css';
            }else{
                jsurl = this.highlightJsUrl;
                cssurl = this.highlightCssUrl;
            }

然后就修改完成啦,再将文件压缩一下就可以了。

三、使用方法

依然保持UEditor原来的使用方法,编辑内容展示,增加了两个可选配置,语法解析文件和高亮主题:

<script>
    UE.sh_config.sh_js="shCore.min.js";    //语法解析文件
    UE.sh_config.sh_theme="Default";    //语法高亮主题
    uParse(".article-content",{rootPath: "/js/ueditor"});
</script>

一共有8种主题可选:Default,Django,Eclipse,Emacs,FadeToGrey,MDUltra,Midnight,RDark

效果

Midnight:

1408548337165875.jpg

RDark:

1408548337540451.jpg

Default:

1408548339694916.jpg

UEditor入门

UEditor

UEditor 是由百度「FEX前端研发团队」开发的所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验等特点,开源基于MIT协议,允许自由使用和修改代码。

1 入门部署和体验

1.1 下载编辑器

到官网下载 UEditor 最新版:UEditor官方地址

1.2 创建demo文件

解压下载的包,在解压后的目录创建 demo.html 文件,填入下面的html代码

<!DOCTYPE HTML><html lang="en-US"><head>
<meta charset="UTF-8">
<title>ueditor demo</title></head><body>
<!-- 加载编辑器的容器 -->
<script id="container" name="content" type="text/plain">
    这里写你的初始化内容
</script>
<!-- 配置文件 -->
<script type="text/javascript" src="ueditor.config.js"></script>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="ueditor.all.js"></script>
<!-- 实例化编辑器 -->
<script type="text/javascript">
    var ue = UE.getEditor('container');
</script></body></html>

1.3 在浏览器打开demo.html

如果看到了下面这样的编辑器,恭喜你,初次部署成功!

请输入图片描述

1.4 传入自定义的参数

编辑器有很多可自定义的参数项,在实例化的时候可以传入给编辑器:

var ue = UE.getEditor('container', {
autoHeight: false});

配置项也可以通过 ueditor.config.js 文件修改,具体的配置方法请看前端配置项说明

1.5 设置和读取编辑器的内容

通过 getContent 和 setContent 方法可以设置和读取编辑器的内容

var ue = UE.getContent();//对编辑器的操作最好在编辑器ready之后再做ue.ready(function() {
//设置编辑器的内容
ue.setContent('hello');
//获取html内容,返回: <p>hello</p>
var html = ue.getContent();
//获取纯文本内容,返回: hello
var txt = ue.getContentTxt();});

UEditor 的更多API请看API文档

2 详细文档

UEditor 官网:http://ueditor.baidu.com

UEditor API 文档:http://ueditor.baidu.com/doc

UEditor Github 地址:http://github.com/fex-team/ueditor

Nginx源码安装

一 . 依赖工具

1)gcc

说明:gcc是一个编译工具,Nginx是c语言写的所以需要一个编译工具

安装方法:

yum install gcc

2)PCRE 库

说明:这个库是用于支持Nginx正则表达式语法

安装方法:

yum install pcre pcre-devel

3)zlib 库

说明:用于支持Nginx gzip压缩

安装方法:

yum install zlib zlib-devel

4)OpenSSL

说明:用于支持Nginx 的安全连接

安装方法:

yum install openssl openssl-devel

二 . Nginx安装

配置:

./configure

编译:

make

安装:

make install