nia / routing
路由组件通过过滤器将请求路由到将请求转换为响应的处理程序。
该软件包的官方仓库似乎已消失,因此软件包已被冻结。
Requires
- php: >=7.0.0
- nia/requestresponse: *
This package is not auto-updated.
Last update: 2022-03-10 22:19:08 UTC
README
路由组件通过过滤器将请求路由到将请求转换为响应的处理程序。
路由器非常灵活,因为没有HTTP、CLI、语音或其他任何环境上下文限制 - 甚至没有配置文件或黑注解魔法。它使用一个独特的条件概念来找到最佳匹配的路由,拦截过滤器来封装逻辑和处理程序来生成响应 - 所有这些都是环境无关的。
安装
使用Composer需要此软件包。
composer require nia/routing
测试
要运行单元测试,请使用以下命令
$ cd /path/to/nia/component/
$ phpunit --bootstrap=vendor/autoload.php tests/
条件
路由有一个条件(或使用复合多个条件)来决定最佳匹配的路由。
该组件提供了一些条件,但您也可以通过实现Nia\Routing\Condition\ConditionInterface
接口来编写自己的条件,以适应更具体的用例。
条件 | 描述 |
---|---|
Nia\Routing\Condition\ArgumentCondition |
检查请求参数是否已设置的条件。 |
Nia\Routing\Condition\ClosureCondition |
允许将闭包设置为条件。 |
Nia\Routing\Condition\CompositeCondition |
此条件允许您使用逻辑AND组合多个条件。 |
Nia\Routing\Condition\MethodCondition |
检查请求是否使用了特定方法。 |
Nia\Routing\Condition\NullCondition |
条件始终为true |
Nia\Routing\Condition\OrCompositeCondition |
此条件允许您使用逻辑OR组合多个条件。 |
Nia\Routing\Condition\PathCondition |
检查请求路径是否与静态路径匹配的条件。 |
Nia\Routing\Condition\RegexPathConidition |
检查请求路径是否与正则表达式匹配。 |
过滤器
请求可以由拦截过滤器过滤到处理程序和生成的响应。过滤器可用于将常见业务逻辑从处理程序移动到过滤器。例如 "如果会话未启动,则显示禁止页面" 或运行 AB测试。
组件提供了一些条件,但您也可以通过实现Nia\Routing\Filter\FilterInterface
接口来编写自己的条件。
过滤器 | 描述 |
---|---|
Nia\Routing\Filter\CompositeFilter |
组合多个过滤器。 |
Nia\Routing\Filter\NullFilter |
此过滤器不执行任何操作。如果您不需要过滤器,请设置此过滤器。 |
Nia\Routing\Filter\RegexPathContentFillerFilter |
此过滤器通过使用正则表达式和命名匹配填充请求路径的context 参数,为filterRequest 。您可以与此过滤器结合使用Nia\Routing\Condition\RegexPathCondition 。 |
处理程序
处理程序是实现,它使用请求并创建响应。处理程序可以是控制器的操作、闭包(使用Nia\Routing\Handler\ClosureHandler
)或任何其他内容,只需实现NiaRoutingModule\Handler\HandlerInterface
。
示例:简单的CLI应用程序
以下示例向您展示了一个简单的命令行应用程序,如果您提供了姓名和年龄,它将欢迎您。这个示例不常见,因为它最好是构建一个CLI路由的界面(见示例:简单HTTP路由器),但它可以很好地展示路由器的使用。
$router = new Router(); // route if age and name are set. // ------------------------------ $condition = new CompositeCondition([ new ArgumentCondition('age'), new ArgumentCondition('name') ]); $handler = new ClosureHandler(function (RequestInterface $request, WriteableMapInterface $context) { $name = $request->getArguments()->get('name'); $age = $request->getArguments()->get('age'); $content = sprintf("Hello, %s! You are %d years old.\n", $name, $age); $response = $request->createResponse(); $response->setContent($content); return $response; }); $router->addRoute(new Route($condition, new NullFilter(), $handler)); // route if no name or age set. // ---------------------------- $handler = new ClosureHandler(function (RequestInterface $request, WriteableMapInterface $context) { $content = 'Just call this script with --age=your-age and --name=your-name' . PHP_EOL; $response = $request->createResponse(); $response->setContent($content); return $response; }); $router->addRoute(new Route(new NullCondition(), new NullFilter(), $handler)); // run the application. $request = new CliRequest($_SERVER['argv']); $response = $router->handle($request, new Map()); echo $response->getContent(); exit($response->getStatusCode());
示例:用户是否登录的过滤器
此示例向您展示如何将常见的控制器逻辑移动到一个过滤器中。在这种情况下:“没有启动会话,显示403禁止访问”。
/** * Filter to check if the user is logged in. */ class UserIsLoggedInFilter implements FilterInterface { /** * * {@inheritDoc} * * @see \Nia\Routing\Filter\FilterInterface::filterRequest($request, $context) */ public function filterRequest(RequestInterface $request, WriteableMapInterface $context): ResponseInterface { // if the session is started the request can pass to the handler. if (isset($_SESSION)) { throw new IgnoreFilterException(); } // user has no session, so the request is forbidden. $response = $request->createResponse(); $response->setStatusCode(403); $response->setContent('You are not logged in.'); return $response; } /** * * {@inheritDoc} * * @see \Nia\Routing\Filter\FilterInterface::filterResponse($response, $context) */ public function filterResponse(ResponseInterface $response, WriteableMapInterface $context): ResponseInterface { return $response; } }
示例:运行A/B测试
如果您需要运行A/B测试,您必须修改模板和/或控制器。如果您使用拦截过滤器(如本例所示),则可以在不修改模板或控制器的任何代码的情况下运行A/B测试。
/** * AB test to check if the brighter layout increases the conversion rate. * The filter adds (if the test is running) an additional css file to the <head>-tag. */ class BrighterLayoutAbTestFilter implements FilterInterface { /** * Whether the test is forced. * * @var bool */ private $forceTest = false; /** * * {@inheritDoc} * * @see \Nia\Routing\Filter\FilterInterface::filterRequest($request, $context) */ public function filterRequest(RequestInterface $request, WriteableMapInterface $context): ResponseInterface { $this->forceTest = $request->getArguments()->has('force-test'); throw IgnoreFilterException; } /** * * {@inheritDoc} * * @see \Nia\Routing\Filter\FilterInterface::filterResponse($response, $context) */ public function filterResponse(ResponseInterface $response, WriteableMapInterface $context): ResponseInterface { // only set brighter layout if the test is forced or is one of two. if ($this->forceTest || mt_rand(0, 1) === 0) { // brighter layout to test. $replacement = '<link href="/css/brighter.css" rel="stylesheet" type="text/css" />'; // append new css-file to header. $content = $response->getContent(); $content = str_replace('</head>', $replacement . '</head>', $content); $response->setContent($content); } return $response; } }
示例:简单HTTP路由器
以简单方式使用路由器会产生大量代码,并降低可用性。一个好的方法是编写一个用于路由环境的门面,如下所示,以避免这种效果。
/** * Simple router facade for common HTTP routing usages. */ class HttpRouterFacade { /** @var RouterInterface */ private $router = null; /** * Constructor. * * @param RouterInterface $router */ public function __construct(RouterInterface $router) { $this->router = $router; } /** * Creates a HTTP/GET route. * * @param string $pathRegex * The regex for the route. * @param HandlerInterface $handler * The used handler to handle the match. * @param FilterInterface $filter * Optional filter. * @return HttpRouterFacade Reference to this instance. */ public function get($pathRegex, HandlerInterface $handler, FilterInterface $filter = null): HttpRouterFacade { $condition = new CompositeCondition([ new MethodCondition(HttpRequestInterface::METHOD_GET), new RegexPathCondition($pathRegex) ]); $filter = new CompositeFilter([ $filter ?? new NullFilter(), new RegexPathContextFillerFilter($pathRegex) ]); $this->router->addRoute(new Route($condition, $filter, $handler)); return $this; } /** * Creates a HTTP/POST route. * * @param string $pathRegex * The regex for the route. * @param HandlerInterface $handler * The used handler to handle the match. * @param FilterInterface $filter * Optional filter. * @return HttpRouterFacade Reference to this instance. */ public function post($pathRegex, HandlerInterface $handler, FilterInterface $filter = null): HttpRouterFacade { $condition = new CompositeCondition([ new MethodCondition(HttpRequestInterface::METHOD_POST), new RegexPathCondition($pathRegex) ]); // for POST add a CSRF token filter to check if the CSRF is valid. $filter = new CompositeFilter([ $filter ?? new NullFilter(), new CsrfTokenFilter(), new RegexPathContextFillerFilter($pathRegex) ]); $this->router->addRoute(new Route($condition, $filter, $handler)); return $this; } } $router = new Router(); $httpRouter = new HttpRouterFacade($router); // start page: / $httpRouter->get('@^/$@', new ClosureHandler(function (RequestInterface $request, WriteableMapInterface $context) { $response = $request->createResponse(); $response->setContent('<h1>Startpage</h1>'); return $response; })) -> // hello-page: /hello/your-name/ get('@^/hello/(?P<name>\w+)/?$@', new ClosureHandler(function (RequestInterface $request, WriteableMapInterface $context) { $response = $request->createResponse(); $response->setContent('<h1>Welcome ' . $context->get('name') . '</h1>'); return $response; })) -> // contact form: /company/contact/ get('@^/company/contact/?$@', new ClosureHandler(function (RequestInterface $request, WriteableMapInterface $context) { $response = $request->createResponse(); $response->setContent('<h1>Contact</h1><form>...</form>'); return $response; })) -> // contact form: /company/contact/ post('@^/company/contact/?$@', new ClosureHandler(function (RequestInterface $request, WriteableMapInterface $context) { $response = $request->createResponse(); $response->setContent('<h1>Contact</h1><p>Thank you for your message!</p>'); return $response; })); // ----------- // [...] // handle the request $response = $router->handle($request, $context); // [...]