nia/routing

路由组件通过过滤器将请求路由到将请求转换为响应的处理程序。

该软件包的官方仓库似乎已消失,因此软件包已被冻结。

1.0.0 2016-02-07 00:01 UTC

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);

	// [...]