ingenioz-it / router
PHP 路由器
Requires
- php: >=8.2
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
README
PHP 路由器。
免责声明
为了确保该软件包易于集成到您的应用程序中,它基于 PHP 标准推荐 构建:它接收一个 PSR-7 服务器请求 并返回一个 PSR-7 响应。它还使用一个 PSR-11 容器(例如 EDICT)来解决路由处理程序。
它受到了知名框架路由器的启发(有人提到 Laravel 吗?)以及一些大公司内部使用的自建路由器。
它注重质量:可读性、不可变性、无全局状态、100% 代码覆盖率、100% 变异测试得分,以及来自各种静态分析工具的最高级别验证。
关于
安装
composer require ingenioz-it/router
文档
概述
以下是使用此路由器的整个流程
- 创建您的路由
- 实例化路由器
- 处理请求
use IngeniozIT\Router\RouteGroup; use IngeniozIT\Router\Route; use IngeniozIT\Router\Router; // Create your routes $routes = new RouteGroup([ Route::get('/hello', fn() => new Response('Hello, world!')), Route::get('/bye', fn() => new Response('Goodbye, world!')), ]); // Instantiate the router /** @var Psr\Container\ContainerInterface $container */ $container = new Container(); $router = new Router($routes, $container); // Handle the request /** @var Psr\Http\Message\ServerRequestInterface $request */ $request = new ServerRequest(); /** @var Psr\Http\Message\ResponseInterface $response */ $response = $router->handle($request);
基本路由
最简单的路由由一个路径和一个处理程序组成。
路径是一个字符串,处理程序是一个在匹配路由时将被执行的调用。处理程序必须返回一个 PSR-7 ResponseInterface。
Route::get('/hello', fn() => new Response('Hello, world!'));
组织路由
路由组用于包含路由定义。
它还允许您根据应用程序的逻辑直观地组织您的路由。
当您想一次将相同的条件、中间件或属性应用于多个路由时,这非常有用(我们稍后将看到)。
new RouteGroup([ Route::get('/hello', fn() => new Response('Hello, world!')), Route::get('/bye', fn() => new Response('Goodbye, world!')), ]);
路由组可以嵌套以创建继承其父组所有内容的路由层次结构。
new RouteGroup([ Route::get('/', fn() => new Response('Welcome !')), new RouteGroup([ Route::get('/hello', fn() => new Response('Hello, world!')), Route::get('/hello-again', fn() => new Response('Hello again, world!')), ]), Route::get('/bye', fn() => new Response('Goodbye, world!')), ]);
HTTP 方法
您可以指定路由应匹配的 HTTP 方法
Route::get('/hello', MyHandler::class); Route::post('/hello', MyHandler::class); Route::put('/hello', MyHandler::class); Route::patch('/hello', MyHandler::class); Route::delete('/hello', MyHandler::class); Route::options('/hello', MyHandler::class);
如果您想使路由匹配多个 HTTP 方法,可以使用 some
方法
Route::some(['GET', 'POST'], '/hello', MyHandler::class);
您也可以使用 any
方法来匹配所有 HTTP 方法
Route::any('/hello', MyHandler::class);
路径参数
基本用法
您可以通过在路由路径中使用 {}
语法来定义路由参数。
Route::get('/hello/{name}', MyHandler::class);
匹配的参数将可在请求属性中找到。
class MyHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { $name = $request->getAttribute('name'); return new Response("Hello, $name!"); } } Route::get('/hello/{name}', MyHandler::class);
自定义参数模式
默认情况下,参数通过 [^/]+
正则表达式进行匹配(任何非 /
的字符)。
您可以通过使用 where
参数来指定自定义模式
// This route will only match if the name contains only letters Route::get('/hello/{name}', MyHandler::class, where: ['name' => '[a-zA-Z]+']);
组内的自定义参数模式
参数模式也可以为组内的所有路由定义全局模式
$routes = new RouteGroup( [ Route::get('/hello/{name}', MyHandler::class), Route::get('/bye/{name}', MyOtherHandler::class), ], where: ['name' => '[a-zA-Z]+'], );
路由处理程序
闭包
定义路由处理程序的最简单方法是使用闭包。
闭包必须返回一个 PSR-7 ResponseInterface。
Route::get('/hello', fn() => new Response('Hello, world!'));
闭包可以接受参数:请求和一个请求处理程序(路由器本身)。
Route::get('/hello', function (ServerRequestInterface $request) { return new Response('Hello, world!'); }); Route::get('/hello', function (ServerRequestInterface $request, RequestHandlerInterface $router) { return new Response('Hello, world!'); });
RequestHandlerInterface
路由处理程序可以是一个调用,但它也可以是 PSR RequestHandlerInterface。
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\ServerRequestInterface; use Psr\Http\Server\ResponseInterface; class MyHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { return new Response('Hello, world!'); } } Route::get('/hello', new MyHandler());
MiddlewareInterface
有时,您可能希望处理程序能够“拒绝”处理请求,并将其传递给链中的下一个处理程序。
这可以通过使用 PSR MiddlewareInterface 作为路由处理程序来完成
use Psr\Http\Server\MiddlewareInterface; class MyHandler implements MiddlewareInterface { public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if (resourceDoesNotExist()) { // We don't want this handler to continue processing the request, // so we pass the responsability to the next handler return $handler->handle($request); } /* ... */ } } $routes = new RouteGroup([ // This handler will be called first Route::get('/{ressource}', fn() => new MyHandler()), // This handler will be called next Route::get('/{ressource}', fn() => new Response('Hello, world!')), ]);
依赖注入
除了使用闭包或类实例之外,您的处理程序还可以是类名。然后路由器将使用您注入到路由器中的 PSR 容器来解析该类。
Route::get('/hello', MyHandler::class);
路由器将通过在容器上调用 get(MyHandler::class)
来解析此处理程序。这意味着您可以使用容器可以解析为有效路由处理程序的任何值。
附加属性
您可以使用 with
方法向路由添加附加属性。
就像路径参数一样,这些属性将在请求属性中可用。
class MyHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { $name = $request->getAttribute('name'); return new Response("Hello, $name!"); } } // Notice there is no name parameter in the route path Route::get('/hello', MyHandler::class, with: ['name' => 'world']);
还可以为组内的所有路由全局定义属性
$routes = new RouteGroup( [ Route::get('/hello', MyHandler::class), Route::get('/bye', MyOtherHandler::class), ], with: ['name' => 'world'], );
中间件
中间件是可以在路由处理程序被调用之前和之后修改请求和/或响应的类。
它们可以应用于路由组。
$routes = new RouteGroup( [ Route::get('/hello', MyHandler::class), ], middlewares: [ MyMiddleware::class, MyOtherMiddleware::class, ], );
中间件类必须实现 PSR \Psr\Http\Server\MiddlewareInterface
接口。
条件
条件是可调用的,将确定是否应解析路由组。
// This one will be parsed $routes = new RouteGroup( [ Route::get('/hello', MyHandler::class), ], conditions: [ fn(ServerRequestInterface $request) => true, ], ); // This one will NOT be parsed $routes = new RouteGroup( [ Route::get('/hello', MyHandler::class), ], conditions: [ fn(ServerRequestInterface $request) => false, ], );
此外,条件还可以返回一个数组,该数组将添加到请求属性中。
class MyHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request): ResponseInterface { $name = $request->getAttribute('name'); return new Response("Hello, $name!"); } } $routes = new RouteGroup( [ Route::get('/hello', MyHandler::class), ], conditions: [ // This condition will add the 'name' attribute to the request fn(ServerRequestInterface $request) => ['name' => 'world'], ], );
如果条件返回一个数组,则假定应解析路由组。
如果有任何条件返回 false
,则不会解析路由组
// This one will NOT be parsed $routes = new RouteGroup( [ Route::get('/hello', MyHandler::class), ], conditions: [ fn(ServerRequestInterface $request) => true, fn(ServerRequestInterface $request) => false, ], );
命名路由
路由可以命名。
Route::get('/hello', MyHandler::class, name: 'hello_route');
使用路由器,您可以生成命名路由的路径
$router->pathTo('hello_route'); // Will return '/hello'
如果路由有参数,您可以将其作为第二个参数传递
Route::get('/hello/{name}', MyHandler::class, name: 'hello_route'); $router->pathTo('hello_route', ['name' => 'world']); // Will return '/hello/world'
错误处理
此路由器使用自定义异常来处理错误。
以下是这些异常的继承树
IngeniozIT\Router\RouterException
(接口):基异常,所有其他异常都继承自该异常IngeniozIT\Router\EmptyRouteStack
:当路由器没有匹配到任何路由时抛出IngeniozIT\Router\Route\RouteException
:(接口) 路由错误的基础异常IngeniozIT\Router\Route\Exception\InvalidRouteHandler
:当路由处理程序不是一个有效的请求处理程序时抛出IngeniozIT\Router\Route\Exception\InvalidRouteResponse
:当路由处理程序不返回 PSR-7 ResponseInterface 时抛出IngeniozIT\Router\Route\Exception\RouteNotFound
:当调用$router->pathTo
时,路由名称不存在时抛出IngeniozIT\Router\Route\Exception\InvalidRouteParameter
:当调用$router->pathTo
时,参数无效时抛出IngeniozIT\Router\Route\Exception\MissingRouteParameters
:当调用$router->pathTo
时,缺少参数时抛出
IngeniozIT\Router\Middleware\MiddlewareException
:(接口) 中间件错误的基础异常IngeniozIT\Router\Middleware\Exception\InvalidMiddlewareHandler
:当中间件不是一个有效的中间件处理程序时抛出IngeniozIT\Router\Middleware\Exception\InvalidMiddlewareResponse
:当中间件不返回 PSR-7 ResponseInterface 时抛出
IngeniozIT\Router\Condition\ConditionException
:(接口) 条件错误的基础异常IngeniozIT\Router\Condition\Exception\InvalidConditionHandler
:当条件不是一个有效的条件处理程序时抛出IngeniozIT\Router\Condition\Exception\InvalidConditionResponse
:当条件不返回有效响应时抛出