ingenioz-it/router

PHP 路由器

v2.0.3 2024-05-10 21:16 UTC

This package is auto-updated.

Last update: 2024-09-10 22:07:34 UTC


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:当条件不返回有效响应时抛出