继续上一章讲到了
<?php
// 路由初始化
$this->routeInit();
开发手册:https://www.kancloud.cn/manual/thinkphp5_1/353962
route
目录下的任何路由定义文件都是有效的,默认的路由定义文件是route.php
,但你完全可以更改文件名,或者添加多个路由定义文件(你可以进行模块定义区分,但最终都会一起加载)。routeInit方法会扫描route目录下的所有文件,将其include进来
/**
* 路由初始化 导入路由定义规则
* @access public
* @return void
*/
public function routeInit()
{
// 路由检测
$files = scandir($this->routePath);
foreach ($files as $file) {
if (strpos($file, '.php')) {
$filename = $this->routePath . $file;
// 导入路由配置
$rules = include $filename; // 如果$rules不是数组,那么include返回值是int(1),失败返回false
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类下的get方法
// routeroute.php
<?php
//Route::get('think', function () {
// return 'hello,ThinkPHP5!';
//});
// :对应动态参数
Route::get('hello/:name', 'index/hello');
// 上面的写法等同于
//Route::rule('hello/:name', 'index/hello', 'get');
// 可选参数
//Route::get('blog/:year/[:month]','Blog/archive');
// ...
// 更多使用方法看手册
这里以Route::get('hello/:name','index/hello');为测试用例进行代码分析,贴出每一步代码执行对应的值,复习的时候看看注释就好。
// thinkphp/library/think/Route.php
<?php
/**
* 注册GET路由
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function get($rule, $route = '', array $option = [], array $pattern = [])
{
return $this->rule($rule, $route, 'GET', $option, $pattern);
}
/**
* 注册路由规则
* @access public
* @param string $rule 路由规则
* @param mixed $route 路由地址
* @param string $method 请求类型
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return RuleItem
*/
public function rule($rule, $route, $method = '*', array $option = [], array $pattern = [])
{
// $rule = 'hello/:name'
// $route = 'index/hello'
// $method = 'GET'
// $option = []
// $pattern = []
return $this->group->addRule($rule, $route, $method, $option, $pattern);
}
说明:为什么可以直接通过$this->group->addRule()调用方法,在哪里定义了成员属性?
我们可以找到构造方法中调用了$this->setDefaultDomain(),在这个方法中将new Domain($this, $this->host)的实例赋值给了$this->group,但是在Domain()类中并没有addRule()方法,所以会再找到Domain类的父类RuleGroup类中的addRule()方法。
// thinkphp/library/think/route/RuleGroup.php
<?php
/**
* 添加分组下的路由规则或者子分组
* @access public
* @param string $rule 路由规则
* @param string $route 路由地址
* @param string $method 请求类型
* @param array $option 路由参数
* @param array $pattern 变量规则
* @return $this
*/
public function addRule($rule, $route, $method = '*', $option = [], $pattern = [])
{
// $rule = 'hello/:name';$route = 'index/hello';$method = 'GET';$option = [];$pattern = []
// 读取路由标识
if (is_array($rule)) {
$name = $rule[0];
$rule = $rule[1];
} elseif (is_string($route)) {
$name = $route;
} else {
$name = null;
}
// $name = 'index/hello';
$method = strtolower($method);
// $method = 'get';
if ('/' === $rule || '' === $rule) {
// 首页自动完整匹配
$rule .= '$'; // /$ 或者 $
}
// 创建路由规则实例
// 说明 $this->router为Route类对象,在构造方法中赋值的,$this 本类 $name='index/hello' $method='get' $option=[] $pattern=[]
$ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern);
// 跨域相关设置
if (!empty($option['cross_domain'])) {
$this->router->setCrossDomainRule($ruleItem, $method);
}
$this->addRuleItem($ruleItem, $method);
return $ruleItem;
}
public function addRuleItem($rule, $method = '*')
{
if (strpos($method, '|')) {
/** @see RuleItem::method() */
$rule->method($method);
$method = '*';
}
$this->rules[$method][] = $rule;
return $this;
}
// thinkphp/library/think/route/RuleItem.php
<?php
/**
* 架构函数
* @access public
* @param Route $router 路由实例
* @param RuleGroup $parent 上级对象
* @param string $name 路由标识
* @param string|array $rule 路由规则
* @param string|Closure $route 路由地址
* @param string $method 请求类型
* @param array $option 路由参数
* @param array $pattern 变量规则
*/
public function __construct(Route $router, RuleGroup $parent, $name, $rule, $route, $method = '*', $option = [], $pattern = [])
{
$this->router = $router;
$this->parent = $parent;
$this->name = $name;
$this->route = $route;
$this->method = $method;
$this->option = $option;
$this->pattern = $pattern;
$this->setRule($rule);
if (!empty($option['cross_domain'])) {
$this->router->setCrossDomainRule($this, $method);
}
}
/**
* 路由规则预处理
* @access public
* @param string $rule 路由规则
* @return void
*/
public function setRule($rule)
{
// 如果是完整匹配会往$this->option数组中压入complete_match = true
// 这里也解释了为什么前面需要对$rule='/'的路由单独处理拼上'$'
if ('$' == substr($rule, -1, 1)) {
// 是否完整匹配
$rule = substr($rule, 0, -1);
$this->option['complete_match'] = true;
}
$rule = '/' != $rule ? ltrim($rule, '/') : '';
// $rule = "hello/:name"
if ($this->parent && $prefix = $this->parent->getFullName()) {
$rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : '');
}
if (false !== strpos($rule, ':')) {
$this->rule = preg_replace(['/[:(w+)]/', '/:(w+)/'], ['<1?>', '<1>'], $rule);
// $this->rule = "hello/<name>"
} else {
$this->rule = $rule;
}
// 生成路由标识的快捷访问
$this->setRuleName();
}
/**
* 设置路由标识 用于URL反解生成
* @access protected
* @param bool $first 是否插入开头
* @return void
*/
protected function setRuleName($first = false)
{
// $this->name = 'indexhello'
if ($this->name) {
// $var array(1) {["name"] => int(1)} 为1 表示动态参数 为2 表示可选参数
$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];
//halt($value);
/*
* array(5) {
* [0] => string(12) "hello/<name>"
* [1] => array(1) {
* ["name"] => int(1)
* }
* [2] => string(9) "127.0.0.1"
* [3] => NULL
* [4] => string(3) "get"
* }
*/
// 注册路由标识
/** @see RuleName::set() */
Container::get('rule_name')->set($name, $value, $first);
}
if (!$this->hasSetRule) {
/** @see RuleName::setRule() */
Container::get('rule_name')->setRule($this->rule, $this);
$this->hasSetRule = true;
}
}
/**
* 分析路由规则中的变量
* @access protected
* @param string $rule 路由规则
* @return array
*/
protected function parseVar($rule)
{
// 提取路由规则中的变量
$var = [];
if (preg_match_all('/<w+??>/', $rule, $matches)) {
foreach ($matches[0] as $name) {
$optional = false;
if (strpos($name, '?')) {
$name = substr($name, 1, -2); // 可选参数
$optional = true;
} else {
$name = substr($name, 1, -1); // 动态参数
}
$var[$name] = $optional ? 2 : 1;
}
}
return $var;
}
// thinkphp/library/think/route/RuleName.php
<?php
/**
* 注册路由标识
* @access public
* @param string $name 路由标识
* @param array $value 路由规则
* @param bool $first 是否置顶
* @return void
*/
public function set($name, $value, $first = false)
{
if ($first && isset($this->item[$name])) {
array_unshift($this->item[$name], $value);
} else {
$this->item[$name][] = $value;
}
// $this->item的值
/*
array(1) {
["index/hello"] => array(1) {
[0] => array(5) {
[0] => string(12) "hello/<name>"
[1] => array(1) {
["name"] => int(1)
}
[2] => string(9) "127.0.0.1"
[3] => NULL
[4] => string(3) "get"
}
}
}
*/
}
/**
* 注册路由规则
* @access public
* @param string $rule 路由规则
* @param RuleItem $route 路由
* @return void
*/
public function setRule($rule, $route)
{
$this->rule[$route->getDomain()][$rule][$route->getMethod()] = $route;
}
![b4391ecfa5d1310d0376c53d7d190ec6.png](https://i-blog.csdnimg.cn/blog_migrate/152d11795115d475b46b9a91da693d9c.jpeg)
代码执行完,回到$this->addRuleItem($ruleItem, $method);打印$this->rules:
![2abcab819f1966a17ee4c728d2b1e878.png](https://i-blog.csdnimg.cn/blog_migrate/e119bbc0f2a22bf78800e15d34ae8c5e.jpeg)
归纳流程:
![63304497d3389e47e0c494bf4c9c6036.png](https://i-blog.csdnimg.cn/blog_migrate/f1c1f658c04c70973093ac4c56e7009b.jpeg)