ThinkPHP5源码学习篇--Response.php

Response使用

自从使用TP5后,Response和Request就随处可见了,那么在知道Request是请求相关业务的封装类后,Response是什么呢?
通过名字我们也可以看出来,Response是输出的封装类。比如Trace调试的实现、响应码设置、响应头部设置等等。


Response的几种类型

基于不同的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,让浏览器更清楚的知道发生了什么。


重要的send方法

其它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()清空。


《你在终点等我》
没有你的地方都是他乡,
没有你的旅行都是流浪。
太好听了~

你可能感兴趣的:(PHP)