phossa / phossa-route
一个快速且功能齐全的PHP路由库
Requires
- php: >=5.4.0
- phossa/phossa-shared: >=1.0.6
Requires (Dev)
This package is not auto-updated.
Last update: 2024-09-14 19:58:39 UTC
README
查看新的库phoole/route 简介
phossa-route 是一个 快速、功能齐全 且 功能丰富 的PHP应用层路由库。它根据URL、HTTP头信息、会话信息等来分发请求。
它需要PHP 5.4,并支持PHP 7.0+、HHVM。它符合PSR-1、PSR-2、PSR-4规范。
为什么还需要另一个路由库?
-
超级快速!如果这对你很重要。
-
支持不同的路由策略及其组合。
-
支持不同的正则表达式路由算法,包括fastRoute算法。
-
简洁的路由语法。支持路由参数和可选路由段。
-
不同级别的默认处理器。
-
通过多级扩展精细控制路由过程。
-
过滤匹配过程中请求的其他字段。
-
路由和正则表达式调试。
-
支持依赖注入。支持第三方Di库。
入门
-
安装
使用
composer
工具安装。composer require "phossa/phossa-route=1.*"
或者在你的
composer.json
中添加以下行{ "require": { "phossa/phossa-route": "^1.0.0" } }
-
简单用法
use Phossa\Route\Dispatcher; // dispatcher with default collector & resolver $dispatcher = (new Dispatcher()) ->addGet( '/blog/{action:xd}[/{year:d}[/{month:d}[/{date:d}]]]', function($result) { echo "action is " . $result->getParameter('action'); }) ->addPost('/blog/post', 'handler2') ->addRoute(new Route\Route( 'GET,HEAD', // multiple methods '/blog/read[/{id:d}]', 'handler3', ['id' => '1'])); // default $id value // route base on info provided by server $dispatcher->dispatch();
-
从文件中加载路由
use Phossa\Route\Dispatcher; /* * routes.php : * return [ * '/user/phossa' => 'handler1', * '/user/{action:xd}/{id:d}' => [['controller', 'action'], 'GET,POST'], * '/user/view[/{id:d}]' => ['handler2', 'GET', ['id' => 23]] * ]; */ $dispatcher = (new Dispatcher())->loadRoute('./routes.php');
路由语法
注意:对其他路由库语法的支持在1.1.x版本中。用户可以使用phossa-route选择他/她的 favorite 路由语法 :)
-
{命名}参数
路由模式语法使用,其中
{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 = (new Dispatcher())->addPost('/blog/post', 'handler2');
addGet()
和addPost()
是addRoute(RouteInterface)
的包装。 -
可以通过使用多个收集器将路由分组到不同的集合中。
// '/user' related $collector_user = (new Route\Collector\Collector()) ->addGet('/user/list/{id:d}', 'handler1') ->addGet('/user/view/{id:d}', 'handler2') ->addPost('/user/new', 'handler3'); // '/blog' related $collector_blog = (new Route\Collector\Collector()) ->addGet('/blog/list/{user_id:d}', 'handler4') ->addGet('/blog/read/{blog_id:d}', 'handler5'); $dispatcher->addCollector($collector_user) ->addCollector($collector_blog);
-
相同的路由模式
用户可以定义具有不同 HTTP 方法的相同路由模式。
$dispatcher ->addGet('/user/{$id}', 'handler1') ->addPost('/user/{$id}', 'handler2');
分发
-
使用分发器的
dispatch()
进行分发在脚本
index.php
中,dispatch()
通常是最后一行。// index.php // ... // dispatch base on server request info $dispatcher->dispatch();
dispatch()
接收一个可选参数Phossa\Route\Context\ResultInterface
。如果没有提供,它将收集来自全局变量(如$_SERVER
和$_REQUEST
)的信息,并根据路由定义将分发到正确的例程或可调用对象。 -
分发一个 URL
用户可以分发一个 URL,
$dispatcher->dispatchUrl('GET', '/error404');
-
匹配而不是分发
如果使用
match()
方法,则默认在dispatch()
中执行处理器,用户可以拥有更多的控制权// use info from $_SERVER etc. if ($dispatcher->match()) { $result = $dispatcher->getResult(); switch($result->getStatus()) { case 200: // ... break; case 404: // ... break; default: // ... break; } } else { // no match found // ... }
matchUrl()
也提供了。
处理器
-
多个处理器
对于状态
200 OK
,路由定义了一个处理器。支持其他结果状态的多处理器。use Phossa\Route\Route; use Phossa\Route\Status; $route = (new Route('GET', '/user/{action:xd}/{id:d}', function($result) { // handler for Status::OK $user_id = $result->getParameter('id'); // ... })->addHandler(Status::METHOD_NOT_ALLOWED, 'handler1'); // extra handler
如果路由匹配但方法无效,将执行处理器
handler1
。 -
分发器和收集器可以具有多个处理器,对应不同的状态。如果结果没有设置处理器,则将检索收集器的处理器(相同的状态码)。如果仍然没有运气,如果已定义,则将使用分发器的处理器(相同的状态码)。
分发器级别的处理器,
use Phossa\Route\Status; $dispatcher->addHandler( Status::SERVICE_UNAVAILABLE, function($result) { // ... } );
收集器级别的处理器,
$collector->addHandler( Status::MOVED_PERMANENTLY, function($result) { // ... } );
-
处理器解析
大多数时候,路由返回一个处理器,例如
[ 'className', 'method' ]
。可以使用处理器解析器将此伪处理器解析为实际的可调用对象。use Phossa\Route; // dispatcher with default resolver $dispatcher = new Route\Dispatcher( new Route\Collector\Collector(), new Route\Handler\ResolverAbstract() );
用户可以通过扩展
Phossa\Route\Handler\ResolverAbstract
类来编写自己的处理器解析器。
扩展
扩展是可执行的任务,在特定分发阶段的匹配结果或其他任务之前或之后执行。
-
扩展的使用
扩展 必须 返回一个布尔值来指示是否继续分发过程。
FALSE
表示停止并返回到顶层,分发器级别。use Phossa\Route\Dispatcher use Phossa\Route\Extensions\RedirectToHttpsExtension; // create dispatcher $dispatcher = new Dispatcher(); // direct any HTTP request to HTTPS port before any routing dispatcher->addExtension(new RedirectToHttpsExtension());
强制对任何以 '/user' 前缀的 URL 进行身份验证,
$dispatcher->addExtension( function($result) { $pattern = $result->getRequest()->getPattern(); if ('/user' == substr($pattern, 0, 5) && !isset($_SESSION['auth'])) { $result->setStatus(Status::UNAUTHORIZED); return false; // return to dispatcher level } return true; // authed or not in /user }, Dispatcher::BEFORE_MATCH // run this extension before matching ); // set default auth handler at dispatcher level $dispatcher->addHandler( Status::UNAUTHORIZED, function($result) { // display auth page etc. } );
-
扩展的示例
参数值的有效性验证,
$route->addExtension( function($result) { $id = (int) $result->getParameter('id'); if ($id > 1000) { // not allowed $result->setStatus(Status::PRECONDITION_FAILED); return false; } return true; }, Route::BEFORE_ROUTE // before execute route handler );
路由集合的统计信息
$collector->addExtension( function($result) { // collect statistics }, Collector::BEFORE_COLL // before collector match )->addExtension( function($result) { // collect statistics }, Collector::AFTER_COLL // after a successful match );
-
扩展阶段
三种类型阶段:分发器级别、收集器级别和路由级别。按执行顺序列出所有阶段。
-
Dispatcher::BEFORE_MATCH
在匹配开始之前-
Collector::BEFORE_COLL
在收集器中的匹配之前 -
Collector::AFTER_COLL
在收集器中成功匹配之后
-
-
Dispatcher::AFTER_MATCH
在派发器级别成功匹配后 -
Dispatcher::BEFORE_DISPATCH
在成功匹配后,但在派发到任何处理器之前-
Route::BEFORE_ROUTE
在执行此路由的处理器(路由或收集器)之前 -
Route::AFTER_ROUTE
处理器成功执行后
-
-
Dispatcher::AFTER_DISPATCH
处理器成功执行后返回派发器级别 -
Dispatcher::BEFORE_DEFAULT
匹配失败或未找到匹配路由的处理器,在执行派发器默认处理器之前 -
Dispatcher::AFTER_DEFAULT
派发器默认处理器执行后
-
过滤器
-
过滤器使用
有时,用户在决定如何派发之前可能需要查看其他信息。 扩展 是实现此目的的一种方法。但
addFilter()
方法在路由级别更为合适。// match against $_SERVER, $_REQUEST, $_SESSION, $_COOKIE etc. $route = (new Route('GET', '/user/list/{$id}', 'handler1')) ->addFilter('server.server_name', '(m|www).phossa.com') ->addFilter('cookie.vote_status', 'voted');
甚至支持闭包
// closure takes the value from $_SERVER['SERVER_NAME'] as input $route->addFilter('server.server_name', function($value) { switch($value) { case 'a1.phossa.com': case 'b2.phossa.com': return true; default: return false; // always return a bool } });
-
与扩展的区别
过滤器在匹配过程中使用,如果过滤失败,匹配过程将尝试下一个路由。
而路由级别的扩展在成功匹配后、执行处理器之前执行。
调试
有时,你需要知道发生了什么错误。
$dispatcher->setDebugMode(true)->setDebugger($logger);
其中 $logger
是一个与 PSR-3 兼容的日志实现,实现了 Psr\Log\LoggerInterface
接口。派发器会将派发过程的日志发送到日志记录器。
路由策略
此库支持几种基于 URL 的路由策略。不同的策略收集器可以组合成一个派发器。
-
查询参数路由 (QPR)
路由信息直接嵌入在 URL 查询中。这种方案的优点是快速且清晰。
http://servername/path/?r=controller-action-id-1-name-nick
此策略在
Phossa\Route\Collector\CollectorQPR
类中实现。 -
使用以下参数和值对
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
此策略在
Phossa\Route\Collector\CollectorPPR
类中实现。 -
正则表达式路由 (RER)
基于正则表达式的路由是此库的默认路由策略,并在
Phossa\Route\Collector\Collector
类中实现。// created with default RER collector $dispatcher = new Dispatcher(); // add supprot for legacy query parameter routing $dispatcher->addCollector(new CollectorQPR());
正则表达式匹配算法
可以在 RER 收集器中使用不同的正则表达式匹配算法。
-
这个 基于分组计数算法 在
Phossa\Route\Regex\ParserGcb
类中实现,并在本文 "使用正则表达式进行快速请求路由" 中详细解释。phossa-route 默认使用此算法。
-
标准算法
此算法由 phossa-route 开发,比 FastRoute GCB 算法稍慢。它在
Phossa\Route\Regex\ParserStd
类中实现。使用此标准算法:
use Phossa\Route\Dispatcher; use Phossa\Route\Regex\ParserStd; use Phossa\Route\Collector\Collector; // use standard algorithm $dispatcher = new Dispatcher(new Collector(new ParserStd));
-
对路由算法的评论
-
这并不像你想象的那么重要。
如果你在你的应用程序中使用路由库,不同的算法可能只对单个请求有 0.1 - 0.2ms 的差异,这对于应用程序来说似乎没有意义,除非你将其用作独立的路由器。
-
如果你 确实 关心路由速度
使用不同的路由策略,例如 参数对路由(PPR),它的速度比基于正则表达式的路由要快得多。通过精心设计您的路由,即使使用 较慢 的算法,也可能实现更好的结果。
-
如果您只是 极度追求速度,请尝试 网络路由或服务器路由。
-
依赖项
-
PHP >= 5.4.0
-
phossa/phossa-shared >= 1.0.6
-
如果您正在使用 调试,则需 phossa/phossa-logger >= 1.0.0
许可证
附录
-
-
最坏情况匹配
这个基准测试匹配最后一个路由和未知路由。它生成一个随机前缀和后缀的路由,以尝试阻止任何优化。1,000条路由,每条路由有8个参数。
这个基准测试由14个测试组成。每个测试执行1,000次,结果被修剪,然后取平均值。那些落在平均值的3个标准差之外的值被丢弃。
"参数对路由(PPR)" 是最快的,用作基准。
-
首次路由匹配
这个基准测试测试每个路由器匹配第一个路由的速度。1,000条路由,每条路由有8个参数。
这个基准测试由7个测试组成。每个测试执行1,000次,结果被修剪,然后取平均值。那些落在平均值的3个标准差之外的值被丢弃。
注意 FastRoute 和 Phroute 都实现了静态路由表,因此它们在首次路由匹配(这是一个静态路由)方面非常快。
-
-
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.conf
中的 Nginx 配置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或ngix配置进行简单路由。
例如,如果您的服务器因维护而关闭,您可以按以下方式替换
.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
执行。所有请求都配置为首先由该脚本处理,然后路由到不同的程序。
-