vitorsreis / extend-router
此包已被废弃,不再维护。未建议替代包。
非常优雅、快速且强大的PHP路由器
4.5.0
2023-11-15 01:21 UTC
Requires
- php: >=5.6
- vitorsreis/extend-caller: ^1.0
Requires (Dev)
README
通过单词树和正则表达式匹配索引,此路由器非常优雅、快速且强大。采用合并中间件队列(非唯一匹配)架构,在路由中提供带缓存、上下文和持久数据的多个交互。单元测试在以下版本通过:5.6
、7.4
、8.1
和 8.2
基准测试
在此处查看与领先公共库的基准测试:这里。
安装
composer require vitorsreis/extend-router
用法
$router = new \VSR\Extend\Router(); $router->get('/product/:id', function ($id) { echo "product $id"; }); $router->match('GET', '/product/100')->execute(); // output: "product 100"
路由分组
您可以添加路由组
$router->group('/product', function (\VSR\Extend\Router $router) { $router->get('/:id', function ($id) { echo "get product $id"; }); $router->post('/:id', function ($id) { echo "post product $id"; }); }); $router->match('POST', '/product/100')->execute(); // output: "post product 100"
404 未找到和 405 方法不允许
- 您可以在捕获中使用 NotFoundException 和 MethodNotAllowedException
try { $router->match('POST', '/aaa'); } catch (\VSR\Extend\Router\Exception\NotFoundException $e) { echo "{$e->getCode()}: {$e->getMessage()}"; // output: "404: Route \"/aaa\" not found" } catch (\VSR\Extend\Router\Exception\MethodNotAllowedException $e) { echo "{$e->getCode()}: {$e->getMessage()}"; // output: "405: Method \"POST\" not allowed for route \"/aaa\"" }
- 或者可以捕获 RuntimeException
try { $router->match('POST', '/aaa'); } catch (\VSR\Extend\Router\Exception\RuntimeException $e) { if (in_array($e->getCode(), [404, 405])) { echo "{$e->getCode()}: {$e->getMessage()}"; // output: "404: Route \"/aaa\" not found" or "405: Method \"POST\" not allowed for route \"/aaa\"" } else { throw $e; } }
- 在 405 的情况下,您可以获取允许的方法
try { $router->match('PUT', '/aaa'); } catch (\VSR\Extend\Router\Exception\MethodNotAllowedException $e) { echo implode(', ', $e->allowedMethods); // output: "GET, POST, ..." }
友好的URI
您可以为特定路由添加友好的URI进行重定向
$router->post('/product/:id', function ($id) { echo "post product $id"; }); $router->friendly('/iphone', '/product/100'); $router->match('POST', '/iphone')->execute(); // output: "post product 100"
过滤器
过滤器用于以更简洁、更优雅的方式添加正则表达式到路由变量。
$router->get('/:var1[09]', function ($var1) { return "[09] : $var1"; }); $router->get('/:var1[az]', function ($var1) { return "[az] : $var1"; }); echo $router->match('GET', '/111')->execute()->result; // output: "[09] : 111" echo $router->match('GET', '/aaa')->execute()->result; // output: "[az] : aaa"
您可以在路由中使用宽松的过滤器
$router->get('/[09]', function () { return "[09]"; }); $router->get('/[az]', function () { return "[az]"; }); echo $router->match('GET', '/111')->execute()->result; // output: "[09]" echo $router->match('GET', '/aaa')->execute()->result; // output: "[az]"
您可以添加自定义过滤器
$router->addFilter('custom_only_numeric', '\d+') $router->get('/:var1[custom_only_numeric]', function ($var1) { return "CUSTOM_VAR1_FILTER : $var1"; }); $router->addFilter('custom_w10', '\w{10}') $router->get('/[custom_w10]', function () { return 'CUSTOM_LOOSE_FILTER'; });
以下是预注册的过滤器
允许/禁止方法
默认列表:GET
、POST
、PUT
、PATCH
、DELETE
、OPTIONS
、HEAD
$router->allowMethod('XXX', ...); // $router->addRoute('XXX', '/aaa', function () { ... }); = OK // $router->match('XXX', '/aaa'); = OK $router->disallowMethod('GET', ...); // $router->addRoute('GET', '/aaa', function () { ... }); = throw SyntaxException(500) // $router->match('GET', '/aaa'); = throw RuntimeException(400)
缓存
- 此使用模式或
Memory
缓存不存储路由映射。
$cache = new \VSR\Extend\Router\Cache\Memory(); // $cache = new \VSR\Extend\Router\Cache\File(__DIR__ . "/cache/"); // $cache = new \VSR\Extend\Router\Cache\Apcu(); // $cache = new \VSR\Extend\Router\Cache\Memcache(); // $cache = new \VSR\Extend\Router\Cache\Memcached(); // $cache = new \VSR\Extend\Router\Cache\Redis(); $router = new \VSR\Extend\Router($cache); $router->get('/product/:id', function ($id) { echo "product $id"; }); $router->friendly('/iphone-xs', '/product/100'); $router->friendly('/samsumg-s10', '/product/200'); $router->match('GET', '/iphone-xs')->execute(); // output: "product 100"
- 使用以下方式实例化已存储的路由或,如果不存在,则实例化并在缓存中存储。这大大减少了第二次加载后的时间。**不支持带有匿名类或匿名/箭头函数(闭包对象)的路由**
&$hash
参数可以用于控制缓存版本,如果省略,则基于反射的$callback
源代码。&$warning
参数如果与false
不同,则表示发生了错误。
$cache = new \VSR\Extend\Router\Cache\File(__DIR__ . "/cache/"); // $cache = new \VSR\Extend\Router\Cache\Apcu(); // $cache = new \VSR\Extend\Router\Cache\Memcache(); // $cache = new \VSR\Extend\Router\Cache\Memcached(); // $cache = new \VSR\Extend\Router\Cache\Redis(); function callback($id) { echo "product $id"; } $router = $cache->createRouter(function (\VSR\Extend\Router $router) { $router->get('/product/:id', 'callback'); $router->friendly('/iphone-xs', '/product/100'); $router->friendly('/samsumg-s10', '/product/200'); }, $hash, $warning); $router->match('GET', '/iphone-xs')->execute(); // output: "product 100"
- 允许/禁止存储的缓存前缀。
FLAG_ROUTER
用于启用/禁用缓存路由FLAG_MATCH
用于启用/禁用缓存匹配FLAG_EXECUTE
用于启用/禁用缓存执行FLAG_OTHERS
用于启用/禁用缓存其他FLAG_ALL
用于启用/禁用缓存所有
$cache = new \VSR\Extend\Router\Cache\Memory(); $cache->disallowCache($cache::FLAG_ALL); $cache->allowCache($cache::FLAG_ROUTER);
上下文参数
上下文包含当前执行的所有信息。在中间件或非静态类方法的类构造函数中使用名称为 "$context" 的参数,类型为 " omitted,""mixed," 或 "\VSR\Extend\Router\Context"。
$router->get('/aaa', function ($context) { ... }); $router->any('/aaa', function (mixed $context) { ... }); # Explicit mixed type only PHP 8+ $router->get('/a*', function (\VSR\Extend\Router\Context $context) { ... }); $router->any('/a*', function (\VSR\Extend\Router\Context $custom_name_context) { ... });
上下文方法/属性
持久数据
您可以在上下文中持久化数据,以便在未来的回调中持久化。
$router->get('/aaa', function (\VSR\Extend\Router\Context $context) { $context->set('xxx', $context->get('xxx', 0) + 10); # 2. Increment value: 5 + 10 = 15 }); $router->get('/var2', function (\VSR\Extend\Router\Context $context) { $context->set('xxx', $context->get('xxx', 0) + 15); # 3. Increment value: 15 + 15 = 30 }); $context = $router->match('GET', '/aaa') ->set('xxx', 5) # 1. Initial value: 5 ->execute(); echo $context->get('xxx'); // output: "30"
使用回调执行
您可以在每个中间件运行时接收回调,并在当前上下文中运行。
$router->post('/:aa', function ($aa) { return "a1:$aa "; }, function ($aa) { return "a2:$aa "; }); $router->post('/:bb', function ($bb) { return "bb:$bb "; }); $router->match('POST', '/99')->execute(function ($context) { // partial result, run 3 times echo '[' . "{$context->header->cursor}/{$context->header->total} " . "{$context->current->method} {$context->current->route} = $context->result" . '], '; }); // output: "[1/3 POST /:aa = a1:99], [2/3 POST /:aa = a2:99], [3/3 POST /:bb = bb:99], "
带参数的中间件
在回调中,路由变量和上下文参数不是必需的,因此可以省略而不会出现问题。
$router->any('/:var1/:var2', function () { ... }); $router->any('/:var1/:var2', function ($var1) { ... }); $router->any('/:var1/:var2', function ($var2) { ... }); $router->any('/:var1/:var2', function ($context) { ... }); $router->any('/:var1/:var2', function (mixed $context) { ... }); # Explicit mixed type only PHP 8+ $router->any('/:var1/:var2', function (\VSR\Extend\Router\Context $context) { ... }); $router->any('/:var1/:var2', function (\VSR\Extend\Router\Context $custom_name_context) { ... }); $router->any('/:var1/:var2', function ($var1, $var2) { ... }); $router->any('/:var1/:var2', function ($var1, $context) { ... }); $router->any('/:var1/:var2', function ($var1, mixed $context) { ... }); # Explicit mixed type only PHP 8+ $router->any('/:var1/:var2', function ($var1, \VSR\Extend\Router\Context $context) { ... }); $router->any('/:var1/:var2', function ($var1, \VSR\Extend\Router\Context $custom_name_context) { ... }); $router->any('/:var1/:var2', function ($var2, $context) { ... }); $router->any('/:var1/:var2', function ($var2, mixed $custom_name_context) { ... }); # Explicit mixed type only PHP 8+ $router->any('/:var1/:var2', function ($var2, \VSR\Extend\Router\Context $context) { ... }); $router->any('/:var1/:var2', function ($var2, \VSR\Extend\Router\Context $custom_name_context) { ... }); $router->any('/:var1/:var2', function ($var1, $var2, $context) { ... }); $router->any('/:var1/:var2', function ($var1, $var2, mixed $context) { ... }); # Explicit mixed type only PHP 8+ $router->any('/:var1/:var2', function ($var1, $var2, \VSR\Extend\Router\Context $context) { ... }); $router->any('/:var1/:var2', function ($var1, $var2, \VSR\Extend\Router\Context $custom_name_context) { ... });
中间件队列
使用“非唯一匹配”模式,您可以按添加顺序为每个URI拥有多个回调队列。
$router->get('/aaa', function () { echo "11 "; }, function () { echo "12 "; }, function () { echo "13 "; }); $router->any('/aaa', function () { echo "2 "; }); $router->get('/a*', function () { echo "3 "; }); $router->any('/a*', function () { echo "4 "; }); $router->get('*', function () { echo "5 "; }); $router->any('*', function () { echo "6 "; }); $router->get('/:var', function ($var) { echo "7 "; }); $router->any('/:var', function ($var) { echo "8 "; }); $router->match('GET', '/aaa')->execute(); // output: "11 12 13 2 3 4 5 6 7 8 "
您可以使用上下文的“停止”方法停止队列。
$router->get('/aaa', function () { echo "11 "; }, function () { echo "12 "; }, function () { echo "13 "; }); $router->any('/aaa', function () { echo "2 "; }); $router->get('/a*', function () { echo "3 "; }); $router->any('/a*', function (\VSR\Extend\Router\Context $context) { echo "4 "; $context->stop(); }); $router->get('*', function () { echo "5 "; }); $router->any('*', function () { echo "6 "; }); $router->get('/:var', function ($var) { echo "7 "; }); $router->any('/:var', function ($var) { echo "8 "; }); $router->match('GET', '/aaa')->execute(); // output: "11 12 13 2 3 4 "
支持的中间件类型
// by native function name $router->any('/:haystack/:needle', "stripos"); #-------------------------------------------------- // by function name function callback($var1, $var2, $context) { ... } $router->any('/:var1/:var2', "callback"); #-------------------------------------------------- // by anonymous function $router->any('/:var1/:var2', function ($var1, $var2, $context) { ... }); $router->any('/:var1/:var2', static function ($var1, $var2, $context) { ... }); #-------------------------------------------------- // by arrow function, PHP 7.4+ $router->any('/:var1/:var2', fn($var1, $var2, $context) => ...); $router->any('/:var1/:var2', static fn($var1, $var2, $context) => ...); #-------------------------------------------------- // by variable function $callback1 = function ($var1, $var2, $context) { ... }; $router->any('/:var1/:var2', $callback1); $callback2 = static function ($var1, $var2, $context) { ... }; $router->any('/:var1/:var2', $callback2); #-------------------------------------------------- // by class method class AAA { public function method($var1, $var2, $context) { ... } } $aaa = new AAA(); $router->any('/:var1/:var2', "AAA::method"); // Call first constructor if exists and then method $router->any('/:var1/:var2', [ AAA::class, 'method' ]); // Call first constructor if exists and then method $router->any('/:var1/:var2', [ new AAA(), 'method' ]); // Call method $router->any('/:var1/:var2', [ $aaa, 'method' ]); // Call method #-------------------------------------------------- // by class static method class BBB { public static function method($var1, $var2, $context) { ... } } $bbb = new BBB(); $router->any('/:var1/:var2', "BBB::method"); // Call static method $router->any('/:var1/:var2', [ BBB::class, 'method' ]); // Call static method $router->any('/:var1/:var2', [ new BBB(), 'method' ]); // Call static method $router->any('/:var1/:var2', [ $bbb, 'method' ]); // Call static method #-------------------------------------------------- // by class method with constructor class CCC { public function __construct($context) { ... } public function method($var1, $var2, $context) { ... } } $router->any('/:var1/:var2', "CCC::method"); // Call first constructor and then method $router->any('/:var1/:var2', [ CCC::class, "method" ]); // Call first constructor and then method #-------------------------------------------------- // by class name/object class DDD { public function __invoke($var1, $var2, $context) { ... } } $ddd = new DDD(); $router->any('/:var1/:var2', "DDD"); // Call first constructor if exists and then __invoke $router->any('/:var1/:var2', DDD::class); // Call first constructor if exists and then __invoke $router->any('/:var1/:var2', new DDD()); // Call __invoke $router->any('/:var1/:var2', $ddd); // Call __invoke #-------------------------------------------------- // by anonymous class, PHP 7+ $router->any('/:var1/:var2', new class { public function __invoke($var1, $var2, $context) { ... } }); // Call __invoke