vitorsreis/extend-router

此包已被废弃,不再维护。未建议替代包。

非常优雅、快速且强大的PHP路由器

4.5.0 2023-11-15 01:21 UTC

This package is auto-updated.

Last update: 2024-08-15 03:01:06 UTC


README

Latest Stable Version PHP Version Require License Total Downloads Repo Stars

通过单词树和正则表达式匹配索引,此路由器非常优雅、快速且强大。采用合并中间件队列(非唯一匹配)架构,在路由中提供带缓存、上下文和持久数据的多个交互。单元测试在以下版本通过:5.67.48.18.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'; });

以下是预注册的过滤器

允许/禁止方法

默认列表:GETPOSTPUTPATCHDELETEOPTIONSHEAD

$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