Fork me on GitHub

thinkphp5 “$this->redirect()” 到底经历了啥

redirect方法,可以在自定义的控制器实现跳转的功能。看thinkphp 5.1源码发现它是在在jump trait中定义的一个方法。

Controller.php

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
.
.
.
use traits\controller\Jump;

class Controller
{
use Jump;

/**
* 视图类实例
* @var \think\View
*/
protected $view;

/**
* Request实例
* @var \think\Request
*/
protected $request;

.
.
.
}

jump.php 可以看到它抛出一个异常

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
<?php

.
.
.
namespace traits\controller;

use think\Container;
use think\exception\HttpResponseException;
use think\Response;
use think\response\Redirect;

trait Jump
{
.
.
.

protected function redirect($url, $params = [], $code = 302, $with = [])
{
$response = new Redirect($url);

if (is_integer($params)) {
$code = $params;
$params = [];
}

$response->code($code)->params($params)->with($with);

throw new HttpResponseException($response);
}

.
.
.

}

查看HttpResponseException可以发现它是Excepton的子类

HttpResponseException.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
namespace think\exception;

use think\Response;

class HttpResponseException extends \RuntimeException
{
protected $response;

public function __construct(Response $response)
{
$this->response = $response;
}

public function getResponse()
{
return $this->response;
}

}

RuntimeException是SPL中的类,它继承Exception

1
2
class RuntimeException extends Exception {
}

到此我们知道了redirect()方法其实是抛出一个异常,那它是在哪里调用然后输出(跳转)呢?

从入口文件index.php开始。发现先加载base.php,然后执行App类的run方法。然后调用返回对象的send方法

1
2
3
4
5
6
7
8
9
10
<?php
namespace think;

// 加载基础文件
require __DIR__ . '/../thinkphp/base.php';

// 支持事先使用静态方法设置Request对象和Config对象

// 执行应用并响应
Container::get('app')->run()->send();

App类,发现它catch了一个HttpResponseException,返回Redirect类型的Response 对象

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php
namespace think;

use think\exception\ClassNotFoundException;
use think\exception\HttpResponseException;
use think\route\Dispatch;

/**
* App 应用管理
*/
class App implements \ArrayAccess
{
const VERSION = '5.1.5';
.
.
.

// 执行应用程序
public function run()
{
// 初始化应用
$this->initialize();

try {
if ($this->bind) {
// 模块/控制器绑定
$this->route->bind($this->bind);
} elseif ($this->config('app.auto_bind_module')) {
// 入口自动绑定
$name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
$this->route->bind($name);
}
}

// 读取默认语言
$this->lang->range($this->config('app.default_lang'));
if ($this->config('app.lang_switch_on')) {
// 开启多语言机制 检测当前语言
$this->lang->detect();
}

$this->request->langset($this->lang->range());

// 加载系统语言包
$this->lang->load([
$this->thinkPath . 'lang/' . $this->request->langset() . '.php',
$this->appPath . 'lang/' . $this->request->langset() . '.php',
]);

// 监听app_dispatch
$this->hook->listen('app_dispatch');

// 获取应用调度信息
$dispatch = $this->dispatch;
if (empty($dispatch)) {
// 进行URL路由检测
$dispatch = $this->routeCheck();
}

// 记录当前调度信息
$this->request->dispatch($dispatch);

// 记录路由和请求信息
if ($this->debug) {
$this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
$this->log('[ HEADER ] ' . var_export($this->request->header(), true));
$this->log('[ PARAM ] ' . var_export($this->request->param(), true));
}

// 监听app_begin
$this->hook->listen('app_begin');

// 请求缓存检查
$this->request->cache(
$this->config('app.request_cache'),
$this->config('app.request_cache_expire'),
$this->config('app.request_cache_except')
);

// 执行调度
$data = $dispatch->run();

} catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}

$this->middlewareDispatcher->add(function (Request $request, $next) use ($data) {
// 输出数据到客户端
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默认自动识别响应输出类型
$isAjax = $request->isAjax();
$type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type');

$response = Response::create($data, $type);
} else {
$response = Response::create();
}
return $response;
});

$response = $this->middlewareDispatcher->dispatch($this->request);

// 监听app_end
$this->hook->listen('app_end', $response);

return $response;
}

.
.
.
}

继续看Redirect类父类Responsesend方法。它设置了header头等

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
<?php	

namespace think;

use think\response\Redirect as RedirectResponse;

class Response
{
.
.
.

/**
* 发送数据到客户端
* @access public
* @return void
* @throws \InvalidArgumentException
*/
public function send()
{
// 监听response_send
Container::get('hook')->listen('response_send', $this);

// 处理输出数据
$data = $this->getContent();

// Trace调试注入
if (Container::get('env')->get('app_trace', Container::get('app')->config('app.app_trace'))) {
Container::get('debug')->inject($this, $data);
}

if (200 == $this->code && $this->allowCache) {
$cache = Container::get('request')->getCache();
if ($cache) {
$this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate';
$this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT';
$this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT';

Container::get('cache')->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]);
}
}

if (!headers_sent() && !empty($this->header)) {
// 发送状态码
http_response_code($this->code);
// 发送头部信息
foreach ($this->header as $name => $val) {
header($name . (!is_null($val) ? ':' . $val : ''));
}
}

$this->sendData($data);

if (function_exists('fastcgi_finish_request')) {
// 提高页面响应
fastcgi_finish_request();
}

// 监听response_end
Container::get('hook')->listen('response_end', $this);

// 清空当次请求有效的数据
if (!($this instanceof RedirectResponse)) {
Container::get('session')->flush();
}
}

.
.
.
}

最后应用终止时会执行shutdown处理方法。它在Error.php已经注册了shutdown方法为Error类的appShutdown方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Error
{
.
.
.
public static function register()
{
error_reporting(E_ALL);
set_error_handler([__CLASS__, 'appError']);
set_exception_handler([__CLASS__, 'appException']);
register_shutdown_function([__CLASS__, 'appShutdown']);
}
.
.
.
}

appShutdown方法

1
2
3
4
5
6
7
8
9
10
11
12
public static function appShutdown()
{
if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
// 将错误信息托管至think\ErrorException
$exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']);

self::appException($exception);
}

// 写入日志
Container::get('log')->save();
}

OK.

另:thinkphp 5.0源码阅读参考:https://www.kancloud.cn/zmwtp/tp5/155311

------本文结束感谢阅读------
欣赏此文?求鼓励,求支持!