之前在跟随@岁寒大佬的《利用 Composer 一步一步构建自己的 PHP 框架》 搭建了自己的 DoyleafPHP,路由使用的是GayHub上route关键词PHP语言下MostStars第一的nikic/fast-route,上一张现在的图
安装方式
通过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']]
重写路由解析器和调度器(写到这发现网上有中文文档是一种什么体验,CNNN,MMP)
这个库使用三个组件,一个路由解析器,一个数据生成器,一个调度器。这个三个组件由以下接口实现:
<?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 方法路由以完全绕过此行为。