探究Thinkphp URL路由流程
探究Thinkphp URL路由流程
Alexthinkphp源码浅析-3.视图层解析流程
1、建立控制器方法
- 路径
thinkphp_5.0.7_core/application/index/controller/test.php
- 代码 建立一个测试方法
1
2
3
4
5
6
7public function test($name = "")
{
$this->assign('name',$name);
$res = view('test');
return $res;
//$this->display();
}
2、建立视图模板文件
- 路径
thinkphp_5.0.7_core/application/index/view/test/test.html
- 代码
1
2
3
4
5
6
7
8
9
10
11
12<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr />
项目更目录:__ROOT__
<h1>{$name}</h1>
</body>
</html>
3、目标 :
- 同过test控制器test方法:接收参数name,并将变量
$name
赋值给视图模板层输出,探究其中运行即解析流程。
4、运行流程
4.1 入口文件
index.php
1
2
3
4// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';4.2 start.php
1
2
3require __DIR__ . '/base.php';
// 执行应用
App::run()->send();4.2.1 App::run() 应用主体运行
4.2.1.1 实例化Request
1
is_null($request) && $request = Request::instance();
4.2.1.2 初始化公共配置
1
$config = self::initCommon();
4.2.1.3 默认路由绑定
1
2
3
4
5
6
7
8
9
10if (defined('BIND_MODULE')) {
// 模块/控制器绑定
BIND_MODULE && Route::bind(BIND_MODULE);
} elseif ($config['auto_bind_module']) {
// 入口自动绑定
$name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
Route::bind($name);
}
}4.2.1.4 请求过滤
1
$request->filter($config['default_filter']);
4.2.1.5 多语言设置与加载
1
2
3
4
5
6
7
8
9
10
11
12
13if ($config['lang_switch_on']) {
// 开启多语言机制 检测当前语言
Lang::detect();
} else {
// 读取默认语言
Lang::range($config['default_lang']);
}
$request->langset(Lang::range());
// 加载系统语言包
Lang::load([
THINK_PATH . 'lang' . DS . $request->langset() . EXT,
APP_PATH . 'lang' . DS . $request->langset() . EXT,
]);4.2.1.6 应用调度路由解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16$dispatch = self::$dispatch;
if (empty($dispatch)) {
// +----------------------------------------------------------------------
// | self::routeCheck($request, $config) 开始路由
// +----------------------------------------------------------------------
// 进行URL路由检测
$dispatch = self::routeCheck($request, $config);
}
// 记录当前调度信息
$request->dispatch($dispatch);
// 记录路由和请求信息
if (self::$debug) {
Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
}- self::routeCheck($request, $config) 开始路由 thinkphp具体路由实现步骤存这里开始
源码浅析一、路由
- self::routeCheck($request, $config) 开始路由 thinkphp具体路由实现步骤存这里开始
4.2.1.7 监听app_begin
1
Hook::listen('app_begin', $dispatch);
4.2.1.8 请求缓存检查
1
$request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']);
4.2.1.9 根据调度类型执行方法 跳转页面、控制器/方法加载等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33switch ($dispatch['type']) {
case 'redirect':
// 执行重定向跳转
$data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
break;
case 'module':
// 模块/控制器/操作
$data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null);
break;
case 'controller':
// 执行控制器操作
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']);
break;
case 'method':
// 执行回调方法
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = self::invokeMethod($dispatch['method'], $vars);
break;
case 'function':
// 执行闭包
$data = self::invokeFunction($dispatch['function']);
break;
case 'response':
$data = $dispatch['response'];
break;
default:
throw new \InvalidArgumentException('dispatch type not support');
}
} catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}- 这里类型为
module
执行对应映射控制器、方法返回方法执行内容 - 映射时 会初始化一个视图
instance
=> 初始化构造方法->初始化模板引擎$this->engine((array) $engine);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//path thinkphp_5.0.7_core/thinkphp/library/think/View.php
/**
* 初始化视图
* @access public
* @param array $engine 模板引擎参数
* @param array $replace 字符串替换参数
* @return object
*/
public static function instance($engine = [], $replace = [])
{
if (is_null(self::$instance)) {
self::$instance = new self($engine, $replace);
}
return self::$instance;
}- 这里类型为
- 执行时
$res = view('test');
创建一个Response
对象
代码 路径thinkphp_5.0.7_core/thinkphp/helper.php
1 | if (!function_exists('view')) { |
4.2.1.10 清空类的实例化
1
Loader::clearInstance();
4.2.1.11 输出数据到客户端
1
2
3
4
5
6
7
8
9
10if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默认自动识别响应输出类型
$isAjax = $request->isAjax();
$type = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type');
$response = Response::create($data, $type);
} else {
$response = Response::create();
}4.2.1.12 监听app_end
1
Hook::listen('app_end', $response);
4.2.1.13 返回
1
return $response;
4.2.2
App::run()->send();
发送数据到客户端都数据返回给客户端了?那么内容标签解析呢?哪里执行的?这里说明下如果控制器使用的是
print
、echo
等直接输出打印的,那么映射控制器方法的时候是会直接接收到内容,其它返回还需接收处理(模板标签替换也在这块执行)。接下来流程:接收处理数据->编辑header(状态码、信息头等) -> 输出内容->用户端接收响应。4.2.2.1 输出数据处理
1
$data = $this->getContent(); // 返回html页面内容
getContent()
代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/**
* 获取输出数据
* @return mixed
*/
public function getContent()
{
if (null == $this->content) {
// +----------------------------------------------------------------------
// | $this->data
// | 'test' //这里数据就是在控制器方法中的view方法中创建response 时初始化赋值的数据
// +----------------------------------------------------------------------
$content = $this->output($this->data);
// +----------------------------------------------------------------------
// | $content 返回html
// +----------------------------------------------------------------------
if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
$content,
'__toString',
])
) {
throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
}
$this->content = (string) $content;
}
return $this->content;
}接着
output()
1
2//path thinkphp_5.0.7_core/thinkphp/library/think/Response.php
$content = $this->output($this->data);这里需注意,虽然
think\Response
当前自身类中有output()
方法,但是却不是执行自身的output()
方法,而是由于在run()
方法,映射类的时候test
方法view
时已经创建了实例,后续操作的也是view实例,所以这里的如果控制器中未实例化view实例在是在通过 Response::create()
创建了think\response\View
实例,$this->output($this->data)
自然操作的是think\response\View
类的putout()
方法^_^4.2.2.2 初始化视图
ViewTemplate::instance
1
2//path think\View instance()
ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str'));// ->fetch($data, $this->vars, $this->replace);4.2.2.3 渲染模板文件
fetch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57//path think\View fetch()
/**
* 解析和获取模板内容 用于输出
* @param string $template 模板文件名或者内容
* @param array $vars 模板输出变量
* @param array $replace 替换内容
* @param array $config 模板参数
* @param bool $renderContent 是否渲染内容
* @return string
* @throws Exception
*/
public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false)
{
// 模板变量
$vars = array_merge(self::$var, $this->data, $vars);
// +----------------------------------------------------------------------
// array (size=1)
// 'name' => string 'hello' (length=5)
// +----------------------------------------------------------------------
// 页面缓存
ob_start();
ob_implicit_flush(0);
// 渲染输出
$method = $renderContent ? 'display' : 'fetch';
// +----------------------------------------------------------------------
// | fetch
// +----------------------------------------------------------------------
//var_dump($method); //fetch
$res = $this->engine->$method($template, $vars, $config);
// var_dump($this->engine); //thinkphp_5.0.7_core/thinkphp/library/think/View.php
// 获取并清空缓存
$content = ob_get_clean();
// 内容过滤标签
Hook::listen('view_filter', $content);
// 允许用户自定义模板的字符串替换
$replace = array_merge($this->replace, $replace);
// +----------------------------------------------------------------------
// $this->replace
// array (size=5)
// '__ROOT__' => string '/thinkphp_5.0.7_core/public' (length=27)
// '__URL__' => string '/thinkphp_5.0.7_core/public/index.php/index/test' (length=48)
// '__STATIC__' => string '/thinkphp_5.0.7_core/public/static' (length=34)
// '__CSS__' => string '/thinkphp_5.0.7_core/public/static/css' (length=38)
// '__JS__' => string '/thinkphp_5.0.7_core/public/static/js' (length=37)
// +----------------------------------------------------------------------
if (!empty($replace)) {
// +----------------------------------------------------------------------
// | // 系统标签 或 自定义配置标签在此处替换
// +----------------------------------------------------------------------
$content = strtr($content, $replace);
}
return $content;
}模板处理
$res = $this->engine->$method($template, $vars, $config);
这里$method
为fetch
1 | path think\Template fetch() |
编译视图文件
$this->compiler($content, $cacheFile);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83$this->compiler($content, $cacheFile);
path think\Template compiler()
/**
* 编译模板文件内容
* @access private
* @param string $content 模板内容
* @param string $cacheFile 缓存文件名
* @return void
*/
private function compiler(&$content, $cacheFile)
{
// 判断是否启用布局
if ($this->config['layout_on']) {
if (false !== strpos($content, '{__NOLAYOUT__}')) {
// 可以单独定义不使用布局
$content = str_replace('{__NOLAYOUT__}', '', $content);
} else {
// 读取布局模板
$layoutFile = $this->parseTemplateFile($this->config['layout_name']);
if ($layoutFile) {
// 替换布局的主体内容
$content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile));
}
}
} else {
$content = str_replace('{__NOLAYOUT__}', '', $content);
}
//var_dump($content); 解析前
// +----------------------------------------------------------------------
// <!DOCTYPE html>
// <html lang="en">
// <head>
// <meta charset="UTF-8">
// <title>Title</title>
// </head>
// <body>
// <hr />
// __ROOT__
// <h1>{$name}</h1>
// </body>
// </html>
// +----------------------------------------------------------------------
// 模板解析
$this->parse($content);
/**
//var_dump($content); 解析后
// +----------------------------------------------------------------------
// <!DOCTYPE html>
// <html lang="en">
// <head>
// <meta charset="UTF-8">
// <title>Title</title>
// </head>
// <body>
// <hr />
// __ROOT__
// <h1><?php echo $name; ?></h1>
// </body>
// </html>
// +----------------------------------------------------------------------
*/
if ($this->config['strip_space']) {
/* 去除html空格与换行 */
$find = ['~>\s+<~', '~>(\s+\n|\r)~'];
$replace = ['><', '>'];
$content = preg_replace($find, $replace, $content);
}
// 优化生成的php代码
$content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content);
// 模板过滤输出
$replace = $this->config['tpl_replace_string'];
$content = str_replace(array_keys($replace), array_values($replace), $content);
// 添加安全代码及模板引用记录
$content = '<?php if (!defined(\'THINK_PATH\')) exit(); /*' . serialize($this->includeFile) . '*/ ?>' . "\n" . $content;
// 编译存储
$this->storage->write($cacheFile, $content);
$this->includeFile = [];
return;
}模板解析入口
parse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81path think\Template parse()
/**
* 模板解析入口
* 支持普通标签和TagLib解析 支持自定义标签库
* @access public
* @param string $content 要解析的模板内容
* @return void
*/
public function parse(&$content)
{
// 内容为空不解析
if (empty($content)) {
return;
}
// 替换literal标签内容
// +----------------------------------------------------------------------
// literal 原样输出 防止标签被解析
// {literal} Hello,{$name}!{/literal}
// +----------------------------------------------------------------------
$this->parseLiteral($content);
// 解析继承
// +----------------------------------------------------------------------
// | {extend name="base" /}
// +----------------------------------------------------------------------
$this->parseExtend($content);
// 解析布局
// +----------------------------------------------------------------------
// | {layout name="layout" /} 使用参考地址 http://www.kancloud.cn/manual/thinkphp5/125013
// +----------------------------------------------------------------------
$this->parseLayout($content);
// 检查include语法
// +----------------------------------------------------------------------
// | {include file='模版文件1,模版文件2,...' /}
// +----------------------------------------------------------------------
$this->parseInclude($content);
// 替换包含文件中literal标签内容
$this->parseLiteral($content);
// 检查PHP语法
$this->parsePhp($content);
// 获取需要引入的标签库列表
// 标签库只需要定义一次,允许引入多个一次
// 一般放在文件的最前面
// 格式:<taglib name="html,mytag..." />
// 当TAGLIB_LOAD配置为true时才会进行检测
if ($this->config['taglib_load']) {
$tagLibs = $this->getIncludeTagLib($content);
if (!empty($tagLibs)) {
// 对导入的TagLib进行解析
foreach ($tagLibs as $tagLibName) {
$this->parseTagLib($tagLibName, $content);
}
}
}
// 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀
if ($this->config['taglib_pre_load']) {
$tagLibs = explode(',', $this->config['taglib_pre_load']);
foreach ($tagLibs as $tag) {
$this->parseTagLib($tag, $content);
}
}
// 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀
$tagLibs = explode(',', $this->config['taglib_build_in']);
foreach ($tagLibs as $tag) {
$this->parseTagLib($tag, $content, true);
}
// 解析普通模板标签 {$tagName}
$this->parseTag($content);
// 还原被替换的Literal标签
$this->parseLiteral($content, true);
return;
}至此内容模板标签解析完成,接下来返回APP,继续执行send剩余代码,编辑header状态码、头信息->输出内容->执行响应->清空本次请求,整个响应用户操作结束。