玖叶教程网

前端编程开发入门

Laravel 路由处理(laravelapi路由定义)

代码展示

protected function sendRequestThroughRouter($request)
{
 # $this->app->instance('request', $request);
 # Facade::clearResolvedInstance('request');
 # $this->bootstrap();
 return (new Pipeline($this->app))
 ->send($request)
 ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
 ->then($this->dispatchToRouter());
}

路由解析

请求分发

protected function dispatchToRouter()
{
 return function ($request) {
 $this->app->instance('request', $request);
 return $this->router->dispatch($request);
 };
}
public function dispatch(Request $request)
{
 $this->currentRequest = $request;
 return $this->dispatchToRoute($request);
}
public function dispatchToRoute(Request $request)
{
 $route = $this->findRoute($request);
 $request->setRouteResolver(function () use ($route) {
 return $route;
 });
 $this->events->dispatch(new Events\RouteMatched($route, $request));
 // 至此,系统的流程走到了控制器相关的处理,后面待续
 $response = $this->runRouteWithinStack($route, $request);
 return $this->prepareResponse($request, $response);
}

路由查找

protected function findRoute($request)
{
 $this->current = $route = $this->routes->match($request);
 $this->container->instance(Route::class, $route);
 return $route;
}
public function match(Request $request)
{
 $routes = $this->get($request->getMethod());
 // 根据请求匹配到第一个相应的路由
 $route = $this->matchAgainstRoutes($routes, $request);
 if (! is_null($route)) {
 return $route->bind($request);
 }
 // 若没有找到,则按照['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']顺序再找一遍
 $others = $this->checkForAlternateVerbs($request);
 if (count($others) > 0) {
 return $this->getRouteForMethods($request, $others);
 }
 throw new NotFoundHttpException;
}
public function get($method = null)
{
 return is_null($method) ? $this->getRoutes() : Arr::get($this->routes, $method, []);
}
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 matchAgainstRoutes(array $routes, $request, $includingMethod = true)
{
 return Arr::first($routes, function ($value) use ($request, $includingMethod) {
 return $value->matches($request, $includingMethod);
 });
}
public function matches(Request $request, $includingMethod = true)
{
 // 解析路由
 $this->compileRoute();
 // 检验(uri正则匹配、方法是否存在、http(s)请求、主机正则匹配)均通过,则返回真
 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,
 ];
}
protected function compileRoute()
{
 if (! $this->compiled) {
 $this->compiled = (new RouteCompiler($this))->compile();
 }
 return $this->compiled;
}
// 记录可选参数并统一 uri 形式,后面进行统一的处理
public function compile()
{
 $optionals = $this->getOptionalParameters();
 $uri = preg_replace('/\{(\w+?)\?\}/', '{$1}', $this->route->uri());
 // 构造成 symfony 的路由形式,再委托 Symfony\\Component\\Routing\\RouteCompiler 处理
 return (
 new SymfonyRoute($uri, $optionals, $this->route->wheres, [], $this->route->domain() ?: '')
 )->compile();
}
protected function getOptionalParameters()
{
 preg_match_all('/\{(\w+?)\?\}/', $this->route->uri(), $matches);
 return isset($matches[1]) ? array_fill_keys($matches[1], null) : [];
}
// 初始化处理(路径和参数的规格化,合并 options,设置主机等)
public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '')
{
 $this->setPath($path);
 $this->setDefaults($defaults);
 $this->setRequirements($requirements);
 $this->setOptions($options); // 合并['compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler',] 和 $options
 $this->setHost($host);
 $this->setSchemes($schemes);
 $this->setMethods($methods);
 $this->setCondition($condition);
}
public function compile()
{
 if (null !== $this->compiled) {
 return $this->compiled;
 }
 $class = $this->getOption('compiler_class');
 // 委托 Symfony\\Component\\Routing\\RouteCompiler 来处理,返回 \Symfony\Component\Routing\CompiledRoute 对象
 return $this->compiled = $class::compile($this);
}
public static function compile(Route $route)
{
 $hostVariables = array();
 $variables = array();
 $hostRegex = null;
 $hostTokens = array();
 if ('' !== $host = $route->getHost()) {
 $result = self::compilePattern($route, $host, true);
 $hostVariables = $result['variables'];
 $variables = $hostVariables;
 $hostTokens = $result['tokens'];
 $hostRegex = $result['regex'];
 }
 $path = $route->getPath();
 $result = self::compilePattern($route, $path, false);
 $staticPrefix = $result['staticPrefix'];
 $pathVariables = $result['variables'];
 foreach ($pathVariables as $pathParam) {
 if ('_fragment' === $pathParam) {
 throw new \InvalidArgumentException(sprintf('Route pattern "%s" cannot contain "_fragment" as a path parameter.', $route->getPath()));
 }
 }
 $variables = array_merge($variables, $pathVariables);
 $tokens = $result['tokens'];
 $regex = $result['regex'];
 // 委托 CompiledRoute 返回数据,数据没做什么处理,只是多提供了序列化和反序列化方法
 return new CompiledRoute(
 $staticPrefix,
 $regex,
 $tokens,
 $pathVariables,
 $hostRegex,
 $hostTokens,
 $hostVariables,
 array_unique($variables)
 );
}
// 核心方法
private static function compilePattern(Route $route, $pattern, $isHost)
{
 $tokens = array();
 $variables = array();
 $matches = array();
 $pos = 0;
 $defaultSeparator = $isHost ? '.' : '/'; // 主机或路径默认分割符
 $useUtf8 = preg_match('//u', $pattern);
 $needsUtf8 = $route->getOption('utf8');
 if (!$needsUtf8 && $useUtf8 && preg_match('/[\x80-\xFF]/', $pattern)) {
 $needsUtf8 = true;
 @trigger_error(sprintf('Using UTF-8 route patterns without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for pattern "%s".', $pattern), E_USER_DEPRECATED);
 }
 if (!$useUtf8 && $needsUtf8) {
 throw new \LogicException(sprintf('Cannot mix UTF-8 requirements with non-UTF-8 pattern "%s".', $pattern));
 }
 // 解析类似 $pattern('/posts/{post}/comments/{comment}') 里面的 {\w+} 到 $matches ($matches 类似 [[['{post}',7]],[['{comment}',23]]],值和位置)
 preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
 foreach ($matches as $match) {
 $varName = substr($match[0][0], 1, -1); // 取方括号中间的值,即变量名称
 $precedingText = substr($pattern, $pos, $match[0][1] - $pos); // 取前一个变量末尾位置后一位到({)的前一段文本
 $pos = $match[0][1] + strlen($match[0][0]); // 取(})后一段的(/)位置
 // 尝试取前一段的最后一个字符
 if (!strlen($precedingText)) {
 $precedingChar = '';
 } elseif ($useUtf8) {
 preg_match('/.$/u', $precedingText, $precedingChar);
 $precedingChar = $precedingChar[0];
 } else {
 $precedingChar = substr($precedingText, -1);
 }
 $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); // 是否分割符
 if (preg_match('/^\d/', $varName)) {
 throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern));
 }
 if (in_array($varName, $variables)) {
 throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName));
 }
 if (strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) {
 throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern));
 }
 // 若前一段有值,即文本形式。则入 $tokens 数组 类似: $tokens[] = ['text', '/posts',]
 if ($isSeparator && $precedingText !== $precedingChar) {
 $tokens[] = array('text', substr($precedingText, 0, -strlen($precedingChar)));
 } elseif (!$isSeparator && strlen($precedingText) > 0) {
 $tokens[] = array('text', $precedingText);
 }
 // 尝试获取 where 条件设置的正则
 $regexp = $route->getRequirement($varName);
 // 没有则用最宽松的方式处理
 if (null === $regexp) {
 // 取分割符后面的内容 类似: '/comments/{comment}'
 $followingPattern = (string) substr($pattern, $pos);
 // 找出下一个分割符 类似: '/'
 $nextSeparator = self::findNextSeparator($followingPattern, $useUtf8);
 // 构造 regexp 类似: '[^\/]'
 $regexp = sprintf(
 '[^%s%s]+',
 preg_quote($defaultSeparator, self::REGEX_DELIMITER),
 $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : ''
 );
 // '[^\/]+'
 if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
 $regexp .= '+';
 }
 } else {
 if (!preg_match('//u', $regexp)) {
 $useUtf8 = false;
 } elseif (!$needsUtf8 && preg_match('/[\x80-\xFF]|(?<!\\\\)\\\\(?:\\\\\\\\)*+(?-i:X|[pP][\{CLMNPSZ]|x\{[A-Fa-f0-9]{3})/', $regexp)) {
 $needsUtf8 = true;
 @trigger_error(sprintf('Using UTF-8 route requirements without setting the "utf8" option is deprecated since Symfony 3.2 and will throw a LogicException in 4.0. Turn on the "utf8" route option for variable "%s" in pattern "%s".', $varName, $pattern), E_USER_DEPRECATED);
 }
 if (!$useUtf8 && $needsUtf8) {
 throw new \LogicException(sprintf('Cannot mix UTF-8 requirement with non-UTF-8 charset for variable "%s" in pattern "%s".', $varName, $pattern));
 }
 }
 // $tokens[] = ['variable', '/', '[^\/]+', 'post']
 $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName);
 // $variables[] = 'post'
 $variables[] = $varName;
 }
 // 循环过后的结果类似:
 // $tokens = [['text', '/posts',],['variable', '/', '[^\/]+', 'post'],['text', '/comments',],['variable', '/', '[^\/]+', 'comment'],]
 // $variables = ['post', 'comment',]
 if ($pos < strlen($pattern)) {
 $tokens[] = array('text', substr($pattern, $pos)); // 将剩下的部分当做是文本形式处理,构造正则时,直接转义输出
 }
 // 取第一个可选项位置,此位置表明后面的所有参数都需要以可选的方式处理,即对应的正则都是零宽可选形式
 $firstOptional = PHP_INT_MAX;
 if (!$isHost) {
 for ($i = count($tokens) - 1; $i >= 0; --$i) {
 $token = $tokens[$i];
 if ('variable' === $token[0] && $route->hasDefault($token[3])) {
 $firstOptional = $i;
 } else {
 break;
 }
 }
 }
 // 构造正则
 $regexp = '';
 for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) {
 $regexp .= self::computeRegexp($tokens, $i, $firstOptional);
 }
 // 类似: '#^\/posts\/(?P<post>[^\/]+)\/comments\/(?P<comment>[^\/]+)$#si'
 $regexp = self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s'.($isHost ? 'i' : '');
 if ($needsUtf8) {
 $regexp .= 'u';
 for ($i = 0, $nbToken = count($tokens); $i < $nbToken; ++$i) {
 if ('variable' === $tokens[$i][0]) {
 $tokens[$i][] = true;
 }
 }
 }
 return array(
 'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '', // 设置静态的前缀
 'regex' => $regexp,
 'tokens' => array_reverse($tokens),
 'variables' => $variables,
 );
}
private static function computeRegexp(array $tokens, $index, $firstOptional)
{
 $token = $tokens[$index];
 // 文本形式,直接转义返回
 if ('text' === $token[0]) {
 return preg_quote($token[1], self::REGEX_DELIMITER);
 }
 // 参数形式
 else {
 // 第一个就是可选项的处理
 if (0 === $index && 0 === $firstOptional) {
 return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
 } else {
 $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
 // 可选项后面的正则处理,均变为可选项。
 if ($index >= $firstOptional) {
 $regexp = "(?:$regexp";
 $nbTokens = count($tokens);
 if ($nbTokens - 1 == $index) {
 $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0));
 }
 }
 return $regexp;
 }
 }
}

小结(路径 /posts/{post}/comments/{comment}):

  1. 路由解析主要就是解析路由的主机和路径部分(带参数部分),差别在于分割符不一样。并将解析结果放到 route 对象的 $compiled 属性供后续使用。

  2. 重点是先将其分割成对应的文本和变量部分( $tokens = [['text', '/posts',],['variable', '/', '1+', 'post'],['text', '/comments',], $variables = ['post', 'comment',])。

  3. 再根据上面分割的数组构造相应的正则表达式(会使用到 where 条件设置的正则)。

  4. 最后返回一个数组,表明此路由对应的静态前缀、正则表达式、tokens、variables。

请求绑定

$route->bind($request):
public function bind(Request $request)
{
 $this->compileRoute();
 $this->parameters = (new RouteParameterBinder($this))
 ->parameters($request);
 return $this;
}
new RouteParameterBinder($this):
public function __construct($route)
{
 $this->route = $route;
}
public function parameters($request)
{
 // 取得有效的对应的参数键值对
 $parameters = $this->bindPathParameters($request);
 if (! is_null($this->route->compiled->getHostRegex())) {
 // 取得有效的对应的主机键值对,并合并到参数键值对
 $parameters = $this->bindHostParameters(
 $request, $parameters
 );
 }
 return $this->replaceDefaults($parameters);
}
protected function bindPathParameters($request)
{
 preg_match($this->route->compiled->getRegex(), '/'.$request->decodedPath(), $matches);
 // 从 pathinfo 中取出正则匹配的相应的值 类似['post'=>1,'comment'=>2]
 return $this->matchToKeys(array_slice($matches, 1));
}
public function decodedPath()
{
 return rawurldecode($this->path());
}
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;
 });
}
public function parameterNames()
{
 if (isset($this->parameterNames)) {
 return $this->parameterNames;
 }
 return $this->parameterNames = $this->compileParameterNames();
}
protected function compileParameterNames()
{
 // 从 uri 上取出参数数组,并去掉 '?'
 preg_match_all('/\{(.*?)\}/', $this->domain().$this->uri, $matches);
 return array_map(function ($m) {
 return trim($m, '?');
 }, $matches[1]);
}
protected function bindHostParameters($request, $parameters)
{
 preg_match($this->route->compiled->getHostRegex(), $request->getHost(), $matches);
 return array_merge($this->matchToKeys(array_slice($matches, 1)), $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;
}

小结:

本质就是 route 对象参数属性 $parameters 的处理。根据路由解析得到的正则从实际请求的PATHINFO和HOST里面提取出相应参数对应的值,再进行合并处理(包括默认值的设置、追加默认参数及对值得过滤)。

路由分离器设置

public function setRouteResolver(Closure $callback)
{
 $this->routeResolver = $callback;
 return $this;
}

事件分发 参考 Kernel实例化后的处理

路由执行

protected function runRouteWithinStack(Route $route, Request $request)
{
 $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
 $this->container->make('middleware.disable') === true;
 $middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
 return (new Pipeline($this->container))
 ->send($request)
 ->through($middleware)
 ->then(function ($request) use ($route) {
 return $this->prepareResponse(
 $request, $route->run()
 );
 });
}

执行前的准备

获取经过优先级处理过的所有的非全局中间件

public function gatherRouteMiddleware(Route $route)
{
 // 返回 object(Illuminate\Support\Collection),$items 属性类似 ['类名','类名:参数','匿名函数']
 $middleware = collect($route->gatherMiddleware())->map(function ($name) {
 return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
 })->flatten();
 return $this->sortMiddleware($middleware);
}
// 获取本次请求设置的所有的非全局的 middleware (包括前置栈、middleware 方法、控制器 getMiddleware 方法)
public function gatherMiddleware()
{
 if (! is_null($this->computedMiddleware)) {
 return $this->computedMiddleware;
 }
 $this->computedMiddleware = [];
 return $this->computedMiddleware = array_unique(array_merge(
 $this->middleware(), $this->controllerMiddleware()
 ), SORT_REGULAR);
}
// 解析 middleware ,返回简单的形式(匿名函数或 middleware:parameters ),优先从 $middleware 和 $middlewareGroups 取,没有则直接返回 $name
public static function resolve($name, $map, $middlewareGroups)
{
 if ($name instanceof Closure) {
 return $name;
 } elseif (isset($map[$name]) && $map[$name] instanceof Closure) {
 return $map[$name];
 } elseif (isset($middlewareGroups[$name])) {
 return static::parseMiddlewareGroup(
 $name, $map, $middlewareGroups
 );
 } else {
 list($name, $parameters) = array_pad(explode(':', $name, 2), 2, null);
 return (isset($map[$name]) ? $map[$name] : $name).
 (! is_null($parameters) ? ':'.$parameters : '');
 }
}
protected static function parseMiddlewareGroup($name, $map, $middlewareGroups)
{
 $results = [];
 foreach ($middlewareGroups[$name] as $middleware) {
 // 组内部还是个组,递归执行
 if (isset($middlewareGroups[$middleware])) {
 $results = array_merge($results, static::parseMiddlewareGroup(
 $middleware, $map, $middlewareGroups
 ));
 continue;
 }
 list($middleware, $parameters) = array_pad(
 explode(':', $middleware, 2), 2, null
 );
 if (isset($map[$middleware])) {
 $middleware = $map[$middleware];
 }
 $results[] = $middleware.($parameters ? ':'.$parameters : '');
 }
 return $results;
}
##################################### 排序块 BEGIN #######################################
protected function sortMiddleware(Collection $middlewares)
{
 return (new SortedMiddleware($this->middlewarePriority, $middlewares))->all();
}
public function __construct(array $priorityMap, $middlewares)
{
 if ($middlewares instanceof Collection) {
 $middlewares = $middlewares->all();
 }
 $this->items = $this->sortMiddleware($priorityMap, $middlewares);
}
// 类似插入排序
protected function sortMiddleware($priorityMap, $middlewares)
{
 $lastIndex = 0;
 foreach ($middlewares as $index => $middleware) {
 // 匿名函数直接 continue
 if (! is_string($middleware)) {
 continue;
 }
 $stripped = head(explode(':', $middleware));
 if (in_array($stripped, $priorityMap)) {
 $priorityIndex = array_search($stripped, $priorityMap);
 if (isset($lastPriorityIndex) && $priorityIndex < $lastPriorityIndex) {
 return $this->sortMiddleware(
 $priorityMap, array_values(
 $this->moveMiddleware($middlewares, $index, $lastIndex)
 )
 );
 } else {
 $lastIndex = $index;
 $lastPriorityIndex = $priorityIndex;
 }
 }
 }
 return array_values(array_unique($middlewares, SORT_REGULAR));
}
protected function moveMiddleware($middlewares, $from, $to)
{
 array_splice($middlewares, $to, 0, $middlewares[$from]);
 unset($middlewares[$from + 1]);
 return $middlewares;
}
##################################### 排序块 END #######################################

执行

后续分解

public function run()
{
 $this->container = $this->container ?: new Container;
 try {
 // 控制器形式的处理(Controller@Method)
 if ($this->isControllerAction()) {
 return $this->runController();
 }
 // 匿名函数形式的处理
 return $this->runCallable();
 } catch (HttpResponseException $e) {
 return $e->getResponse();
 }
}
public function prepareResponse($request, $response)
{
 if ($response instanceof PsrResponseInterface) {
 $response = (new HttpFoundationFactory)->createResponse($response);
 } elseif (! $response instanceof SymfonyResponse) {
 $response = new Response($response);
 }
 return $response->prepare($request);
}

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言