内容简介:Laravel HTTP——路由的匹配与参数绑定
前言
本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/laravel-source-analysis
上一篇文章我们说到路由的正则编译,正则编译的目的就是和请求的 url 来匹配,只有匹配上的路由才是我们真正想要的,此外也会通过正则匹配来获取路由的参数。
路由的匹配
路由进行正则编译后,就要与请求 request 来进行正则匹配,并且进行一些验证,例如 UriValidator、MethodValidator、SchemeValidator、HostValidator。
class RouteCollection implements Countable, IteratorAggregate
{
public function match(Request $request)
{
$routes = $this->get($request->getMethod());
$route = $this->matchAgainstRoutes($routes, $request);
if (! is_null($route)) {
return $route->bind($request);
}
$others = $this->checkForAlternateVerbs($request);
if (count($others) > 0) {
return $this->getRouteForMethods($request, $others);
}
throw new NotFoundHttpException;
}
protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
{
return Arr::first($routes, function ($value) use ($request, $includingMethod) {
return $value->matches($request, $includingMethod);
});
}
}
class Route
{
public function matches(Request $request, $includingMethod = true)
{
$this->compileRoute();
foreach ($this->getValidators() as $validator) {
if (! $includingMethod && $validator instanceof MethodValidator) {
continue;
}
if (! $validator->matches($this, $request)) {
return false;
}
}
return true;
}
public static function getValidators()
{
if (isset(static::$validators)) {
return static::$validators;
}
return static::$validators = [
new UriValidator, new MethodValidator,
new SchemeValidator, new HostValidator,
];
}
}
UriValidator uri 验证
UriValidator 验证主要是目的是查看路由正则与请求是否匹配:
class UriValidator implements ValidatorInterface
{
public function matches(Route $route, Request $request)
{
$path = $request->path() == '/' ? '/' : '/'.$request->path();
return preg_match($route->getCompiled()->getRegex(), rawurldecode($path));
}
}
值得注意的是,在匹配路径之前,程序使用了 rawurldecode 来对请求进行解码。
MethodValidator 验证
请求方法验证:
class MethodValidator implements ValidatorInterface
{
public function matches(Route $route, Request $request)
{
return in_array($request->getMethod(), $route->methods());
}
}
SchemeValidator 验证
路由 scheme 协议验证:
class SchemeValidator implements ValidatorInterface
{
public function matches(Route $route, Request $request)
{
if ($route->httpOnly()) {
return ! $request->secure();
} elseif ($route->secure()) {
return $request->secure();
}
return true;
}
}
public function httpOnly()
{
return in_array('http', $this->action, true);
}
public function secure()
{
return in_array('https', $this->action, true);
}
HostValidator 验证
主域验证:
class HostValidator implements ValidatorInterface
{
public function matches(Route $route, Request $request)
{
if (is_null($route->getCompiled()->getHostRegex())) {
return true;
}
return preg_match($route->getCompiled()->getHostRegex(), $request->getHost());
}
}
也就是说,如果路由中并不设置 host 属性,那么这个验证并不进行。
路由的参数绑定
一旦某个路由符合请求的 uri 四项认证,就将会被返回,接下来就要对路由的参数进行绑定与赋值:
class RouteCollection implements Countable, IteratorAggregate
{
public function bind(Request $request)
{
$this->compileRoute();
$this->parameters = (new RouteParameterBinder($this))
->parameters($request);
return $this;
}
}
bind 函数负责路由参数与请求 url 的绑定工作:
class RouteParameterBinder
{
public function parameters($request)
{
$parameters = $this->bindPathParameters($request);
if (! is_null($this->route->compiled->getHostRegex())) {
$parameters = $this->bindHostParameters(
$request, $parameters
);
}
return $this->replaceDefaults($parameters);
}
}
可以看出,路由参数绑定分为主域参数绑定与路径参数绑定,我们先看路径参数绑定:
路径参数绑定
class RouteParameterBinder
{
protected function bindPathParameters($request)
{
preg_match($this->route->compiled->getRegex(), '/'.$request->decodedPath(), $matches);
return $this->matchToKeys(array_slice($matches, 1));
}
}
例如,{foo}/{baz?}.{ext?} 进行正则编译后结果:
#^/(?P<foo>[^/]++)(?:/(?P<baz>[^/\.]++)(?:\.(?P<ext>[^/]++))?)?$#s
其与 request 匹配后的结果为:
$matches = array (
0 = "/foo/baz.ext",
1 = "foo",
foo = "foo",
2 = "baz",
baz = "baz",
3 = "ext",
ext = "ext",
)
array_slice($matches, 1) 取出了 $matches 数组 1 之后的结果,然后调用了 matchToKeys 函数,
protected function matchToKeys(array $matches)
{
if (empty($parameterNames = $this->route->parameterNames())) {
return [];
}
$parameters = array_intersect_key($matches, array_flip($parameterNames));
return array_filter($parameters, function ($value) {
return is_string($value) && strlen($value) > 0;
});
}
该函数中利用正则获取了路由的所有参数:
class Route
{
public function parameterNames()
{
if (isset($this->parameterNames)) {
return $this->parameterNames;
}
return $this->parameterNames = $this->compileParameterNames();
}
protected function compileParameterNames()
{
preg_match_all('/\{(.*?)\}/', $this->domain().$this->uri, $matches);
return array_map(function ($m) {
return trim($m, '?');
}, $matches[1]);
}
}
可以看出,获取路由参数的正则表达式采用了勉强模式,意图提取出所有的路由参数。否则,对于路由 {foo}/{baz?}.{ext?},贪婪型正则表达式 /\{(.*)\}/ 将会匹配整个字符串,而不是各个参数分组。
提取出的参数结果为:
$matches = array (
0 = array (
0 = "{foo}".
1 = "{baz?}",
2 = "{ext?}",
)
1 = array (
0 = "foo".
1 = "baz?",
2 = "ext?",
)
)
得出的结果将会去除 $matches[1],并且将会删除结果中最后的 ?。
之后,在 matchToKeys 函数中,
$parameters = array_intersect_key($matches, array_flip($parameterNames));
获取了匹配结果与路由所有参数的交集:
$parameters = array (
foo = "foo",
baz = "baz",
ext = "ext",
)
主域参数绑定
protected function bindHostParameters($request, $parameters)
{
preg_match($this->route->compiled->getHostRegex(), $request->getHost(), $matches);
return array_merge($this->matchToKeys(array_slice($matches, 1)), $parameters);
}
步骤与路由参数绑定一致。
替换默认值
进行参数绑定后,有一些可选参数并没有在 request 中匹配到,这时候就要用可选参数的默认值添加到变量 parameters 中:
protected function replaceDefaults(array $parameters)
{
foreach ($parameters as $key => $value) {
$parameters[$key] = isset($value) ? $value : Arr::get($this->route->defaults, $key);
}
foreach ($this->route->defaults as $key => $value) {
if (! isset($parameters[$key])) {
$parameters[$key] = $value;
}
}
return $parameters;
}
匹配异常处理
如果 url 匹配失败,没有找到任何路由与请求相互匹配,就会切换 method 方法,以求任意路由来匹配:
protected function checkForAlternateVerbs($request)
{
$methods = array_diff(Router::$verbs, [$request->getMethod()]);
$others = [];
foreach ($methods as $method) {
if (! is_null($this->matchAgainstRoutes($this->get($method), $request, false))) {
$others[] = $method;
}
}
return $others;
}
如果使用其他方法匹配成功,就要判断当前方法是否是 options,如果是则直接返回,否则报出异常:
protected function getRouteForMethods($request, array $methods)
{
if ($request->method() == 'OPTIONS') {
return (new Route('OPTIONS', $request->path(), function () use ($methods) {
return new Response('', 200, ['Allow' => implode(',', $methods)]);
}))->bind($request);
}
$this->methodNotAllowed($methods);
}
protected function methodNotAllowed(array $others)
{
throw new MethodNotAllowedHttpException($others);
}
以上所述就是小编给大家介绍的《Laravel HTTP——路由的匹配与参数绑定》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- ginprc:Gin 注解路由,自动参数绑定工具
- Golang Echo数据绑定中time.Time类型绑定失败
- 如何在Symfony的表单中添加一个未绑定字段,否则绑定到一个实体?
- js双向绑定
- 延迟静态绑定——static
- 绑定自定义事件
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Mastering Regular Expressions, Second Edition
Jeffrey E F Friedl / O'Reilly Media / 2002-07-15 / USD 39.95
Regular expressions are an extremely powerful tool for manipulating text and data. They have spread like wildfire in recent years, now offered as standard features in Perl, Java, VB.NET and C# (and an......一起来看看 《Mastering Regular Expressions, Second Edition》 这本书的介绍吧!