guirong / php-router
Routing management module
Requires
- php: >7.1.0
- guirong/php-closure: ^1.0
Requires (Dev)
- guirong/php-closure: ^1.0
- phpunit/phpunit: ^7.5
README
非常快速且轻量的请求匹配路由器。
- 无依赖、简洁、速度快、功能完善
- 轻量级且速度快,查找速度不受路由数量的影响
- 支持路由组, 支持路由参数定义,以及丰富的自定义路由选项
- 支持给指定的路由命名,可根据名称拿到注册的路由对象
- 支持请求方法:
GET
POST
PUT
DELETE
HEAD
OPTIONS
... - 支持自动匹配路由到控制器就像 Yii 一样, 请参看配置项
autoRoute
(不推荐) - 压测对比数据请看路由测试
多个版本:
不同的版本有稍微的区别以适应不同的场景
Router
通用版本,也是后几个版本的基础类,适用于所有的情况。SRouter
静态类版本。Router
的简单包装,通过静态方法使用(方便小应用快速使用)CachedRouter
继承自Router
,支持路由缓存的版本,可以 缓存路由信息到文件- 适合php-fpm 环境使用(有缓存将会省去每次的路由收集和解析消耗)
PreMatchRouter
继承自Router
,预匹配路由器。当应用的静态路由较多时,将拥有更快的匹配速度- 适合php-fpm 环境,php-fpm 情形下,实际上我们在收集路由之前,已经知道了路由path和请求动作METHOD
ServerRouter
继承自Router
,服务器路由。内置支持动态路由临时缓存. 适合swoole
等常驻内存应用使用- 最近请求过的动态路由将会缓存为一个静态路由信息,下次相同路由将会直接匹配命中
内置调度器:
- 支持事件:
notFound
methodNotAllowed
. 当触发事件时你可以做一些事情(比如记录日志等) - 支持通过方法
$router->dispatch($path, $method)
手动调度一个路由 - 你即使不配置任何东西, 它也能很好的工作
路由器管理
RouterManager
当需要在一个项目里处理多个域名下的请求时,方便的根据不同域名配置多个路由器
项目地址
安装
- composer 命令
composer require guirong/php-router
- composer.json
{ "require": { "guirong/php-router": "dev-master" } }
- 直接拉取
git clone https://github.com/GuiRong2969/php-router.git // github
压测
自动生成了1000条路由,每条有9个参数位,分别测试1000次的
- 第一条路由匹配
- 最后一条路由匹配
- 不存在的路由匹配
- 压测日期 2022.11.19
- An example route:
/9b37eef21e/{arg1}/{arg2}/{arg3}/{arg4}/{arg5}/{arg6}/{arg7}/{arg8}/{arg9}/bda37e9f9b
Worst-case matching
This benchmark matches the last route and unknown route. It generates a randomly prefixed and suffixed route in an attempt to thwart any optimization. 1,000 routes each with 9 arguments.
This benchmark consists of 14 tests. Each test is executed 1,000 times, the results pruned, and then averaged. Values that fall outside of 3 standard deviations of the mean are discarded.
First route matching
This benchmark tests how quickly each router can match the first route. 1,000 routes each with 9 arguments.
This benchmark consists of 7 tests. Each test is executed 1,000 times, the results pruned, and then averaged. Values that fall outside of 3 standard deviations of the mean are discarded.
使用说明
各个版本的方法名和参数都是一样的
首先, 需要导入类
use Guirong\PhpRouter\Router; $router = new Router();
快速开始
创建一个简单的 public/index.php
文件:
use Guirong\PhpRouter\Router; // 需要先加载 autoload 文件 require dirname(__DIR__) . '/vendor/autoload.php'; $router = new Router(); $router->get('/', function() { echo 'hello'; }); // 开始调度运行 $app = new \Guirong\PhpRouter\App\Application($router); $response = $app->handle( $request = $_REQUEST, //你的请求资源,可自定义 ); $response->send();
使用php启动一个测试server: php -S 127.0.0.1:8080 -t ./public
好了,现在你可以访问 http://127.0.0.1:8080 可以看到输出 hello
- 不使用 Composer
如果是直接下载的包代码,可以加载 test/boot.php
文件,也可以加载到 Guirong\PhpRouter
命名空间.
用如下的语句替换上面的 autoload.php
加载语句即可:
require dirname(__DIR__) . '/test/boot.php';
添加路由
// 匹配 GET 请求. 处理器是个闭包 Closure $router->get('/', function() { echo 'hello'; }); // 匹配参数 'test/john' $router->get('/test/{name}', function($params) { echo $params['name']; // 'john' }, [ 'name' => '\w+', // 添加参数匹配限制。若不添加对应的限制,将会自动设置为匹配除了'/'外的任何字符 ]); // 可选参数支持。匹配 'hello' 'hello/john' $router->get('/hello[/{name}]', function() { echo $params['name'] ?? 'No input'; // 'john' }, [ 'name' => '\w+', // 添加参数匹配限制 ]); // 匹配 POST 请求 $router->post('/user/login', function() { var_dump($_POST); }); // 匹配 GET 或者 POST $router->map(['get', 'post'], '/user/login', function() { var_dump($_GET, $_POST); }); // 允许任何请求方法 $router->any('/home', function() { echo 'hello, you request page is /home'; }); $router->any('/404', function() { echo "Sorry,This page not found."; });
使用路由组
// 路由组 $router->group('/user', function ($router) { $router->get('/', function () { echo 'hello. you access: /user/'; }); $router->get('/index', function () { echo 'hello. you access: /user/index'; }); });
使用控制器
// 使用 控制器 $router->get('/', App\Controllers\HomeController::class); $router->get('/index', 'App\Controllers\HomeController@index');
备用路由处理
可以注册一个备用路由处理。当没匹配到时,就会使用它
$router->any('*', 'fallback_handler');
如果配置了
'ignoreLastSlash' => true
, '/index' 等同于 '/index/'
注意
可选参数 - 只能是在路由path的最后
正确的:
/hello[/{name}] // match: /hello/tom /hello
/my[/{name}[/{age}]] // match: /my/tom/78 /my/tom
错误的:
/my[/{name}]/{age}
自动匹配路由
支持根据请求的URI自动匹配路由(就像 yii 一样), 需配置 autoRoute
.
'autoRoute' => 1, // 启用 'controllerNamespace' => 'App\\Controllers', // 控制器类所在命名空间
请参看示例
example
中的使用
此时请求没有配置路由的 /demo
/demo/test
。将会自动尝试从 App\\Controllers
命名空间下去查找 DemoController
查找逻辑是
- 只有一节的(如
/demo
),直接定义它为控制器类名进行查找 - 大于等于两节的默认先认为最后一节是控制器类名,进行查找
- 若失败,再尝试将倒数第二节认为是控制器名,最后一节是action名
设置路由配置
// set config $router->config([ 'ignoreLastSlash' => true, 'autoRoute' => 1, 'controllerNamespace' => 'app\\controllers', ]);
NOTICE: 必须在添加路由之前调用
$router->config()
路由匹配
array public function match($path, $method)
$path
string 请求的URI path$method
string 请求的request method- 返回
array
返回匹配结果信息
示例
根据请求的 URI path 和 请求 METHOD 查找匹配我们定义的路由信息。
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $method = $_SERVER['REQUEST_METHOD']; $routeInfo = $router->match($path, $method);
根据返回的路由信息,我们就可以自由的决定如何调用对应的处理。
关于返回的数据结构,可以查看 关键方法参考
路由调度
如果你不想自己实现路由调度,可以使用内置的路由调度器 Guirong\PhpRouter\Dispatcher\Dispatcher
use Guirong\PhpRouter\Dispatcher\Dispatcher; $dispatcher = new Dispatcher([ // default action method name 'defaultAction' => 'index', 'actionPrefix' => '', 'dynamicAction' => true, // @see Router::$globalParams['act'] 'dynamicActionVar' => 'act', ]);
设置事件处理(可选)
// 当匹配失败, 重定向到 '/404' $dispatcher->on('notFound', '/404'); // 或者, 当匹配失败, 输出消息... $dispatcher->on('notFound', function () { echo "the page not found!"; }); // 当请求方式不允许, 仅允许 POST/GET 等, 输出消息... $dispatcher->on('methodNotAllowed', function ($uri) { echo "the method not allowed!"; });
开始路由匹配和调度
$app = new \Guirong\PhpRouter\App\Application($router); $response = $app->setDispatcOptions( ['dispatcher' => $dispatcher] )->handle( $request = $_REQUEST, //你的请求资源,可自定义 ); $response->send();
使用控制器方法
通过@
符号连接控制器类和方法名可以指定执行方法。
$router->get('/', App\Controllers\HomeController::class); $router->get('/index', 'App\Controllers\HomeController@index'); $router->get('/about', 'App\Controllers\HomeController@about');
NOTICE: 若第二个参数仅仅是个 类,将会尝试执行通过
defaultAction
配置的默认方法
动态匹配控制器方法
动态匹配控制器方法, 需配置
'dynamicAction' => true, // 启用 // action 方法名匹配参数名称,符合条件的才会当做action名称 // @see Router::$globalParams['act'] 匹配 '[a-zA-Z][\w-]+' 'dynamicActionVar' => 'act',
NOTICE: 使用动态匹配控制器方法, 应当使用
any()
添加路由. 即此时不能限定请求方法REQUEST_METHOD
// 访问 '/home/test' 将会执行 'App\Controllers\HomeController::test()' $router->any('/home/{act}', App\Controllers\HomeController::class); // 可匹配 '/home', '/home/test' 等 $router->any('/home[/{act}]', App\Controllers\HomeController::class);
NOTICE: 上面两个的区别是 第一个无法匹配
/home
使用方法执行器
配置 actionExecutor
为你需要的方法名,例如配置为 'actionExecutor' => 'run'
,那所有的方法请求都会提交给此方法。
会将真实的 action 作为参数传入run($action)
, 需要你在此方法中调度来执行真正的请求方法。
NOTICE: 在你需要将路由器整合到自己的框架时很有用
示例:
// 访问 '/user', 将会调用 App\Controllers\UserController::run('') $router->get('/user', 'App\Controllers\UserController'); // 访问 '/user/profile', 将会调用 App\Controllers\UserController::run('profile') $router->get('/user/profile', 'App\Controllers\UserController'); // 同时配置 'actionExecutor' => 'run' 和 'dynamicAction' => true, // 访问 '/user', 将会调用 App\Controllers\UserController::run('') // 访问 '/user/profile', 将会调用 App\Controllers\UserController::run('profile') $router->any('/user[/{name}]', 'App\Controllers\UserController');
开始路由匹配和调度
$app = new \Guirong\PhpRouter\App\Application($router); $response = $app->handle( $request = $_REQUEST, //你的请求资源,可自定义 ); $response->send();
中间件(可选)
中间件提供了一种方便的机制来检查和过滤进入应用程序的 HTTP 请求。
NOTICE: 以下中间件将在应用程序处理请求之前执行一些任务:
<?php namespace App\Middleware; use Closure; class BeforeMiddleware { public function handle($request, Closure $next) { // 你可以在这里执行一些操作 ... return $next($request); } public function terminate($request,$response){ // 请求响应完成后执行操作 } }
NOTICE: 此中间件将在请求由应用程序处理后执行其任务:
<?php namespace App\Middleware; use Closure; class AfterMiddleware { public function handle($request, Closure $next) { $response = $next($request); // 执行操作 return $response; } public function terminate($request,$response){ // 请求响应完成后执行操作 } }
NOTICE: 定义中间件配置类
如果您想自己定义配置类,请先继承默认配置 Guirong\PhpRouter\Middleware\Config
<?php namespace App\Middleware; use Guirong\PhpRouter\Middleware\Config; class MyConfig extends Config { /** * The application's middleware stack. * Global middleware * @var array */ protected $middleware = [ // \App\Middleware\BeforeMiddleware::class ]; /** * The application's route middleware. * * @return array */ protected $routeMiddleware = [ 'before' => \App\Middleware\BeforeMiddleware::class, 'after' => \App\Middleware\AfterMiddleware::class, ]; }
NOTICE: 全局中间件默认全局生效,路由中间需手动指派
示例:
在单一路由中启用
$router->middleware(['before','after'])->get('/user', 'App\Controllers\UserController');
或
$router->middleware('before','after')->get('/user', 'App\Controllers\UserController');
在路由组中启用
$router->middleware('before','after')->group('/user', function ($router) { $router->get('/', function () { echo 'hello. you access: /user/'; }); $router->get('/index', function () { echo 'hello. you access: /user/index'; }); });
或
$router->group('/user', function ($router) { $router->get('/', function () { echo 'hello. you access: /user/'; }); $router->get('/index', function () { echo 'hello. you access: /user/index'; }); },['before','after']);
路由匹配和调度
$app = new \Guirong\PhpRouter\App\Application($router); $response = $app->handle( $request = $_REQUEST, //你的请求资源,可自定义 \App\Middleware\MyConfig::class //你的中间件配置类,可自定义 ); $response->send(); $app->terminate($request, $response);
中间件异常捕获
配置完中间件后,所有系统异常将被$response
捕获,并挂载至 exception
属性中,您可以通过 $response->getException()
获取异常,自行处理
$response = $app->handle( $request = $_REQUEST, //你的请求资源,可自定义 \App\Middleware\MyConfig::class //你的中间件配置类,可自定义 ); if($response->getException()){ // 提取异常信息 $exception = $response->getException(); // ......自行处理异常 }
NOTICE: 在以上路由中配置完中间件,
http
请求会经过before
中间件的handle
函数,在处理完主业务后,继而经过after
中间件的handle
函数,最后在程序 response 后依次进入before
,after
中间件的terminate
函数
运行示例
示例代码在 example
下。
- 对象版本
你可以通过 php -S 127.0.0.1:5670 example/object.php
来运行一个测试服务器, 现在你可以访问 http://127.0.0.1:5671
测试
phpunit
- simple benchmark
php example/benchmark.php
License
MIT
我的其他项目
guirong/cli-message
github
一个简单易用的,命令行输出样式工具库