一直想摸一个自己的PHP框架出来,无奈技术力不足。听前辈说看源码能快速提升码力,于是去翻了一些主流框架的源码开始啃。对着优雅的Laravel,十年磨一剑的TP看了一上午,压根毛都没看懂,自身实力还是太薄弱了,妄想一步登天是不可能滴,于是就想着先把基本的思想搞明白,试着解析一些简单的路由框架试试看。这不,Macaw就蹦出来了。
Macaw is a simple, open source PHP router. It's super small (~150 LOC), fast, and has some great annotated source code. This class allows you to just throw it into your project and start using it immediately.
Macaw是一个简单,开源的PHP路由器。它炒鸡小(大概150行代码),炒鸡快还有着精心注释过的源码。这个类允许你直接扔到你的项目里,开箱即用~
Oh,这太棒了。还有什么比这个更能快速上手路由器吗?这就开始!
Rewrite
首先看一下Macaw的重写规则,与一般的框架一样,将所有非静态资源全部请求至index.php。有了这个地址重写后就方便多了。
#302重定向,保留地址栏
rewrite ^/(.*)/$ /$1 redirect;
#判断是否为非静态资源
if (!-e $request_filename){
#若为非静态资源则指向入口文件Index.php并停止之后的重写
rewrite ^(.*)$ /index.php break;
}
用法
再来简单看看Macaw的使用方法,从Usage开始,顺瓜摸藤。
use NoahBuscher\Macaw\Macaw;
//普通的get请求
Macaw::get('/', function() {
echo 'JOJO思密达';
});
//普通的post请求
Macaw::post('/', function() {
echo '就用这个砍下承太郎的脑袋';
});
//当然还有delete等等
//使用lambda URIs
Macaw::get('/(:num)', function($id){
echo "id is ".$id;
});
Macaw::get('/(:any)', function($name){
echo "name is ".$name;
});
//重头戏
Macaw::get('page', 'Controllers\demo@page');
Macaw::get('view/(:num)', 'Controllers\demo@view');
//执行路由
Macaw::dispatch();
可以看到Macaw支持各种请求方式,回调函数的调用也很自由。这就让我非常好奇,150行代码就能整出这么个麻雀虽小五脏俱全的路由器来了吗。
代码实现
见识到了Macaw的功能后我迫不及待地打开了源码想要一探究竟。好家伙,果真加上注释就174行代码。在仔细地学习研究之后,我大为震撼。下面开始我粗糙的分析。
类的一开头定义了一些静态的数组与变量与函数。
$routes
用于存储路由路径(/view/(:num)
,/page/about
...)$methods
用处储存路由访问方式(get
,post
...)$callbacks
用于存储回调函数,其中有的为字符串,有的则为直接的函数$patterns
用于储存正则规则$error_callback
用于存储发生错误时要调用的回调函数- 其他俩
$halts
和$maps
暂时不知道是要干啥的
而$routes
,$callbacks
,$methods
的下标是一一对应的。非常巧妙的存储方式。
public static $halts = false;
public static $routes = array();
public static $methods = array();
public static $callbacks = array();
public static $maps = array();
public static $patterns = array(
':any' => '[^/]+',
':num' => '[0-9]+',
':all' => '.*'
);
public static $error_callback;
接下来精彩的地方来了。我们不难发现,我们定义路由规则时,不同的规则调用的不同的函数。例如注册get
请求使用的是Macaw::get()
方法,而注册post
请求时使用的是Macaw::post()
方法。虽然我们用的是不同的函数,但是要执行的功能都是一致的,即将两个参数(路由路径和回调函数)压入上面的$routes
和$callbacks
里面,再将对应的请求方法压入$methods
里头。
可是HTTP有那么多请求方式,难道要一个个写出来吗?No,那太noob了。作者告诉了我们一个更加聪明的办法。没错,就是__callstatic()
魔术方法。这类似与C++中的重载,而PHP所提供的重载(overloading)是指动态地创建类属性和方法。更加详细的解释可以看PHP: 重载 - Manual,我这里简单介绍一下。就是当你调用了一个本不存在的静态函数时(假如get方法),会调用到这个函数来动态创建一个名为get
的静态函数并执行。而参数则为那个动态创建的函数名get
和原本传入参数。
public static function __callStatic($method,$arg){
echo '你试图调用'.$method.'方法,可惜它不存在';
echo '你还穿了个参数';
var_dump($arg);
}
有了这个,我们就可以很轻松地将多个HTTP请求方式写进一个函数里头
/**
* Defines a route w/ callback and method
*/
public static function __callstatic($method, $params) {
if ($method == 'map') {
$maps = array_map('strtoupper', $params[0]);
$uri = strpos($params[1], '/') === 0 ? $params[1] : '/' . $params[1];
$callback = $params[2];
} else {
$maps = null;
$uri = strpos($params[0], '/') === 0 ? $params[0] : '/' . $params[0];
$callback = $params[1];
}
//将自定义的规则压入数组储存
array_push(self::$maps, $maps);
array_push(self::$routes, $uri);
array_push(self::$methods, strtoupper($method));
array_push(self::$callbacks, $callback);
}
最后来看看最为关键的dispatch()
方法。首先是一系列的准备工作,将各类参数抽丝剥茧取出来
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
$searches = array_keys(static::$patterns);
$replaces = array_values(static::$patterns);
$found_route = false;
self::$routes = preg_replace('/\/+/', '/', self::$routes);
$uri
记录了当前访问的路径,例如/user/1/setting
,$method
则顾名思义记录了请求的HTTP方式。同时,还取了$patterns
的键值和值作为$searches
和$replaces
数组。再标记一下是否找到路由规则$found_route
为否。为了方便之后的比对,将$routes
数组中多个/
同时出现的地方替换为单/
。
其次,判断是否能够直接在不考虑正则的情况下在已经注册的$routes
中找到匹配的规则。如果找到了,那么就取出他的下标(们)来遍历。
// Check if route is defined without regex
if (in_array($uri, self::$routes)) {
$route_pos = array_keys(self::$routes, $uri);
foreach ($route_pos as $route) {
// Using an ANY option to match both GET and POST requests
if (self$maps[$route]) && in_array($method, self::$maps[$route]))) {
$found_route = true;
// If route is not an object
if (!is_object(self::$callbacks[$route])) {
// Grab all parts based on a / separator
$parts = explode('/',self::$callbacks[$route]);
// Collect the last index of the array
$last = end($parts);
// Grab the controller name and method call
$segments = explode('@',$last);
// Instanitate controller
$controller = new $segments[0]();
// Call method
$controller->{$segments[1]}();
if (self::$halts) return;
} else {
// Call closure
call_user_func(self::$callbacks[$route]);
if (self::$halts) return;
}
}
}
}
如果当前下标所对应的路由规则的方法与当前请求对应,或是规则中是ANY
任意方法的话,就说明找到了匹配的路由,将$found_route
标记为true
,并开始尝试调用回调函数。如果回调函数数组$callbacks
中对应的值就是一个函数时,直接调用那个就好。如果不是,而是一个字符串时,就分割字符串,实例化对象再调用回调函数。
正则模式下也是大同小异了。这就是Macaw运行的基本流程。
总结
前辈诚不欺我,阅读别人的代码果然可以快速增长自己的码力。Macaw简洁而高效的代码打开了我新世界的大门。这些函数,魔术方法都是学过的,可如何运用于实战,融会贯通却是一门学问。而在这之上,体现的则是设计模式的思维。看来要走的路还有很长啊。
学到了
ohh,是Kiosr
嘿嘿, 我又有时间折腾了
有趣,__callstatic这种方法之前几乎没怎么用过,想不到能这样
PHP的情况果然是入门简单,但是玩精不简单,因为他太方便了。
有时候确实不能一味埋着头写代码,得看看别人的手笔...
这样易懂的学习笔记摩多摩多
oh些瓶,给我摩多摩多