TP5.1 源码窥探之路由
在上一节中,了解到项目初始化的大致步骤,加载Env环境变量,加载惯例配置,加载公共文件,加载助手函数,加载配置文件,注册类库别名,设置时区,设置语言包等一系列操作,再然后的操作就是路由初始化,现在就看一下路由是怎么初始化的。
路由初始化
thinkphp/library/think/App.php
public function routeInit()
{
// 路由检测
$files = scandir($this->routePath);
foreach ($files as $file) {
if (strpos($file, '.php')) {
$filename = $this->routePath . $file;
// 导入路由配置
$rules = include $filename;
if (is_array($rules)) {
$this->route->import($rules);
}
}
}
if ($this->route->config('route_annotation')) {
// 自动生成路由定义
if ($this->appDebug) {
$suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix');
$this->build->buildRoute($suffix);
}
$filename = $this->runtimePath . 'build_route.php';
if (is_file($filename)) {
include $filename;
}
}
}
由上面的代码可以看出,系统是遍历引入项目根目录下的route
目录的php
文件,如果引入路由文件后返回的数组,则执行import
方法。接着如果项目中配置了注解路由,自动生成路由定义,如果runtime
文件夹下存在文件则引入。
路由解析
路由的解析过程:
- 路由定义:完成路由规则的定义和参数设置(5.1的路由定义采用了对象化的思维,相对5.0而言更直观);
- 路由检测:检查当前的URL请求是否有匹配的路由;
- 路由解析:解析当前路由实际对应的操作(方法或闭包);
- 路由调度:执行路由解析的结果调度(主业务逻辑);
路由定义
最基础的路由定义方法是: Route::rule('路由表达式','路由地址','请求方式','路由参数','变量规则')
;
另外两个比较有意思的定义是:
- 动态路由:
Route::get('hello/:name', 'index/:name/hello')
; - 路由地址多级控制器:
Route::get('blog/:id','index/group.blog/read')
;
就以route/route.php
文件里面的路由定义为例
Route::get('hello/:name', 'index/hello');
系统类文件自动加载,会定位到thinkphp/library/think/Route.php
文件,执行get方法
public function get($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, 'GET', $option, $pattern);
}
然后又会调用rule
方法
public function rule($rule, $route, $method = '*', array $option = [], array $pattern = [])
{
return $this->group->addRule($rule, $route, $method, $option, $pattern);
}
然而$this->group
是哪里来的,它是Route在初始化的时候定义的
public function __construct(App $app, array $config = [])
{
$this->app = $app;
$this->request = $app['request'];
$this->config = $config;
$this->host = $this->request->host(true) ?: $config['app_host'];
$this->setDefaultDomain();
}
protected function setDefaultDomain()
{
// 默认域名
$this->domain = $this->host;
// 注册默认域名
$domain = new Domain($this, $this->host);
$this->domains[$this->host] = $domain;
//var_dump($domain);exit;
// 默认分组
$this->group = $domain;
}
由此可见$this->group
是一个Domain实例对象。然而Domain类又继承RuleGroup类,然后再执行addRule方法。
thinkphp/library/think/route/RuleGroup.php
public function addRule($rule, $route, $method = '*', $option = [], $pattern = [])
{
//var_dump($rule, $route, $method, $option, $pattern);exit;hello/:name,index/hello,get,[],[]
// 读取路由标识
if (is_array($rule)) {
$name = $rule[0];
$rule = $rule[1];
} elseif (is_string($route)) {
$name = $route;
} else {
$name = null;
}
//var_dump($name);exit;
$method = strtolower($method);
if ('/' === $rule || '' === $rule) {
// 首页自动完整匹配
$rule .= '$';
}
// 创建路由规则实例
$ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern);
//var_dump($ruleItem);exit;
if (!empty($option['cross_domain'])) {
$this->router->setCrossDomainRule($ruleItem, $method);
}
$this->addRuleItem($ruleItem, $method);
return $ruleItem;
}
可见最终返回的是RuleItem对象实例,在RuleItem对象实例化的时候,有两个重要的步骤,一个是路由规则预处理,另外一个是设置路由标识,用于URL反解生成。
thinkphp/library/think/route/RuleItem.php
public function setRule($rule)
{
//var_dump($rule);exit; hello/:name
if ('$' == substr($rule, -1, 1)) {
// 是否完整匹配
$rule = substr($rule, 0, -1);
$this->option['complete_match'] = true;
}
$rule = '/' != $rule ? ltrim($rule, '/') : '';
//var_dump($rule);exit;hello/:name
if ($this->parent && $prefix = $this->parent->getFullName()) {
$rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : '');
}
//var_dump(111,$rule);exit;hello/:name
if (false !== strpos($rule, ':')) {
$this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule);// hello/<name>
} else {
$this->rule = $rule;
}
//var_dump($this->rule);exit;
// 生成路由标识的快捷访问
$this->setRuleName();
}
protected function setRuleName($first = false)
{
//var_dump($this->name);exit; index/hello
if ($this->name) {
$vars = $this->parseVar($this->rule);
$name = strtolower($this->name);
if (isset($this->option['ext'])) {
$suffix = $this->option['ext'];
} elseif ($this->parent->getOption('ext')) {
$suffix = $this->parent->getOption('ext');
} else {
$suffix = null;
}
$value = [$this->rule, $vars, $this->parent->getDomain(), $suffix, $this->method];
//var_dump($name, $value, $first);exit;
Container::get('rule_name')->set($name, $value, $first);
}
//var_dump(!$this->hasSetRule);exit; true
if (!$this->hasSetRule) {
Container::get('rule_name')->setRule($this->rule, $this);
$this->hasSetRule = true;
}
}
protected function parseVar($rule)
{
//var_dump($rule);exit; hello/<name>
// 提取路由规则中的变量
$var = [];
if (preg_match_all('/<\w+\??>/', $rule, $matches)) {
//var_dump($matches[0]);exit;
foreach ($matches[0] as $name) {
$optional = false;
//var_dump($name);exit;<name>
if (strpos($name, '?')) {
$name = substr($name, 1, -2);
$optional = true;
} else {
$name = substr($name, 1, -1);
}
//var_dump($name);exit;
$var[$name] = $optional ? 2 : 1;
}
}
return $var;
}
注册路由规则
thinkphp/library/think/route/RuleName.php
public function setRule($rule, $route)
{
$this->rule[$route->getDomain()][$rule][$route->getMethod()] = $route;
}
本作品采用《CC 协议》,转载必须注明作者和本文链接