rarerloop/router

一个受 Laravel API 启发的强大 PHP 路由器,用于 PSR7 消息


README

Latest Stable Version CI Coverage Status

一个基于 AltoRouter 构建,但受 Laravel API 启发的简单 PHP 路由器。

安装

composer require rareloop/router

用法

创建路由

映射

创建路由是通过使用 map 函数完成的

use Rareloop\Router\Router;

$router = new Router;

// Creates a route that matches the uri `/posts/list` both GET 
// and POST requests. 
$router->map(['GET', 'POST'], 'posts/list', function () {
    return 'Hello World';
});

map() 需要 3 个参数

  • methods (数组):匹配请求方法的列表,有效值
    • GET
    • POST
    • PUT
    • PATCH
    • DELETE
    • OPTIONS
  • uri (字符串):要匹配的 URI
  • action (函数|string):要么是一个闭包,要么是一个控制器字符串

路由参数

可以使用 {keyName} 语法在路由上定义参数。当一个匹配包含参数的路由时,将传递一个 RouteParams 对象实例到动作。

$router->map(['GET'], 'posts/{id}', function(RouteParams $params) {
    return $params->id;
});

如果您需要向参数添加约束,可以将正则表达式模式传递到定义的 Routewhere() 函数

$router->map(['GET'], 'posts/{id}/comments/{commentKey}', function(RouteParams $params) {
    return $params->id;
})->where('id', '[0-9]+')->where('commentKey', '[a-zA-Z]+');

// or

$router->map(['GET'], 'posts/{id}/comments/{commentKey}', function(RouteParams $params) {
    return $params->id;
})->where([
    'id', '[0-9]+',
    'commentKey', '[a-zA-Z]+',
]);

可选路由参数

有时您的路由参数需要是可选的,在这种情况下,您可以在参数名称后添加一个 ?

$router->map(['GET'], 'posts/{id?}', function(RouteParams $params) {
    if (isset($params->id)) {
        // Param provided
    } else {
        // Param not provided
    }
});

命名路由

路由可以被命名,以便可以程序化地生成它们的 URL

$router->map(['GET'], 'posts/all', function () {})->name('posts.index');

$url = $router->url('posts.index');

如果路由需要参数,您可以传递一个关联数组作为第二个参数

$router->map(['GET'], 'posts/{id}', function () {})->name('posts.show');

$url = $router->url('posts.show', ['id' => 123]);

如果传递的参数未通过正则表达式约束,将抛出 RouteParamFailedConstraintException

HTTP 动词快捷方式

通常您只需要为路由允许一个 HTTP 动词,对于这些情况,可以使用以下快捷方式

$router->get('test/route', function () {});
$router->post('test/route', function () {});
$router->put('test/route', function () {});
$router->patch('test/route', function () {});
$router->delete('test/route', function () {});
$router->options('test/route', function () {});

设置基本路径

路由器假设您正在处理域的路由。如果这不是这种情况,您可以设置基本路径

$router->setBasePath('base/path');
$router->map(['GET'], 'route/uri', function () {}); // `/base/path/route/uri`

控制器

如果您希望使用一个类来将相关的路由操作分组在一起,您可以将控制器字符串传递到 map() 而不是闭包。字符串的格式为 {类名}@{方法名}。使用类的完整命名空间和类名非常重要。

示例

// TestController.php
namespace \MyNamespace;

class TestController
{
    public function testMethod()
    {
        return 'Hello World';
    }
}

// routes.php
$router->map(['GET'], 'route/uri', '\MyNamespace\TestController@testMethod');

创建组

通常,将类似的路由放在一个共同的名称前缀后面。这可以通过路由组实现

$router->group('prefix', function ($group) {
    $group->map(['GET'], 'route1', function () {}); // `/prefix/route1`
    $group->map(['GET'], 'route2', function () {}); // `/prefix/route2§`
});

中间件

PSR-15/7 中间件可以添加到路由和组。

向路由添加中间件

在最简单的情况下,通过传递一个对象到 middleware() 函数来向路由添加中间件

$middleware = new AddHeaderMiddleware('X-Key1', 'abc');

$router->get('route/uri', '\MyNamespace\TestController@testMethod')->middleware($middleware);

可以通过向 middleware() 函数传递更多参数来添加多个中间件

$header = new AddHeaderMiddleware('X-Key1', 'abc');
$auth = new AuthMiddleware();

$router->get('route/uri', '\MyNamespace\TestController@testMethod')->middleware($header, $auth);

或者,您也可以传递一个中间件数组

$header = new AddHeaderMiddleware('X-Key1', 'abc');
$auth = new AuthMiddleware();

$router->get('route/uri', '\MyNamespace\TestController@testMethod')->middleware([$header, $auth]);

向组添加中间件

您还可以向组添加中间件。为此,您需要将一个数组作为 group() 函数的第一个参数传递,而不是一个字符串。

$header = new AddHeaderMiddleware('X-Key1', 'abc');

$router->group(['prefix' => 'my-prefix', 'middleware' => $header]), function ($group) {
    $group->map(['GET'], 'route1', function () {}); // `/my-prefix/route1`
    $group->map(['GET'], 'route2', function () {}); // `/my-prefix/route2§`
});

如果您需要多个中间件,您也可以传递一个中间件数组

$header = new AddHeaderMiddleware('X-Key1', 'abc');
$auth = new AuthMiddleware();

$router->group(['prefix' => 'my-prefix', 'middleware' => [$header, $auth]]), function ($group) {
    $group->map(['GET'], 'route1', function () {}); // `/my-prefix/route1`
    $group->map(['GET'], 'route2', function () {}); // `/my-prefix/route2§`
});

在控制器上定义中间件

您还可以在控制器类上应用中间件。为了做到这一点,您的控制器必须扩展 Rareloop\Router\Controller 基类。

通过在控制器中的 __constructor() 中调用 middleware() 函数来添加中间件。

use Rareloop\Router\Controller;

class MyController extends Controller
{
    public function __construct()
    {
        // Add one at a time
        $this->middleware(new AddHeaderMiddleware('X-Key1', 'abc'));
        $this->middleware(new AuthMiddleware());

        // Add multiple with one method call
        $this->middleware([
            new AddHeaderMiddleware('X-Key1', 'abc',
            new AuthMiddleware(),
        ]);
    }
}

默认情况下,通过控制器添加的所有中间件将影响该类上的所有方法。要限制中间件应用于哪些方法,您可以使用 only()except()

use Rareloop\Router\Controller;

class MyController extends Controller
{
    public function __construct()
    {
        // Only apply to `send()` method
        $this->middleware(new AddHeaderMiddleware('X-Key1', 'abc'))->only('send');

        // Apply to all methods except `show()` method
        $this->middleware(new AuthMiddleware())->except('show');

        // Multiple methods can be provided in an array to both methods
        $this->middleware(new AuthMiddleware())->except(['send', 'show']);
    }
}

将路由匹配到请求

一旦您定义了路由,就可以使用 match() 函数尝试将当前请求与它们进行匹配。 match() 接受一个 Symfony 的 Request 实例,并返回一个 Symfony 的 Response 实例

$request = Request::createFromGlobals();
$response = $router->match($request);
$response->send();

返回值

如果您从闭包中返回一个 Response 实例,它将被原样发送。但是,如果您返回其他内容,它将被包装在一个包含您返回值的 Response 实例中。

响应对象

如果您从闭包中返回一个实现了 Responsable 接口的对象,它将自动为您调用 toResponse() 对象。

class MyObject implements Responsable
{
    public function toResponse(RequestInterface $request) : ResponseInterface
    {
        return new TextResponse('Hello World!');
    }
}

$router->get('test/route', function () {
    return new MyObject();
});

404

如果没有路由匹配请求,将返回一个状态码设置为 404Response 对象;

访问当前路由

可以通过调用以下方法来检索当前匹配的 Route

$route = $router->currentRoute();

如果没有路由匹配或尚未调用 match(),将返回 null

您还可以通过调用以下方法来访问当前匹配的 Route 的名称

$name = $router->currentRouteName();

如果没有路由匹配或尚未调用 match(),或者匹配的路由没有名称,将返回 null

与依赖注入容器一起使用

路由器也可以与您选择的 PSR-11 兼容容器一起使用。这允许您在路由闭包或控制器方法中为依赖项添加类型提示。

要使用容器,只需将其作为参数传递给路由器的构造函数

use MyNamespace\Container;
use Rareloop\Router\Router;

$container = new Container();
$router = new Router($container);

之后,您的路由闭包和控制器方法将自动添加类型提示

$container = new Container();

$testServiceInstance = new TestService();
$container->set(TestService::class, $testServiceInstance);

$router = new Router($container);

$router->get('/my/route', function (TestService $service) {
    // $service is now the same object as $testServiceInstance
});