自从使用TP5后,Response和Request就随处可见了,那么在知道Request是请求相关业务的封装类后,Response是什么呢?
通过名字我们也可以看出来,Response是输出的封装类。比如Trace调试的实现、响应码设置、响应头部设置等等。
基于不同的type封装响应逻辑实现定制化向客户端输出。
/**
* 创建Response对象
* @access public
* @param mixed $data 输出数据
* @param string $type 输出类型
* @param int $code
* @param array $header
* @param array $options 输出参数
* @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse
*/
public static function create($data = '', $type = '', $code = 200, array $header = [], $options = [])
{
//Response有Json、Jsonp、Redirect、View、Xml5个不同功能的子类
//可自定义Response类 一般type参数默认都为json或html
//type ==> redirect 将实例化think\response\Redirect对象
//type ==> json 将实例化think\response\Json对象
//type ==> jsonp 将实例化think\response\Jsonp对象
//观察下发现,区别在与 响应header设置的不同,输出数据封装不同
//对比下 output方法
$class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));
//所以一般都是new static
if (class_exists($class)) {
$response = new $class($data, $code, $header, $options);
} else {
$response = new static($data, $code, $header, $options);
}
return $response;
}
通过调用静态static方法来实例化对象,其实直接new也能实例Response,但是这样操作无法选择不同的类型。
正常情况下,使用较多的有四种
Response::create();
控制器业务执行完成后,若不需输出body响应数据,按照以前的做法可能就是执行exit结束了。但是如此这么做,会导致没有执行到Response的send方法,至于send方法有什么用后面再描述。所以基于此考虑,我们自行调用Response::create()->send()或在控制器方法内return。
Response::create($data, ‘html’);
Response::create($data, ‘json’);
这是使用最多的两种,一般情况我们业务代码执行完直接返回字符串文本,或返回json格式的报文,html其实就实例化Response类,和不穿参数调用过程相似。而传入json略有不同,
class Json extends Response
{
// 输出参数
protected $options = [
'json_encode_param' => JSON_UNESCAPED_UNICODE,
];
protected $contentType = 'application/json';
/**
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
* @return mixed
* @throws \Exception
*/
protected function output($data)
{
try {
// 返回JSON数据格式到客户端 包含状态信息
$data = json_encode($data, $this->options['json_encode_param']);
if ($data === false) {
throw new \InvalidArgumentException(json_last_error_msg());
}
return $data;
} catch (\Exception $e) {
if ($e->getPrevious()) {
throw $e->getPrevious();
}
throw $e;
}
}
}
实例化Response的子类Json对象,内部设置了contentType,和重写output方法。观察这里的方法可以看出来,$data是array类型数据,经json_encode函数处理后,返回json格式的字符串,同时后续将设置响应头部contentType为json对应内容。
Response::create($data, ‘rediret’);
若传入redirect会如何呢?
/**
* 处理数据
* @access protected
* @param mixed $data 要处理的数据
* @return mixed
*/
protected function output($data)
{
$this->header['Location'] = $this->getTargetUrl();
return;
}
/**
* 获取跳转地址
* @return string
*/
public function getTargetUrl()
{
if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) {
return $this->data;
} else {
return Url::build($this->data, $this->params);
}
}
其实也就看output方法即可,其它方法比如重定向传值啊,获取跳转URL信息等等使功能更加完善。
所以重定向功能在于Location,同时getTargetUrl方法会解析传入的数据,我们可以传入一个URL地址,也可以传入模块名、控制器名、方法名,进行内部重定向。
再和json比较一下就发现挺有意思的,json封装数据格式,而redirect对headaer内对Location设值,如此这般实现了重定向功能,另外可执行类code方法将http响应码设置成302,让浏览器更清楚的知道发生了什么。
其它jsonp、View、Xml都来观察下output方法,都是为了将数据处理成想要的格式。
/**
* 获取输出数据
* @return mixed
*/
public function getContent()
{
if (null == $this->content) {
//基本上 不同的Response子类区分就在output方法里了
//Json.php 将数据转换成json字符串
//Redirect.php 将设置Location头部来实现重定向跳转
//Xml.php 将数据转换成Xml格式字符串
//View.php 将渲染模版文件
$content = $this->output($this->data);
//经处理后的$content, 非null、非string类型、非数字类型、且无__toString方法,则类型错误抛出异常
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;
}
/**
* 发送数据到客户端
* @access public
* @return mixed
* @throws \InvalidArgumentException
*/
public function send()
{
// 监听response_send
Hook::listen('response_send', $this);
// 处理输出数据 若数据格式不满足默认要求,则抛出异常
$data = $this->getContent();
// Trace调试注入
if (Env::get('app_trace', Config::get('app_trace'))) {
Debug::inject($this, $data);
}
//状态码为200 且 配置了缓存选项对header头部进行缓存处理
if (200 == $this->code) {
//从Request对象中获取前置可能被赋值的缓存数据
$cache = Request::instance()->getCache();
//若存在缓存,则进行响应头部设置
if ($cache) {
//设置缓存有效持续时间
//must-revalidate:可缓存数据,但是必须再请求源服务器确认
//客户端可缓存数据,但是当前缓存是否可用呢?这还得向服务器确认下,比如返回304,那用就行了
$this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate';
//$this->header['Cache-Control'] = 'max-age=' . $cache[1] . '';
//当前时间 ==> 指代资源最后更新时间
$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';
//将缓存写入到Cache内
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) {
if (is_null($val)) {
header($name);
} else {
header($name . ':' . $val);
}
}
}
//输出数据
echo $data;
//当PHP运行在FastCGI模式时,PHP FPM提供了一个名为fastcgi_finish_request的方法
//执行该函数后,客户端响应就已经结束,提高体验
//有点类似与将PHP缓冲区刷出的感觉
if (function_exists('fastcgi_finish_request')) {
// 提高页面响应
fastcgi_finish_request();
}
// 监听response_end
Hook::listen('response_end', $this);
// 清空当次请求有效的数据
if (!($this instanceof RedirectResponse)) {
//站内重定向跳转,可以隐式通过SESSION传参,检测并删除指定SESSION域的数据
Session::flush();
}
}
进入send()时,会立马执行getContent方法,然后执行output(),所以不同的子类实现不同逻辑,就基于此了。
再看Trace调试注入,如果配置项启动了app_trace,则将注入代码运行的环境检测,如执行SQL语句、执行时间、加载文件、资源损耗等等,非常详细。
//当PHP运行在FastCGI模式时,PHP FPM提供了一个名为fastcgi_finish_request的方法
//执行该函数后,客户端响应就已经结束,提高体验
//有点类似与将PHP缓冲区刷出的感觉
if (function_exists('fastcgi_finish_request')) {
// 提高页面响应
fastcgi_finish_request();
}
这看起来是一个很不错的操作,链接,详细介绍了使用fastcgi_finish_request提高页面响应速度
// 清空当次请求有效的数据
if (!($this instanceof RedirectResponse)) {
//站内重定向跳转,可以隐式通过SESSION传参,检测并删除指定SESSION域的数据
Session::flush();
}
刚看到这里觉得挺奇怪的,为什么说清空当前请求有效数据呢?而且我们需要清除SESSION的话,完全可以使用Session::clear()。看这个if判断,若当前非RedirectResponse执行则执行Session::flush()。
实际上为了内部重定向能够传值,Response子类Redirect有一个with方法,调用Session::flah()将值写入到SESSION的特定区间内,所以非重定向的请求,针对进行flush()清空。
《你在终点等我》
没有你的地方都是他乡,
没有你的旅行都是流浪。
太好听了~