之前在跟随@岁寒大佬的《利用 Composer 一步一步构建自己的 PHP 框架》 搭建了自己的 DoyleafPHP,路由使用的是GayHub上route关键词PHP语言下MostStars第一的nikic/fast-route,上一张现在的图
![](https://blog.doylee.cn/wp-content/uploads/2018/05/QQ图片20180525153026-300x207.png)
安装方式
通过composer安装:
composer require nikic/fast-route
需要PHP 5.4及更高版本.
使用方法
快速构造
<?php
// 引入composer自动加载文件
require '/path/to/vendor/autoload.php';
// 声明路由规则*
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/users', 'get_all_users_handler');
// {id} must be a number (\d+)
$r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler');
// The /{title} suffix is optional
$r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler');
});
// 获取http请求方式和资源URI
$httpMethod = $_SERVER['REQUEST_METHOD'];
$uri = $_SERVER['REQUEST_URI'];
// 将url中的get传参方式(?foo=bar)剥离并对URI进行解析
if (false !== $pos = strpos($uri, '?')) {
$uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
// 根据调度器进行匹配
switch ($routeInfo[0]) {
// 使用未定义路由格式
case FastRoute\Dispatcher::NOT_FOUND:
// ... 404 Not Found
break;
// 请求的HTTP⽅法与配置的不符合
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$allowedMethods = $routeInfo[1];
// ... 405 Method Not Allowed
break;
// 调用$handler和$vars*
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
// ... call $handler with $vars
break;
}
定义路由
通过调用 FastRoute\simpleDispatcher() 函数来定义路由,参数为可调用的FastRoute\RouteCollector实例。
通过在收集器实例上调用addRoute()来添加路由:
$r->addRoute($method, $routePattern, $handler);
$method参数是该条路由匹配的一个大写的HTTP方法字符串。可以使用数组指定多个有效方法:
// 分两条调用
$r->addRoute('GET', '/test', 'handler');
$r->addRoute('POST', '/test', 'handler');
// 也可以一次性调用
$r->addRoute(['GET', 'POST'], '/test', 'handler');
$routePattern参数默认使用以下语法:{foo} 指定的名称为foo的占位符,与正则表达式[^/]+匹配。若要调整占位符正则匹配的模式,可以通过编写{bar:[0-9]+}来指定自定义模式。举个例子:
// 允许匹配/user/42, 不匹配/user/xyz
$r->addRoute('GET', '/user/{id:\d+}', 'handler');
// 允许匹配/user/foobar,不匹配/user/foo/bar
$r->addRoute('GET', '/user/{name}', 'handler');
// 允许匹配/user/foo/bar
$r->addRoute('GET', '/user/{name:.+}', 'handler');
路由占位符的自定义模式不能使用捕获组。例如 {lang:(en|de)} 就不是一个有效的占位符,因为() 是一个捕获组。 但是您可以使用 {lang:en|de} 或{lang:(?:en|de)}。
(这里的捕获组我理解的就是可选项,只能是这二者之一的意思)
此外,可以使用[…]来表示可选路由,因此 /foo[bar]既能匹配 /foo,也能匹配/foobar。 可选部件仅可以出现在末尾位置,而不能出现在路径中间。
// 使用可选语法的路由
$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler');
// 可以相当于下列两条同时存在
$r->addRoute('GET', '/user/{id:\d+}', 'handler');
$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler');
// 多个嵌套可选部件也是可用的
$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler');
// 这条路由就是错误的,因为可选部分必须出现在末尾
$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler');
$handler 参数不一定非要是回调函数,它可以是一个控制器的类名,或者其他任何你想要与路由相关联的类型的数据。FastRoute只告诉你你的URI对应哪个处理程序,你如何解释它取决于你。
常用请求的快捷写法
对于 GET, POST, PUT, PATCH, DELETE 和HEAD 方式的请求,我们提供了对应的快捷写法。举个例子:
$r->get('/get-route', 'get_handler');
$r->post('/post-route', 'post_handler');
这两条路由就相当于:
$r->addRoute('GET', '/get-route', 'get_handler');
$r->addRoute('POST', '/post-route', 'post_handler');
路由组
另外,您可以使用路由组来指定路由。 组内定义的所有路由都会拥有一个通用前缀。
举个例子,你可以像这样定义你的多条路由:
$r->addGroup('/admin', function (RouteCollector $r) {
$r->addRoute('GET', '/do-something', 'handler');
$r->addRoute('GET', '/do-another-thing', 'handler');
$r->addRoute('GET', '/do-something-else', 'handler');
});
我们得到的结果就相当于:
$r->addRoute('GET', '/admin/do-something', 'handler');
$r->addRoute('GET', '/admin/do-another-thing', 'handler');
$r->addRoute('GET', '/admin/do-something-else', 'handler');
也可以对组进行嵌套,所有被嵌套的组的前缀都会被合并到一起。
缓存
我们支持无缝缓存,所以simpleDispatcher可以通过回调来定义路由。 使用cachedDispatcher来替换simpleDispatcher,您可以将生成的路由数据进行缓存,以从缓存数据中构建调度程序:
<?php
$dispatcher = FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
$r->addRoute('GET', '/user/{name}', 'handler2');
}, [
'cacheFile' => __DIR__ . '/route.cache', /* required */
'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */
]);
方法的第二部分是一个可选数组,可用于指定缓存文件的位置等。
对URI进行调度
URI 通过已生成的 dispatcher
中的 dispatch()
方法来调用。这个方法可以用来接收 HTTP 参数和 URI。这个库不会绑定到 PHP Web SAPI 上,所以您可以自行获取这两部分信息(并适当地标准化)。
dispatch()
方法返回一个数组,这个数组的第一个元素包含一个状态码。这个状态码只能是 Dispatcher::NOT_FOUND
, Dispatcher::METHOD_NOT_ALLOWED
或Dispatcher::FOUND
。 对于 Method Not Allowed 状态,返回数组的第二个元素是一个列表,这个列表包含了本 URI 可用的 HTTP 请求方式。
像这样:
[FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']]
说明:HTTP规范要求405 Method Not Allowed 响应包含“Allow:”头,用以详细说明所请求资源的可用方法。 使用 FastRoute 的应用程序在返回 405 响应时,应使用数组的第二个元素添加此标头。
对于得到的这个状态码(即 405),第二个数组元素是与路由关联的句柄,第三个数组元素是一个键值对应的占位符的字典。
就像这样:
/* 路由拒绝了/user/nikic/42的GET请求 */
[FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']]
重写路由解析器和调度器
这个库使用三个组件,一个路由解析器,一个数据生成器,一个调度器。这个三个组件由以下接口实现:
<?php
namespace FastRoute;
interface RouteParser {
public function parse($route);
}
interface DataGenerator {
public function addRoute($httpMethod, $routeData, $handler);
public function getData();
}
interface Dispatcher {
const NOT_FOUND = 0, FOUND = 1, METHOD_NOT_ALLOWED = 2;
public function dispatch($httpMethod, $uri);
}
路由解析器获取路由模式字符串并将其转换为路由信息数组,其中每个路线信息又是它的部分数组。
/* 本路由/user/{id:\d+}[/{name}]被转化为下述数组: */
[
[
'/user/',
['id', '\d+'],
],
[
'/user/',
['id', '\d+'],
'/',
['name', '[^/]+'],
],
]
然后可以将该数组传递给数据生成器的 addRoute()
方法,在添加了所有路由之后,调用生成器的 getData()
,它将返回调度器所需的所有路由数据。
调度程序通过构造函数接受路由数据,并提供 dispatch()
方法。
路由解析器可以被单独覆盖,然而数据生成器和调度器应该总是一起修改,因为前者的输出与后者的输入紧密耦合。
当使用 simpleDispatcher
/ cachedDispatcher
时,可以通过传入额外的参数,进行覆盖
<?php
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
/* ... */
}, [
'routeParser' => 'FastRoute\\RouteParser\\Std',
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
]);
上面给出了默认的设置,通过把 GroupCountBased 替换成 GroupPosBased 可以使用完全不同的调度策略
关于HEAD请求的说明
HTTP 规范要求服务器 同时支持 GET 和 HEAD 方法
GET和HEAD方法必须得到所有通用服务器的支持
为避免强制用户为每个资源手动注册 HEAD 路由,将使用一个匹配的 GET 路由响应请求。PHP web SAPI 透明地从 HEAD 响应中移除实体主体,所以这种行为对绝大多数用户没有影响。
但是,在 Web SAPI 环境外部使用 FastRoute ,绝不能发送响应 HEAD 请求而生成的实体主体,如果你是非 SAPI 用户,这是你的责任;在这种情况下,FastRoute 无权限制你破坏 HTTP 。
最后,请注意,应用程序可以始终为给定资源指定其自己的 HEAD 方法路由以完全绕过此行为。
推荐另一篇同期其他大佬翻译的文章