mattferris/http-routing

轻量级、灵活的HTTP请求路由器

1.0 2020-02-04 05:30 UTC

This package is auto-updated.

Last update: 2024-09-04 16:22:08 UTC


README

Build Status SensioLabsInsight

一个符合PSR-7规范的PHP HTTP路由库

通过composer安装

composer require mattferris/http-routing

要使用此库,您还需要安装一个符合PSR-7规范的HTTP消息库,例如 zendframework/zend-diactoros

分发器

分发器通过将传递的实例 Psr\Http\Message\ServerRequestInterface 与其路由列表进行比较来解析请求。

use MattFerris\Http\Routing\Dispatcher;

$dispatcher = new Dispatcher($request);

调用 dispatch() 将请求路由到操作。操作负责生成和返回响应。然后,dispatch() 返回该响应。该响应是 Psr\Http\Message\ResponseInterface 的实例。

// get a response
$response = $dispatcher->dispatch($request);

路由

路由定义了请求必须匹配的准则,以便给定的操作可以处理该请求。操作必须是 callable 或一个以冒号分隔的类名和方法名的字符串。使用 ClassName:methodName 语法的好处是让控制器负责实例化控制器(允许控制器有依赖注入)。

路由按添加的顺序进行评估。多个操作可以处理单个请求,处理会继续,直到操作返回响应。这些路由的集合称为 路由堆栈

可以通过多种方式添加路由。一种简单的方法是使用以您想要匹配的HTTP方法命名的辅助方法(get()post()put()delete()head()options()trace())。

// handle requests for GET /foo with a closure
$dispatcher->get('/foo', function () {
    return $response;
});

// handle requests for POST /foo with the fooAction() method on the Controller class
$dispatcher->post('/foo', 'Controller::fooAction');

您还可以使用 any() 匹配任何HTTP方法。

// handle requests for /foo with fooAction() method on $object
$dispatcher->any('/foo', array($object, 'fooAction'));

所有辅助方法还支持一个第三个参数来匹配HTTP头。头必须传递一个数组,其中数组键是头名称。头名称不区分大小写。

$dispatcher->get('/foo', 'Controller::fooAction', array(
    'Host' => 'example.com'
));

捕获参数

您可以使用命名参数捕获URI的部分。例如,要捕获URI /users/joe 中的用户名,其中 joe 可以是任何用户名,您可以执行以下操作。

$dispatcher->get('/users/{username}', 'Controller::fooAction');

参数捕获的值作为参数传递给操作。

class Controller
{
    public function fooAction($username)
    {
        ...
    }
}

可以捕获多个参数,并将以相同的方式传递给操作。

$dispatcher->get('/users/{username}/{option}', function ($username, $option) {
    // ...
});

参数名称必须以字母或下划线开头,并包含字母、数字和下划线(作为正则表达式,这看起来像 [a-zA-Z_][a-zA-Z0-9_]+)。这遵循PHP允许的变量名称字符。

默认情况下,所有参数都是必需的,URI才能与路由匹配。创建路由时,您可以为其参数定义默认值。任何具有默认值的参数都视为可选。参数作为第四个参数传递。

$dispatcher->get('/users/{username}/{option}', $action, $headers, ['option' => 'update']);

此路由现在将匹配 /users/joe/preferences 以及 /users/joe/。在 /users/joe/ 的情况下,因为未指定 {option},它将默认为 update

一旦定义了一个参数为可选的,所有随后的参数也必须是可选的。如果必需的参数在可选参数之后,将抛出 BadLogicException

// {details} must be optional because {option} is optional
$dispatcher->get('/users/{username}/{option}/{details}', $action, $headers, ['option' => 'update', 'details' => '']);

// {details} is required (no default value), which will throw an exception
$dispatcher->get('/users/{username}/{option}/{details}', $action $headers, ['option' => 'update']);

错误404处理

您可以通过在 路由堆栈 中的最后一个路由定义通用的准则,并将操作设置为可以生成适当响应的代码来轻松定义404处理器。

$dispatcher->any('/', function () {
    return $error404Response;
});

操作

一个动作定义了实际处理请求并生成响应的代码。动作的唯一要求是它返回一个 Psr\Http\Message\ResponseInterface 实例、一个 Psr\Http\Message\ServerRequestInterface 实例,或者什么都不返回。

// given these routes...
$dispatcher
    ->get('/foo/{bar}', 'MyController::getFooAction')
    ->post('/foo', 'MyController::postFooAction');

// your controller might look like...
class MyController
{
    public function getFooAction($bar)
    {
        ...

        return $response;
    }

    public function postFooAction()
    {
        ...

        return $response;
    }
}

内部重定向

您可以使用HTTP 301响应(例如)重定向客户端,浏览器随后会解析并发起新的请求到指定的URL。在某些情况下,您可能只想重新评估一个新请求而不向客户端返回任何内容。通过从动作中返回一个 Psr\Http\Message\ServerRequestInterface 实例即可实现这一点。

public function someAction()
{
    return $request;
}

Dispatcher 将动作的返回值识别为新请求时,它会再次调用 dispatch() 并将新请求作为参数传递。新请求的处理方式与原始请求完全相同。

透传路由

透传路由 是不返回响应的路由,因此允许继续进行进一步匹配。这对于在不终止路由过程的情况下执行代码非常有用。例如,您可以使用 透传路由 来添加请求日志。

$dispatcher->any('/', function (Request $request) {
    error_log('received request: '.$request->getUri());
});

参数注入

路由部分提到了如何通过动作的参数访问命名参数,即名为 username 的模式可以通过名为 $username 的参数名访问。这是通过注入实现的,其中依赖注入器将参数名与参数匹配。除了参数外,您的动作还可以通过参数访问其他信息,例如当前请求对象。

public function someAction(\Psr\Http\Message\ServerRequestInterface $request)
{
    // ...
}

有关依赖注入的更多信息,请参阅mattferris/di

命名路由和反向路由

您可以指定路由的名称。命名路由随后可以使用 generate($name) 生成将匹配给定路由的URI。使用此方法生成URI将应用程序绑定到路由而不是URI。

// specify a route called auth_login
$dispatcher->post('/login', 'AuthController::login', [], 'auth_login');

// now you can generate URIs in your actions based on the route
public function someAction()
{
    $uri = $dispatcher->generate('auth_login');
    // $uri contains '/login'
}

// if you need to change the route, the action will automatically
// generate the correct URI
$dispatcher->post('/auth/login', 'AuthController::login', [], 'auth_login');

参数

包含参数的路由必须传递一个参数值数组才能使用。

$dispatcher->get('/users/{user}', 'UsersController::getUser', [], 'get_user');

// pass the username to use for the {user} parameter
$uri = $dispatcher->generate('get_user', ['user' => 'joe']);
echo $uri; // outputs '/users/joe'

您可以传递在路由中未定义的额外参数。额外参数用于生成查询字符串。

$uri = $dispatcher->generate('get_user', ['user' => 'joe', 'foo' => 'bar']);
echo $uri; // outputs '/users/joe?foo=bar'

路由提供者

在您的应用程序中,您可以定义 提供者,它提供一组路由,这些路由是应用程序的各个部分可以处理的。可以通过 register()提供者 注册到分发器。 提供者 是实现 MattFerris\Provider\ProviderInterface 的普通类,并且必须定义一个方法,即 provides(),它接受一个 Dispatcher 实例作为它的唯一参数。

class RoutingProvider implements \MattFerris\Provider\ProviderInterface
{
    public function provides($consumer)
    {
        $dispatcher->get('/users/{username}', 'Controller::someAction', ['Host => 'example.com']);
    }
}

$dispatcher->register(new RoutingProvider());

提供者提供了一种方便的方法,允许应用程序的各个部分管理自己的路由。

高级路由

额外的路由类型

内部,路由表示为实现 RouteInterface 的对象。当使用辅助方法添加路由时,Dispatcher 创建路由对象。默认情况下,这些路由对象都是 PathRoute 实例,因为 PathRoute 是默认类型。这可以通过调用 Dispatcher::setDefaultRouteType() 来更改。还包括两种其他路由类型:SimpleRouteRegexRoute

$dispatcher->setDefaultRouteType('\MattFerris\Http\Routing\RegexRoute');

设置新的默认路由类型后,所有辅助方法都将创建新的路由类型的实例。这可以用来实现自己的路由类型。

您还可以使用 Dispatcher::add()Dispatcher::insert() 直接添加路由对象。 add() 将路由添加到路由堆栈的末尾,而 insert() 允许您将路由插入路由堆栈的任何位置。这可以用于添加早期路由以捕获中间件处理的请求。 add()insert() 接受 RouteInterface 的实例。

$dispatcher
    ->add(new SimpleRoute('/foo', 'Controller::someAction'))
    ->insert(new RegexRoute('/foo/(bar|baz)', 'Controller::anotherAction'));

路由构造函数接受4个参数。

new SimpleRoute($uri, $action, $method, $headers);

$method$headers 是可选的。

RegexRoute 允许您使用正则表达式来匹配 URI、方法和头。虽然灵活,但捕获参数的语法可能有些难以操作(/users/(?P[a-zA-Z_]+?)/)。PathRoute 扩展了 RegexRoute,为 URI 提供友好的参数匹配,但也可以为 URI、方法和头使用完整的正则表达式功能。

SimpleRoute 真的是很简单。不进行模式匹配。URI 通过前缀进行匹配,所以 /foo 将匹配 /foo/bar/foo/baz。它的唯一目的是为了提高效率。

可以在同一个路由栈中使用不同的路由类型,以达到在需要的地方提高效率或灵活性。

// capture requests for logging middleware 
$dispatcher->add(new SimpleRoute('/', 'LoggerController::logRequest'));

// process user requests
$dispatcher->add(new PathRoute('/users/{username}', 'UsersController::getUser', 'GET');

// capture similar requests
$dispatcher->add(new RegexRoute('/(help|support)', 'HelpController::getHelp', 'GET');

// error 404
$dispatcher->add(new SimpleRoute('/', 'ErrorController::error404');

RegexRoute 和可选参数

RegexRoute 路由中指定默认参数时,可选参数的子模式必须允许空匹配。

new RegexRoute('/users/(?<username>[a-zA-Z_]+?|)', $action, $method, $headers, ['username' => '']);