之前使用dispatch, 但是从4.0到现在的8.0 API变动比较大,特别是在最近两次大的版本的升级,为了保持代码简洁丢失了向下兼容的特性。
感觉作者的的核心思想不是很坚定。所以生出了自己造轮子的冲动。
router.lua
这个是一个微型的可以在openresty里面运行的路由控制器,曾经帮作者做了一次重大改版,现在还是这个项目第二贡献者。
其中的思想是很值得借鉴的:
使用树形结构来保存url和handler的映射关系。(按照树形结构查找保证了查找回调函数的效率Olog(n),而传统的以正则表达式做key映射handler方式,查找回调函数时间不稳定,最坏情况需要执行一遍所有的正则表达式)
将reqest method定义成是match函数的一个封装形式。便于提供方便的形式来映射路由。
Router
鉴于以上两个非常好的特性,所以就把这个lua的库在PHP下面重写了一遍。同时在写的过程中加入了一些新的特性:
增加error这个API,一个API提供两种调用方式(这个借鉴了dispatch里面的一些特性,有点像jquery的某些方法),可以兼具定义error handler和触发error handler的作用。
增加hook API,同样有两种调用方式。
默认触发“before”和“after”两个hook。分别在执行真正的handler前后。
在“before”这个hook后面执行用户自定义的hook,这些hook是在定义回调函数的时候一起给定当前url需要调用的hook列表。(当然这些hook全部都要用户自己定义回调函数)
安装
这个微型的路由控制器已经提交到packagist网站,可以通过composer工具安装
composer require lloydzhou/router
此处附README里面的一个例子:
(new Router())
/* 定义错误处理函数 */
->error(401, function($message){
header('Location: /login', true, 302);
die($message);
})
->error(405, function($message){
header('Location: /hello/world', true, 302);
})
->error(406, function($message){
die($message);
})
/* 定义hook函数,除了内置默认调用的before和after,还定义了检查登录的auth */
->hook('auth', function($params){
if ('lloyd' == $params['name'])
return $params;
$params['router']->error(401, 'Forbiden');
})
/* 定义after这个钩子函数,支持json或者jsonp格式输出 */
->hook('after', function($result, $router){
if ($result) {
header('Content-type: application/'. ($_GET['jsoncallback']?'javascript':'json'));
if ($_GET['jsoncallback'])
print $_GET['jsoncallback']. '('. json_encode($result). ')';
else print json_encode($result);
}
})
->hook('before', function($params){
//$params['name'] = 'lloydzhou';
return $params;
})
/* 定义url映射 */
->get('/', function(){
echo "Hello world !!!";
})
->get('/hello/:name', function($name){
echo "Hello $name !!!";
})
->get('/hello/:name/again', function($name){
echo "Hello $name again !!!";
}, 'auth')
->get('/hello/:name.:ext', function($name, $ext){
if ('js' == $ext || 'json' == $ext) return array('name'=>$name);
return array('code'=>1, 'msg'=>'error message...');
}, 'auth')
/* 程序入口,以当前的url查找对应的处理函数,并获取变量执行该函数 */
->execute();
启动服务
php -S 0.0.0.0:8888 test.php
测试
curl -vvv 127.0.0.1:8888/hello/
url未能映射成功,触发405错误处理函数, 自动跳转向 URL: "/hello/world"
curl -vvv 127.0.0.1:8888/hello/lloyd
返回 "Hello lloyd !!!"
curl -vvv 127.0.0.1:8888/hello/lloyd/again
返回 "Hello lloyd again !!!"
curl -vvv 127.0.0.1:8888/hello/world/again
在钩子函数auth处理失败触发401错误处理函数, 自动跳转到 URL: "/login"
curl -vvv 127.0.0.1:8888/hello/lloyd.json
支持“/”和“.”作为pathinfo的分隔符,并且和after钩子函数配合,返回json格式文本 {"name": "lloyd"}
curl -vvv 127.0.0.1:8888/hello/lloyd.js?jsoncallback=test
返回jsonp格式文本 test({"name": "lloyd"})
curl -vvv 127.0.0.1:8888/hello/lloyd.jsx?jsoncallback=test
最后的后缀名不匹配,输出错误jsonp格式的消息 test({"code":1,"msg":"error message..."})
编译
开发环境使用CRouter替代Router可以自动检测文件修改时间,并且编译成原生数组保存至router.inc.php
(new CRouter('router.inc.php', true))
发布的时候只需要把路由映射的部分替换成以下两行代码即可跳过创建路由映射表的阶段。以节省时间!!!
$router = include('router.inc.php');
$router->execute();
性能
使用树形结构来保存url和handler的映射关系。查找URL映射函数的时候保证了查找回调函数的效率O(log n)。
而传统的以正则表达式做key映射handler方式,查找回调函数时间不稳定,最坏情况需要执行一遍所有的正则表达式。
支持编译,将映射好的树形路由映射数组直接编译成源码。不需要每次PHP请求的时候重新切割pathinfo再来生成树形节点。以节省时间!
DEMO
为了一边测试,一边完善这个库。所以使用这个库结合另外一个ActiveRecord和MicroTpl 写了一个简单的博客,里面基本涵盖了这几个库的API。