phossa2 / route
适用于PHP的快速且功能齐全的路由库
Requires
- php: ~5.4|~7.0
- phossa2/event: ^2.1.6
- phossa2/shared: ^2.0.27
Requires (Dev)
- phossa2/logger: *
- phpunit/phpunit: 4.*
Replaces
This package is not auto-updated.
Last update: 2020-01-24 16:10:12 UTC
README
请使用 phoole/route 库代替
phossa2/route 是一个 快速、功能齐全 且 特性丰富 的PHP应用级路由库。
它需要PHP 5.4,支持PHP 7.0+ 和 HHVM。它符合 PSR-1、PSR-2、PSR-3、PSR-4,以及建议的 PSR-5。
为什么还需要另一个路由库?
-
超级快速!如果你在意的话。
-
支持不同的 路由策略 和这些策略的组合。
-
支持不同的 正则表达式路由算法,包括 fastRoute 算法
-
简洁的路由语法。路由参数和可选路由段。
-
使用收集器中的 路径前缀检查 进行快速路由。
-
不同级别的 默认处理器。
-
通过 多级扩展 精细控制路由过程。
-
路由和正则表达式 调试。
安装
通过 composer
工具安装。
composer require "phossa2/route"
或者将以下行添加到您的 composer.json
{ "require": { "phossa2/route": "2.*" } }
使用方法
将路由定义(模式、处理器、默认值等)注入分发器,然后调用 match()
或 dispatch()
。
use Phossa2\Route\Route; use Phossa2\Route\Dispatcher; // dispatcher with default collector & resolver $dispatcher = (new Dispatcher()) ->addGet( '/blog/{action:xd}[/{year:d}[/{month:d}[/{date:d}]]]', function($result) { $params = $result->getParameters(); echo "action is " . $params['action']; } )->addPost( '/blog/post', 'handler2' )->addRoute(new Route( 'GET,HEAD', '/blog/read[/{id:d}]', 'handler3', ['id' => '1'] // default values )); // diaptcher (match & execute controller action) $dispatcher->dispatch('GET', '/blog/list/2016/05/01');
或者从数组中加载路由:
$routes = [ '/user/{action:xd}/{id:d}' => [ 'GET,POST', // methods, function ($result) { $params = $result->getParameters(); echo "user id is " . $params['id']; }, // handler, ['id' => 1] // default values ], // ... ]; $dispatcher = (new Dispatcher())->loadRoutes($routes); $dispatcher->dispatch('GET', '/user/view/123456');
路由语法
-
命名参数
路由模式语法中使用
{foo}
指定一个名为foo
的命名参数或占位符,默认正则表达式模式为[^/]++
。为了匹配更具体的类型,您可能需要指定一个自定义的正则表达式模式,例如{foo:[0-9]+}
。// with 'action' & 'id' two named params $dispatcher->addGet('/user/{action:[^0-9/][^/]*}/{id:[0-9]+}', 'handler1');
可以使用以下方式使用预定义的快捷方式作为占位符:
':d}' => ':[0-9]++}', // digit only ':l}' => ':[a-z]++}', // lower case ':u}' => ':[A-Z]++}', // upper case ':a}' => ':[0-9a-zA-Z]++}', // alphanumeric ':c}' => ':[0-9a-zA-Z+_\-\.]++}', // common chars ':nd}' => ':[^0-9/]++}', // not digits ':xd}' => ':[^0-9/][^/]*+}', // no leading digits
前面的模式可以重写为:
// with 'action' & 'id' two named params $dispatcher->addGet('/user/{action:xd}/{id:d}', 'handler1');
-
可选段
可以使用
[]
指定路由模式中的可选段,如下所示:// $action, $year/$month/$date are all optional $pattern = '/blog[/{action:xd}][/{year:d}[/{month:d}[/{date:d}]]]';
其中可选段可以是 嵌套的。与其它库不同,可选段不仅限于模式的末尾,只要是一个有效的模式,就像示例中的
[/{action:xd}]
。 -
语法限制
-
参数名称 必须 以字母开头
由于
{2}
在正则表达式中有特殊含义。参数名称 必须 以字母开头。此外,在占位符内部或外部使用{}
可能会引起混淆,因此不推荐使用。 -
占位符外部的
[]
表示 可选 的段[]
不能作为正则表达式模式的一部分使用,如果您确实需要 使用它们作为正则表达式模式的一部分,请将它们包含在 占位符内部。 -
不允许在占位符内部使用捕获组
()
捕获组
()
不能在占位符内部使用。例如,{user:(root|phossa)}
是无效的。相反,您可以使用{user:root|phossa}
或{user:(?:root|phossa)}
。
-
-
默认值
可以在命名参数的末尾添加默认值,形式为
{action:xd=list}
。默认值必须是字母数字字符。例如,// $action, $year/$month/$date are all optional $pattern = '/blog[/{action:xd=list}][/{year:d=2016}[/{month:d=01}[/{date:d=01}]]]'; $dispatcher->addGet($pattern, function($result) { $params = $result->getParameters(); echo $params['year']; })->dispatch('GET', '/blog');
路由
-
使用分发器定义路由
您可以使用分发器定义路由。但实际上,这是通过分发器中的第一个路由收集器定义路由的。
// a new route collector will be added automatically if not yet $dispatcher = (new Dispatcher())->addPost('/blog/post', 'handler2');
addGet()
和addPost()
是addRoute(RouteInterface)
的包装器。 -
可以使用多个收集器将路由分组到不同的集合中。
use Phossa2\Route\Collector\Collector; // '/user' related $collector_user = (new Collector()) ->addGet('/user/list/{id:d}', 'handler1') ->addGet('/user/view/{id:d}', 'handler2') ->addPost('/user/new', 'handler3'); // '/blog' related $collector_blog = (new Collector()) ->addGet('/blog/list/{user_id:d}', 'handler4') ->addGet('/blog/read/{blog_id:d}', 'handler5'); $dispatcher->addCollector($collector_user) ->addCollector($collector_blog);
-
收集器可以使用
setPathPrefix()
设置路径前缀,以指示处理的确切URI路径前缀。找到任何不匹配的前缀将完全跳过收集器。// '/user/' prefix $collector_user = (new Collector()) ->setPathPrefix('/user/') ->addGet('/user/list/{id:d}', 'handler1') ->addGet('/user/view/{id:d}', 'handler2') ->addPost('/user/new', 'handler3');
-
相同的路由模式
用户可以定义具有不同HTTP方法的相同路由模式。
$dispatcher ->addGet('/user/{$id}', 'handler1') ->addPost('/user/{$id}', 'handler2');
分发
-
使用分发器的
dispatch()
进行分发$dispatcher->dispatch('GET', '/user/view/123');
-
匹配而不是分发
如果使用
match()
方法,则在dispatch()
中默认执行处理程序的情况下,用户可以获得更多控制。if ($dispatcher->match('GET', '/user/view/1234')) { $result = $dispatcher->getResult(); switch($result->getStatus()) { case 200: // ... break; case 404: // ... break; default: // ... break; } } else { // no match found // ... }
处理程序
-
路由处理程序
仅定义了状态
200 OK
的处理程序的路由。use Phossa2\Route\Route; use Phossa2\Route\Status; $route = new Route( 'GET', '/user/{action:xd}/{id:d}', function($result) { // handler for Status::OK // ... } );
-
分发器和收集器可以具有与不同结果状态相对应的多个处理程序。
如果结果没有设置处理程序(例如,没有找到匹配项),则将检索收集器的处理程序(相同状态码)。如果仍然没有成功,如果已定义,则将使用分发器的处理程序(相同状态码)。
分发器级别的处理程序,
use Phossa2\Route\Status; $dispatcher->addHandler( function($result) { echo "method " . $result->getMethod() . " not allowed"; }, Status::METHOD_NOT_ALLOWED );
收集器级别的处理程序,
$collector->addHandler( function($result) { // ... }, Status::MOVED_PERMANENTLY );
当使用状态设置为
0
的addHandler()
时,将导致此处理程序成为其他状态的其他默认处理程序。use Phossa2\Route\Status; $dispatcher->addHandler( function($result) { echo "no other handler found"; }, 0 // <-- match all other status );
-
处理程序解析
大多数情况下,匹配的路由将返回一个类似
[ 'ControllerName', 'actionName' ]
的处理程序。处理程序解析器可以用于将此伪处理程序解析为真实可调用的。use Phossa2\Route\Collector\Collector; use Phossa2\Route\Resolver\ResolverSimple; // dispatcher with default resolver $dispatcher = new Route\Dispatcher( new Collector(), new ResolverSimple() // the default resolver anyway );
用户可以通过实现
Phossa2\Route\Interfaces\ResolverInterface
来编写自己的处理程序解析器。
扩展
扩展是可调用的,处理匹配结果或其他任务,在特定分发阶段的之前或之后。
扩展可以添加到 Dispatcher
、Collector
或甚至 Route
。
-
扩展的使用
扩展 必须 返回一个布尔值,以指示是否继续进行分发过程。
FALSE
表示停止并返回到顶级。扩展可以是
Phossa2\Event\EventableExtensionAbstract
类,通过addExtension()
或addExt()
方法添加,或者是一个符合签名callableName(Phossa2\Event\EventInterface $event): bool
的可调用对象,可以通过addExt(callable, eventName, priority)
方法添加为扩展。use Phossa2\Route\Status; use Phossa2\Route\Dispatcher; use Phossa2\Route\Extensions\RedirectToHttps; // create dispatcher $dispatcher = new Dispatcher(); // direct any HTTP request to HTTPS port before any routing $dispatcher ->addExtension(new RedirectToHttps()) ->addHandler(function() { echo "redirect to https"; }, Status::MOVED_PERMANENTLY) ->dispatch('GET', '/user/view/123');
强制对以 '/user/' 前缀的任何 URL 进行身份验证,
use Phossa2\Route\Status; use Phossa2\Route\Dispatcher; use Phossa2\Route\Extensions\UserAuth; $dispatcher = new Dispatcher(); $dispatcher // add handler for unauthorized routing ->addHandler( function() { echo "need auth"; }, Status::UNAUTHORIZED) // add a route ->addGet('/user/view/{id:d}', function() { echo "AUTHED!"; }) // add extension to force auth routes under '/user/' ->addExt(function($event) { $result = $event->getParam('result'); $path = $result->getPath(); if (!isset($_SESSION['authed']) && '/user/' === substr($path, 0, 6)) { $result->setStatus(Status::UNAUTHORIZED); return false; } return true; }, Dispatcher::EVENT_BEFORE_MATCH); // try a not authed route $dispatcher->dispatch('GET', '/user/view/123'); // try a authed route $_SESSION['authed'] = 1; $dispatcher->dispatch('GET', '/user/view/123');
-
扩展示例
在路由上验证参数值,
use Phossa2\Route\Status; use Phossa2\Route\Dispatcher; use Phossa2\Route\Extensions\IdValidation; $dispatcher = new Dispatcher(); // add extension to a route $route = (new Route('GET', '/user/{id:d}', null)) ->addExtension(new IdValidation()); // will fail $dispatcher->addRoute($route)->dispatch('GET', '/user/1000');
-
扩展事件
三种类型的事件,分发器级别、收集器级别和路由级别。按执行顺序列出所有事件。
-
Dispatcher::EVENT_BEFORE_MATCH
在匹配开始之前-
Collector::EVENT_BEFORE_MATCH
在收集器中的匹配之前 -
Collector::EVENT_AFTER_MATCH
在收集器中成功匹配之后
-
-
Dispatcher::EVENT_AFTER_MATCH
在分发器级别成功匹配之后 -
Dispatcher::EVENT_BEFORE_DISPATCH
在成功匹配后,在将任何处理器分发之前-
Route::EVENT_BEFORE_HANDLER
在执行此路由的处理器(路由的或收集器的)之前 -
Route::EVENT_AFTER_HANDLER
处理器成功执行之后
-
-
Dispatcher::EVENT_AFTER_DISPATCH
返回到分发器级别,在处理器成功执行之后 -
Dispatcher::EVENT_BEFORE_HANDLER
匹配失败或未找到匹配路由的处理器之前,在执行分发器的默认处理器之前 -
Dispatcher::EVENT_AFTER_HANDLER
分发器的默认处理器执行之后
-
调试
有时,你需要知道发生了什么错误。
$dispatcher->enableDebug()->setDebugger($logger);
其中 $logger
是一个符合 PSR-3 的日志实现,实现了 Psr\Log\LoggerInterface
接口。分发器将分发过程的日志发送到日志记录器。
路由策略
此库支持几种基于 URL 的路由策略。不同的策略收集器可以组合成一个分发器。
-
使用参数和值对进行路由,
http://servername/path/index.php/controller/action/id/1/name/nick
参数顺序可以是任意的,但必须以对的形式出现。此方案的优势是快速且对网络爬虫友好。如果使用 URL 重写,则上述内容可以写成以下形式,
http://servername/path/controller/action/id/1/name/nick
除了 '?' 和 '&' 之外,可以使用任何有效的 URL 字符作为参数分隔符。
http://servername/path/controller-action-id-1-name-nick
此策略在
Phossa2\Route\Collector\CollectorPPR
类中实现。 -
查询参数路由(QPR)
路由信息直接嵌入在 URL 查询中。此方案的优势是快速且清晰。
http://servername/path/?r=controller-action-id-1-name-nick
此策略在
Phossa2\Route\Collector\CollectorQPR
类中实现。 -
正则表达式路由(RER)
正则表达式路由是此库的默认路由策略,并在
Phossa2\Route\Collector\Collector
类中实现。// created with default RER collector $dispatcher = (new Dispatcher()) ->addCollector(new Collector()) // regex based routing first ->addCollector(new CollectorQPR()); // support for legacy QPR
正则表达式匹配算法
RER 收集器可以与不同的正则表达式匹配算法一起使用。
-
此 基于分组计数的算法 在
Phossa2\Route\Parser\ParserGcb
类中实现,并在本文中详细解释 "使用正则表达式快速请求路由"。phossa-route 默认使用此算法。
-
标准算法
此算法由 phossa2/route 开发,比 fastRoute GCB 算法慢一点。它在
Phossa2\Route\Parser\ParserStd
类中实现。使用此标准算法,
use Phossa2\Route\Dispatcher; use Phossa2\Route\Parser\ParserStd; use Phossa2\Route\Collector\Collector; // use standard algorithm $dispatcher = new Dispatcher(new Collector(new ParserStd));
-
关于路由算法的注释
-
它并不像你想象的那么重要。
如果您在应用程序中使用路由库,不同的算法在单个请求上的差异可能只有0.1 - 0.2毫秒,对于应用程序来说这似乎微不足道,除非您将其用作独立路由器。
-
如果您确实在乎路由速度
使用不同的路由策略,例如参数对路由(PPR),它比基于正则表达式的路由要快得多。通过精心设计您的路由,即使使用较慢的算法,您也可能获得更好的结果。
-
如果您只是对速度狂热,尝试网络路由或服务器路由。
-
变更日志
请参阅CHANGELOG获取更多信息。
测试
$ composer test
贡献
请参阅CONTRIBUTE获取更多信息。
依赖项
-
PHP >= 5.4.0
-
phossa2/event >= 2.1.5
-
phossa2/shared >= 2.0.27
许可证
附录
-
-
最坏情况匹配
此基准测试匹配最后一个路由和未知路由。它生成一个随机前缀和后缀的路由,试图阻止任何优化。1,000个路由,每个路由有8个参数。
此基准测试由14个测试组成。每个测试执行1,000次,结果被修剪,然后取平均值。落在平均值的3个标准差之外的数据被丢弃。
"参数对路由(PPR)"是最快的,用作基线。
测试名称 结果 时间 + 间隔 变化 Phossa PPR - 未知路由(1,000路由) 998 0.0000724551 +0.0000000000 基线 Phossa PPR - 最后一个路由(1,000路由) 993 0.0000925307 +0.0000200755 慢28% Symfony2 Dumped - 未知路由(1,000路由) 998 0.0004353616 +0.0003629065 慢501% Phroute - 最后一个路由(1,000路由) 999 0.0006205601 +0.0005481050 慢756% Phossa - 未知路由(1,000路由) 998 0.0006903790 +0.0006179239 慢853% FastRoute - 未知路由(1,000路由) 1,000 0.0006911943 +0.0006187392 慢854% FastRoute - 最后一个路由(1,000路由) 999 0.0006962751 +0.0006238200 慢861% Phroute - 未知路由(1,000路由) 998 0.0007134676 +0.0006410125 慢885% Symfony2 Dumped - 最后一个路由(1,000路由) 993 0.0008066097 +0.0007341545 慢1013% Phossa - 最后一个路由(1,000路由) 998 0.0009104498 +0.0008379947 慢1157% Symfony2 - 未知路由(1,000路由) 989 0.0023998006 +0.0023273455 慢3212% Symfony2 - 最后一个路由(1,000路由) 999 0.0025880890 +0.0025156339 慢3472% Aura v2 - 最后一个路由(1,000路由) 981 0.0966411463 +0.0965686912 慢133281% Aura v2 - 未知路由(1,000路由) 992 0.1070026719 +0.1069302168 慢147581% -
第一次路由匹配
此基准测试测试每个路由器匹配第一个路由的速度有多快。1,000个路由,每个路由有8个参数。
此基准测试由7个测试组成。每个测试执行1,000次,结果被修剪,然后取平均值。落在平均值的3个标准差之外的数据被丢弃。
注意 由于FastRoute和Phroute都实现了静态路由表,因此在第一次路由匹配(这是一个静态路由)上它们都很快速。
测试名称 结果 时间 + 间隔 变化 FastRoute - 第一个路由 999 0.0000403543 +0.0000000000 基线 Phroute - 第一个路由 998 0.0000405911 +0.0000002368 慢1% Symfony2导出 - 第一路由 999 0.0000590617 +0.0000187074 慢46% Phossa PPR - 第一路由 977 0.0000678727 +0.0000275184 慢68% Phossa - 第一路由 999 0.0000898475 +0.0000494932 慢123% Symfony2 - 第一路由 998 0.0003983802 +0.0003580259 慢887% Aura v2 - 第一路由 986 0.0004391784 +0.0003988241 慢988%
-
-
URL重写
设置URL重写以使用
index.php
进行路由-
Apache的
.htaccess
文件使用mod_rewrite
模块已启用DirectorySlash Off Options -MultiViews DirectoryIndex index.php RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-l RewriteRule ^ index.php [QSA,L]
并在你的
httpd.conf
文件中启用使用.htaccess
<VirtualHost *:80> ServerAdmin me@mysite.com DocumentRoot "/path/www.mysite.com/public" ServerName mysite.com ServerAlias www.mysite.com <Directory "/path/www.mysite.com/public"> Options -Indexes +FollowSymLinks +Includes AllowOverride All Order allow,deny Allow from all </Directory> </VirtualHost>
-
Nginx配置在
nginx.conf
文件中server { listen 80; server_name www.mysite.com mysite.com; root /path/www.mysite.com/public; try_files $uri $uri/ /index.php$is_args$args; location /index.php { fastcgi_connect_timeout 3s; fastcgi_read_timeout 10s; include fastcgi.conf; fastcgi_pass 127.0.0.1:9000; } }
-
-
基于请求信息,如请求设备、源IP、请求方法等,服务提供商可能将请求直接导向不同的主机、服务器、应用模块或处理器。
-
网络级别路由
常见情况,例如根据请求的源IP进行路由,将请求路由到最近的服务器,这在内容分发网络(CDN)中很常见,并在网络级别完成。
-
Web服务器路由
出于性能考虑,一些简单的路由可以在Web服务器级别完成,例如使用Apache或Nginx配置进行简单路由。
例如,如果您的服务器因维护而关闭,您可能需要替换以下
.htaccess
文件DirectorySlash Off Options -MultiViews DirectoryIndex maintenance.php RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-l RewriteRule ^ maintenance.php [QSA,L]
-
应用级别路由
它解决了更多复杂的问题,并且更加灵活。
通常,路由在单个点
index.php
完成。所有请求都被配置为首先由该脚本处理并路由到不同的程序。
-