monolyth / reroute
Monolyth无框架的极灵活路由器
Requires
- php: >=8.1
- jakeasmith/http_build_url: ^1.0.0
- league/pipeline: ^0.1.0
- psr/http-message: ^1.0
Requires (Dev)
- gentry/gentry: ^0.13.12
- gentry/toast: ^0.1.1
- laminas/laminas-diactoros: ^2.2
- toast/unit: ^2.1
- dev-master
- 5.3.2
- 5.3.1
- 5.3.0
- 5.2.0
- 5.1.0
- 5.0.0
- 4.2.7
- 4.2.6
- 4.2.5
- 4.2.4
- 4.2.3
- 4.2.2
- 4.2.1
- 4.2.0
- 4.1.3
- 4.1.2
- 4.1.1
- 4.1.0
- 4.0.0
- 3.9.0
- 3.8.0
- 3.7.0
- 3.6.7
- 3.6.6
- 3.6.5
- 3.6.4
- 3.6.3
- 3.6.2
- 3.6.1
- 3.6.0
- 3.5.0
- 3.4.7
- 3.4.6
- 3.4.5
- 3.4.4
- 3.4.3
- 3.4.2
- 3.4.1
- 3.4.0
- 3.3.4
- 3.3.3
- 3.3.2
- 3.3.1
- 3.3.0
- 3.2.2
- 3.2.1
- 3.2.0
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.0
- 1.1.8
- 1.1.7
- 1.1.6
- 1.1.5
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.0
- 0.1.9
- 0.1.8
- 0.1.7
- 0.1.6
- 0.1.5
- 0.1.4
- 0.1.3
- 0.1.2
- 0.1.1
- 0.1.0
- 0.0.4
- 0.0.3
- 0.0.2
- 0.0.1
- dev-dependabot/composer/laminas/laminas-diactoros-2.24.2
- dev-develop
This package is auto-updated.
Last update: 2024-09-25 01:52:24 UTC
README
灵活的PHP5 HTTP路由器,支持各种类型的URL匹配、URL参数、自定义状态处理和URL生成。Reroute旨在在任何类型的项目中使用。
安装
Composer(推荐)
$ composer require monolyth/reroute
手动安装
- 获取代码;
- 从GitHub等克隆仓库;
- 下载ZIP文件(例如从GitHub)并解压。
- 让项目识别Reroute
- 在PSR-4自动加载器中注册
/path/to/reroute/src
为命名空间Monolyth\\Reroute\\
(推荐); - 或者,手动
include
所需的文件。
- 在PSR-4自动加载器中注册
基本用法
响应请求的URL
由于Reroute路由器响应HTTP请求,我们使用when
方法来定义一个有效的URL
<?php use Monolyth\Reroute\Router; $router = new Router('http://example.com'); $state = $router->when('/some/url/', 'some-state');
when
返回一个State
新对象作为指定URL的响应,可选地通过给定的名称标识。然后必须定义该状态将响应哪些HTTP动词以及如何响应
<?php $state->get(new Zend\Diactoros\Response\HtmlResponse('Hello world!')); $state->post(new Zend\Diactoros\Response\EmptyResponse(500));
目前支持的HTTP动词方法有get
、post
、put
、delete
、head
和options
。还有一个特殊的any
方法,它用一个单独的响应涵盖了它们所有。
响应必须是Psr\Http\Message\ResponseInterface
的实例。然而,为了方便起见,你也可以定义其他作为响应的东西
- 如果它是一个可调用的,它会使用从URL中提取的参数被调用,直到它不再可调用。
- 如果它是一个字符串并且存在一个具有该名称的类,它将是该类的实例。
- 如果该类是可调用的,它将被调用,并且结果被使用。
- 否则,该类本身必须实现
Psr\Http\Message\ResponseInterface
。
因此,以下形式是等效的
<?php use Zend\Diactoros\Response\HtmlResponse; $router->when('/some/url/')->get(function () { return new HtmlsReponse('Hello world!'); }); $router->when('/some/url/')->get(new HtmlResponse('Hello world!')); class Foo { public static function getInstance() { return new Foo; } public function __invoke() { return new HtmlResponse('Hello world!'); } } $router->when('/some/url/')->get(new Foo); $router->when('/some/url/')->get(Foo::class); $router->when('/some/url/')->get(['Foo', 'getInstance']);
命名状态
when
的第二个参数是(希望是!)状态的名称。这可以在稍后生成路由时使用(见下文)。如果不需要名称,它可以是null
(也是默认值)。您还可以使用Router::get
在稍后通过名称检索特定的状态(例如,也许您的路由非常复杂且分散在多个文件中)。
<?php
$router->when('/the/url/', 'myname')->get('handler');
$state = $router->get('myname'); // Ok!
$state instanceof Monolyth\Reroute\State; // true
解析请求
定义路由后,在您的入口控制器中的某个地方,您可能希望实际解析请求。然后可以使用例如Zend Diactoros发出ResponseInterface
对象。
<?php use Zend\Diactoros\ServerRequestFactory; use Zend\Diactoros\Response\SapiEmitter; if ($response = $router(ServerRequestFactory::fromGlobals())) { $emitted = new SapiEmitter; $emitter->emit($response); } else { // 404! }
路由器必须用当前请求作为参数调用。注意,在调用之前,将无法生成路由。如果您需要这样做(例如,在CRON作业中发送电子邮件),您可以手动调用路由器。
调用路由器开始一个管道。通过调用路由器的pipe
方法,您可以向堆栈添加中间件。
如果找到了当前URL的有效状态,则管道返回其返回值。否则,它将解析为null
。
要模拟与实际请求类型不同的请求类型,只需更改
$_SERVER['REQUEST_METHOD']
。
传递参数
您的URL实际上是正则表达式,因此您可以匹配变量并将它们传递到回调中
<?php $router->when("/(?'name'\w+)/")->get(function (string $name) { return "Hi there, $name!"; });
变量可以是命名的(在这种情况下,您将它们传递到回调的顺序无关紧要 - Reroute会在可调用中进行反射以确定最佳匹配)或匿名的(在这种情况下,它们将按顺序传递)。
简写占位符
对于更简单的URL,您还可以使用一些简写占位符。以下三个语句是相同的
<?php $router->when("/(?'param'.*?)/"); $router->when('/:param/'); $router->when('/{param}/');
当使用占位符时,请注意,对参数类型的控制较少。使用正则表达式更强大,因为您可以强制例如 "/(?'id'\d+)/"
匹配整数,甚至可以在可调用对象中类型提示。
检查当前请求
通过将参数类型提示为 Psr\Http\Message\RequestInterface
的实例,您可以注入原始请求对象并检查使用的方法(或当然任何其他内容)
<?php use Psr\Http\Message\RequestInterface; use Zend\Diactoros\Response\HtmlResponse; $router->when('/some/url/')->any(function (RequestInterface $request) { switch ($request->getMethod()) { case 'POST': // Perform some action case 'GET': return new HtmlResponse('ok'); default: return new HtmlResponse($request->getMethod()." method not allowed.", 405); } });
引用其他回调
类型提示为 callable
且匹配已定义操作(大写)的参数可用于“链式”到另一个操作。因此,以下模式对于需要对例如 POST
进行特殊处理的 URL 是常见的
<?php $router->when('/some/url/')->then('my-state', function() { return 'This is a normal page'; })->post(function (callable $GET) { // Perform some action... return $GET; });
注意不需要重新传递任何 URL 参数给可调用对象;它们会自动注入。因此,对 get
和 post
等的调用可能会接受/识别不同顺序的不同参数。
自定义动词回调不会“冒泡”到路由链。因此,在
/foo/
上特别禁用POST
不会影响/foo/bar/
的默认行为。
如果注入的操作在此状态下不可用,则返回 405 错误。
分组
when
的可选第三个参数是一个可调用对象,它期望一个参数:一个新的(子)路由器。使用 when
在子路由器上定义的所有路由都将继承父路由器的 URL
<?php $router->when('/foo/', null, function ($router) { $router->when('/bar/')->get('I match /foo/bar/!'); })->get('I match /foo/!);
分组 when
调用的结果是本身也是一个状态,它可以被管道和/或解析。为了方便,此状态也可以使用 ->when('/', ...)
在回调内部定义。因此,而不是这个(这本身是完全有效的)
<?php $router->when('/', 'home', function ($router) { // Looooong list of subroutes under /... })->pipe($somePipe)->get('Home!');
...您还可以编写(更易读的)
<?php $router->when('/', null, function ($router) { $router->when('/', 'home')->get('Home!'); // Looooong list of subroutes under /... })->pipe($somePipe);
管道中间件
由于状态是管道化的,您可以在任何位置添加一个或多个对 pipe
方法的调用以添加中间件
<?php $router->when('/restricted/') ->pipe(function ($payload) { if (!user_is_authenticated()) { return new RedirectResponse('/login/'); } return $payload; }) ->get(new Zend\Diactoros\Response\HtmlResponse('For authenticated eyes only!'));
您可以根据需要多次调用 pipe
。如果管道在任何地方短路,则子路由器将不会执行。
当使用命名参数时,管道化的可调用对象可以可选地指定它还想要使用哪些参数
<?php $router->when("/(?'foo':\d+)/") ->pipe(function ($payload, int $foo) { if ($foo != 42) { // return error response or something... } return $payload; });
这与状态解析的可调用对象类似,但有一个 始终 的第一个参数 $payload
,并且无法注入 $request
。
此功能的一个常见用途是为一组路由中的第一个 $language
参数定义管道,并将某些环境变量设置为它的值,以便用于所有底层路由。
$payload
是,根据定义,是 Psr\Http\Message\RequestInterface
的一个实例。一旦任何管道返回 Psr\Http\Message\ResponseInterface
的一个实例,一切都会停止,它被指定为当前状态下此路由的选定响应。此功能的一个常见用途是在用户试图访问页面 A 但需要先在页面 B 上执行某些操作(例如登录)时重定向用户。
生成 URL
要为定义的命名状态生成 URL,请使用 generate
方法
<?php $router->when('/:some/:params/', 'myname')->get('handler'); echo $router->generate('myname', ['some' => 'foo', 'params' => 'bar']); // outputs: /foo/bar/
generate
的可选第三个参数是一个布尔值,告诉 generate
如果用户已经在当前主机上,是否应该优先选择没有方案/主机名的路由。默认值为 true。如果第三个参数为 false
,上述示例可能输出 https:///foo/bar/
。这在生成的路由需要在应用程序外部使用时很有用,例如在发送的电子邮件中。
生成仅适用于命名状态,因为匿名状态显然只能通过其实际 URL(在这种情况下,您可以直接将其硬编码)检索。如果您的 URL 很可能随着时间的推移而改变,请使用命名状态!
级联参数
在子路由器中生成路由时,所有在父路由器中设置的命名参数都会自动注入到传递的参数中。
示例
<?php use Zend\Diactoros\Response\RedirectResponse; $router->when("/(?'language'[a-z]{2})/", null, function ($router) { $router->when('/', 'home')->get(function () { /* some page */ }); $router->when('/home/')->get(function () use ($router) { return new RedirectResponse($router->generate('home')); }); }); // Now, assuming we navigate to `/en/home/` we get redirected to `/en/`!
当然,您也可以将 string $langauge
作为参数注入到子路由中,但这会变得繁琐。
注意,在调用
generate
方法的第二个参数中传入的参数优先级高于之前匹配的参数。这意味着您可以通过以下方式在上述示例中将/en/home/
显式重定向到/nl/
:$router->generate('home', ['language' => 'nl']);
。
处理404和其他错误
$router()
的结果如果没有找到匹配项将是 null
,这意味着您需要显示404错误页面。最佳实践是将此调用包装在 try/catch
块中,并在出错时抛出异常。这样,您就可以在 catch
块中显示一个通用的500错误页面(并可能进行一些日志记录)。
具有默认参数的路由
在某些情况下,您可以在回调中指定一个“默认参数”很有用。例如,当调用 /user/
应显示当前登录用户的个人资料,而当调用 /user/:id/
时显示指定用户的个人资料。
在Reroute中,通过在URL中将参数设为可选并在回调中提供默认值,可以实现这一点。对于上述示例,例如可以这样操作:
<?php $router->when("/user/(?'id'\d+/)?")->then(function ($id = null) { if (!isset($id)) { $id = $GLOBALS['user']->id; } // ...return profile for $id... return "<h1>User profile for $id</h1>"; });
可以通过在占位符后加上问号使简写URL匹配样式成为可选的
<?php $router->when('/user/:id?/'); $router->when('/user/{id}?/');
请注意,任何以斜杠结尾的参数都会被删除,因为斜杠通常保留用于参数分隔。此外,连续的斜杠将被自动合并。
支持的HTTP方法
Reroute支持GET、POST、PUT、DELETE和OPTIONS HTTP方法。每个方法都有一个相应命名的 State
对象上的方法,可以用来定义响应。如果没有定义响应,Reroute将返回一个包含405状态码的 Laminas\Diactoros\Response\EmptyResponse
实例。
然而,有两种特殊情况。如果已定义了GET响应,但没有显式定义POST响应,则GET响应将被用来响应POST操作。通常您应该通过实际执行一些操作并将页面重定向到某处来响应POST,以防止重复提交,但这样至少您的网站不会崩溃。
另一个例外是HTTP HEAD方法。它通过获取GET响应,删除主体,添加内容长度标头,并返回一个状态为200的 Laminas\Diactoros\Response\EmptyResponse
来自动支持。这显然意味着定义了一个GET响应;否则将返回405空响应。