alexmorbo/reactphp-router

dev-master 2023-02-08 10:18 UTC

This package is auto-updated.

Last update: 2024-09-11 12:51:46 UTC


README

底层使用 ReactPHP/HTTP 构建的路由包

目录

安装

composer require commandstring/router

入门

首先需要创建一个 ReactPHP SocketServer

$socket = new \React\Socket\SocketServer("127.0.0.1:8000");

然后创建一个路由实例

$router = new \Router\Http\Router($socket, true);

第二个参数是是否启用开发模式,您可以在这里了解开发模式

创建一些路由,然后开始监听请求

$router->listen();

路由

您可以通过使用 match 方法添加路由

use Router\Http\Methods;

$router->match([Methods::GET], "/", function() { /* ... */ });

您可以通过将它们添加到数组中来监听更多方法

$router->match([Methods::GET, Methods::POST], "/", function() { /* ... */ });

路由简写

提供单个请求方法的简写

$router->get('pattern', function() { /* ... */ });
$router->post('pattern', function() { /* ... */ });
$router->put('pattern', function() { /* ... */ });
$router->delete('pattern', function() { /* ... */ });
$router->options('pattern', function() { /* ... */ });
$router->patch('pattern', function() { /* ... */ });
$router->head('pattern', function() { /* ... */ });

您可以使用此简写来访问任何方法的路由

$router->all('pattern', function() { /* ... */ });

路由模式

路由模式可以是静态的或动态的

  • 静态路由模式 不包含动态部分,必须与当前 URL 的 path 部分完全匹配。
  • 动态路由模式 包含可以随每个请求而变化的动态部分。这些变化的部分称为 子模式,并使用 Perl 兼容的正则表达式 (PCRE) 或占位符定义

静态路由模式

静态路由模式是表示 URI 的常规字符串。它将直接与当前 URL 的 path 部分进行比较。

示例

  • /about
  • /contact

使用示例

$router->get('/about', function($req, $res) {
    $res->getBody()->write("Hello World");
    return $res;
});

基于 PCRE 的动态路由模式

此类路由模式包含可以随每个请求而变化的动态部分。这些变化的部分称为 子模式,并使用正则表达式定义。

示例

  • /movies/(\d+)
  • /profile/(\w+)

动态路由模式中常用的基于 PCRE 的子模式包括

  • \d+ = 一个或多个数字(0-9)
  • \w+ = 一个或多个单词字符(a-z 0-9 _)
  • [a-z0-9_-]+ = 一个或多个单词字符(a-z 0-9 _)和连字符 (-)
  • .* = 任何字符(包括 /),零个或多个
  • [^/]+ = 除了 / 之外的任何字符,一个或多个

注意:可能需要PHP PCRE 技巧表

在动态 PCRE-based 路由模式中定义的 子模式 被转换为传递给路由处理函数的参数。前提是这些子模式需要定义为 括号子模式,这意味着它们应该用括号括起来

// Bad
$router->get('/hello/\w+', function($req, $res, $name) {
    $res->getBody()->write('Hello '.htmlentities($name));
    return $res;
});

// Good
$router->get('/hello/(\w+)', function($req, $res, $name) {
    $res->getBody()->write('Hello '.htmlentities($name));
    return $res;
});

注意:在路由模式开头处非常推荐但不强制使用前导 /

当定义多个子模式时,将按照它们定义的顺序将结果 路由处理参数 传递给路由处理函数

$router->get('/movies/(\d+)/photos/(\d+)', function($req, $res, $movieId, $photoId) {
    $res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
    return $res;
});

基于占位符的动态路由模式

此类路由模式与基于PCRE的动态路由模式相同,但有一点区别:它们不是使用正则表达式进行模式匹配,而是使用更简单的占位符。占位符是包围在花括号中的字符串,例如{name}。您不需要在占位符周围添加括号。

示例

  • /movies/{id}
  • /profile/{username}

占位符比PRCE更容易使用,但提供的控制能力更少,因为它们在内部被转换为匹配任何字符的PCRE(.*)。

$router->get('/movies/{movieId}/photos/{photoId}', function($req, $res, $movieId, $photoId) {
    $res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
    return $res;
});

注意:占位符的名称不需要与传递给路由处理函数的参数名称匹配。

$router->get('/movies/{foo}/photos/{bar}', function($req, $res, $movieId, $photoId) {
    $res->getBody()->write('Movie #'.$movieId.', photo #'.$photoId);
    return $res;
});

可选路由子模式

可以通过在子模式后添加一个?来使路由子模式可选。以博客URL的形式为例,如/blog(/year)(/month)(/day)(/slug)

$router->get(
	'/blog(/\d+)?(/\d+)?(/\d+)?(/[a-z0-9_-]+)?',
	function($req, $res, $year = null, $month = null, $day = null, $slug = null) {
		if (!$year) { 
			$res->getBody()->write("Blog Overview");
			return $res;
		}
		
		if (!$month) {
			$res->getBody()->write("Blog year overview");
			return $res;
		}
		
		if (!$day) {
			$res->getBody()->write("Blog month overview");
			return $res;
		}
		
		if (!$slug) {
			$res->getBody()->write("Blog day overview");
			return $res;
		}
		
		$res->getBody()->write('Blogpost ' . htmlentities($slug) . ' detail');
		return $res;
	}
);

上面的代码片段响应以下URL:/blog/blog/year/blog/year/month/blog/year/month/day/blog/year/month/day/slug

注意:使用可选参数时,很重要的一点是将子模式的起始/放在子模式内部。不要忘记为可选参数设置默认值。

不幸的是,上面的代码片段还响应像/blog/foo这样的URL,并声明需要显示概览——这是不正确的。可以通过扩展括号中的子模式,使其包含其他可选子模式来使可选子模式连续:模式应类似于/blog(/year(/month(/day(/slug))))而不是之前的/blog(/year)(/month)(/day)(/slug)

$router->get('/blog(/\d+(/\d+(/\d+(/[a-z0-9_-]+)?)?)?)?', function($req, $res, $year = null, $month = null, $day = null, $slug = null) {
    // ...
});

注意:强烈建议始终定义连续的可选参数。

使用量词来要求URL中正确数量的数字

$router->get('/blog(/\d{4}(/\d{2}(/\d{2zz}(/[a-z0-9_-]+)?)?)?)?', function($req, $res, $year = null, $month = null, $day = null, $slug = null) {
    // ...
});

控制器

定义路由时,您可以选择传递一个匿名函数或包含一个类及其要调用的静态方法的数组。此外,您的控制器必须返回PSR7响应接口的实现。

匿名函数控制器

$router->get("/home", function ($req, $res) {
    $res->getBody()->write("Welcome home!");
    return $res;
});

类控制器

我有一个具有静态方法的类,您的处理器必须是静态方法!

class Home {
	public static function handler($req, $res) {
		$res->getBody()->write("Welcome home!");
		return $res;
	}
}

然后,我用一个数组替换匿名函数,第一个元素是类字符串,第二个键是静态方法名称。

$router->get("/home", [Home::class, "handler"]);

404处理器

定义404处理器是必需的,与创建路由类似。您还可以为不同的模式提供不同的404页面。

要设置404处理器,您可以调用map404方法,并插入一个用于第一个参数的模式,然后是第二个参数的控制器。

$router->map404("/(.*)", function ($req, $res) {
	$res->getBody()->write("{$req->getRequestTarget()} is not a valid route");
	return $res;
});

500处理器

定义500处理器是推荐的,与映射404处理器完全相同。

$router->map500("/(.*)", function ($req, $res) {
	$res->getBody()->write("An error has happened internally :(");
	return $res;
});

注意:在开发模式下,您的500错误处理器将被覆盖

中间件

中间件是一种连接MVC应用程序中的模型和视图的软件,它促进了这两个组件之间的通信和数据流,同时也提供了一层抽象,解耦模型和视图,允许它们在不了解其他组件操作细节的情况下进行交互。

一个好的例子是在用户进入受限制页面之前确保用户是管理员。您可以在每个管理员页面上的路由控制器中这样做,但这将是多余的。或者,对于后中间件,您可能有一个返回JSON响应的REST API。您可以在后中间件中确保JSON响应没有损坏。

前置中间件

您可以通过提供方法、模式和控制器来定义类似路由的前置中间件。请注意,在使用beforeMiddleware时,您期望创建一个实现ResponseInterface的对象并将其传递给主路由。

$router->beforeMiddleware("/admin?(.*)", function (ServerRequest $req, Closure $next) {
	if (!isAdmin()) {
		return new RedirectResponse("/", 403);
	}

	return $next(new Response);
});

在主路由中的后中间件

如果路由器检测到后中间件,则第三个参数将是一个闭包,其功能类似于前置中间件

$router->all("/admin/roster/json", function (ServerRequest $req, Response $res, Closure $next) {
	/*
		some code that gets the admin roster and converts to json
	*/

	$res->getBody()->write($jsonString);
	$res->withHeader("content-type", "application/json");

	return $next($res);
});

后置中间件

$router->afterMiddleware("/admin?(.*)", function (ServerRequest $req, ResponseInterface $res) {
	/*
		some code that makes sure the body is a valid json string
	*/

	if (!jsonValid((string)$res->getBody())) {
		return new EmptyResponse(500);
	}

	return $res;
});

关于中间件的特别说明,您可以通过在 next 关闭的第二个参数中提供变量,将 beforeMiddleware 中的变量传递到主路由,或将主路由中的变量传递到 afterMiddleware。

模板引擎集成

您可以使用 CommandString/Env 将模板引擎对象存储在单例中。然后您可以轻松地获取它,而无需尝试将其传递给控制器。

use CommandString\Env\Env;

$env = new Env;
$env->twig = new Environment(new \Twig\Loader\FilesystemLoader("/path/to/views"));

// ...

$router->get("/home", function ($req, $res) {
	return new HtmlResponse($env->get("twig")->render("home.html"));\\\
});

响应请求

所有控制器 必须 返回 \Psr\Http\Message\ResponseInterface\React\Promise\PromiseInterface 的实现。您可以使用传递给控制器的预置响应对象,或者实例化自己的响应对象。我建议您查看 HttpSoft/Response 了解预构建的响应类型。这还包括在路由中,因为它用于开发模式。

$response = new HttpSoft\Response\HtmlResponse('<p>HTML</p>');
$response = new HttpSoft\Response\JsonResponse(['key' => 'value']);
$response = new HttpSoft\Response\JsonResponse("{key: 'value'}");
$response = new HttpSoft\Response\TextResponse('Text');
$response = new HttpSoft\Response\XmlResponse('<xmltag>XML</xmltag>');
$response = new HttpSoft\Response\RedirectResponse('https/example.com');
$response = new HttpSoft\Response\EmptyResponse();

运行和高级用法

如果您想更精细地控制 HTTP 服务器,可以使用 getHttpServer 方法创建并返回 HttpServer 对象。

$httpServer = $router->getHttpServer();

除了可以获取 HTTP 服务器外,您还可以使用 getSocketServer 方法获取套接字服务器。

$socketServer = $router->getSocketServer();

开发模式

目前,开发模式只做一件事。当您的路由抛出异常时,它会以响应的形式返回异常和堆栈跟踪,而不是将其输出到控制台。

Nodemon

我建议在开发时使用 nodemon,因为它会在每个文件更改时重新启动您的服务器。要安装 nodemon,您需要 nodejs 和 npm。

npm install -g nodemon

然后在项目目录的根目录下创建一个名为 nodemon.json 的新文件,并将以下内容放入其中

{
    "verbose": false,
    "ignore": [
        ".git",
        ".idea"
    ],
    "execMap": {
        "php": "php"
    },
    "restartable": "r",
    "ext": "php,html,json"
}

之后,不要使用 php index.php 来启动您的服务器,而是使用 nodemon index.php 并更改文件。您会看到服务器正在因为文件更改而重新启动。现在,当您更改文件时,无需反复重新启动服务器!如果需要,您还可以在控制台输入 r 以手动重新启动!