mattferris / http-routing
轻量级、灵活的HTTP请求路由器
Requires
- mattferris/di: ^1.0
- mattferris/events: ~0.4
- mattferris/provider: ~0.3
- psr/http-message: ^1.0
Requires (Dev)
- phpunit/phpunit: ~4.3
README
一个符合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()
来更改。还包括两种其他路由类型:SimpleRoute
和 RegexRoute
。
$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
)。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' => '']);