ThinkPHP5源码学习篇--请求缓存

数据结构很有意思

最近看《PHP核心技术与最佳实践》一书时,有提到PHP的数组实现是依赖于哈希表,包含哈希碰撞、哈希函数、拉链表等等,觉得很有意思,于是重拾大学时期简单学过的数据结构,结果在看Mysql资料时,也涉及到数据结构方面知识,感觉打开了一扇新大门。


请求缓存学习

学习了TP5的请求缓存内容,特此来总结一下。

在App::run()方法内,存在如下代码

$request->cache(
    $config['request_cache'],
    $config['request_cache_expire'],
    $config['request_cache_except']
);

先来看看config配置对应三个参数是什么意思
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
’request_cache’ => false,
// 请求缓存有效期
’request_cache_expire’ => 3600,
// 全局请求缓存排除规则
’request_cache_except’ => [],
从注释文字来理解,
request_cache用来控制请求缓存是否启动
request_cache_expire表示缓存支持的时间
request_cache_except作类似白名单作用,若想要排除指定请求不使用缓存,可预先设置


请求缓存是什么呢?

了解一下http的304状态(Not Modified),借用《图解HTTP》的描述是:
该状态码表示客户端发生附带条件的请求时,服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304Not Modified(服务器端资源未改变,可直接使用客户端未过期的缓存)。304状态吗返回时,不包含任何响应的主体部分。
附带条件是指采用GET方法的请求报文中包含If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since中任一首部。

先举一个例子:
访问html类型文件,
第一次访问状态码:Status Code: 200 OK
第二次访问状态吗:Status Code: 304 Not Modified
我对这部分原理还是似懂非懂的感觉,所以就不说明避免误导了。主要是想通过访问html文件来说明,返回304状态码的前提必须得有一次200初次通信,那么在后续判断了文件未过期,且内容未修改后,服务器端直接返回304状态码,不需要再传输content,减少了通信数据量,页面加载缓存下来的数据来提高的体验。

上面说了html静态页面缓存的好处,再回到thinkphp5,就有疑惑的地方了,一般PHP作为动态请求,能使用缓存吗?就当能使用,又有什么用呢?我个人觉得业务场景各种情况都有,不少请求类型都是获取一些固定不变或长时间不变的配置项。我们先要知道原来可以做,至于要不要做,就看各自的选择了。


看看TP5的实现

说来惭愧,在看Request类源码的时候,好像把cache()方法忽略了

public function cache($key, $expire = null, $except = [], $tag = null)

四个参数:
$key:缓存标识
$expire = null:缓存有效期
$except = []:缓存排除
$tag = null:缓存标签

//如果$except传入非数据,则表示作为$tag传入
if (!is_array($except)) {
    $tag    = $except;
    $except = [];
}
满足三个条件,则进行缓存的检查
false !== $key && $this->isGet() && !$this->isCheckCache

1.如果$key的值为false,则缓存默认不开启
2.判断请求类型是否为GET,非GET请求不进行缓存操作
3.isCheckCache全局变量初始化值为false,if内的第一句就是改值为true,用来标注缓存逻辑是否已处理

if (false === $expire) {
     return;
}
$expire变量来设置缓存的有效时间,若值为false,不再执行后续逻辑

前置检测结束,执行缓存的实际操作

if ($key instanceof \Closure) {
     //$key为闭包函数,执行后获取返回内容作为$key的新值
     $key = call_user_func_array($key, [$this]);
} elseif (true === $key) {
     //若$key为true,这是比较常用的类型,表示对请求URL进行缓存
     //遍历缓存排除的URL相关信息
     foreach ($except as $rule) {
          //请求地址:http://localhost/thinkphp5/public/index/Index/indexTest
          //$this->url()返回内容:/thinkphp5/public/index/Index/indexTest
          //所以$except(request_cache_except)设置较为麻烦,项目可能是会隐藏域名或不隐藏的,那么规则定义要根据实际情况来做区分
          //根据本地测试环境,请求缓存作用域可在项目、模块、控制器、方法区分
          //对整个项目做免缓存操作,(当前这没什么必要,$key传入false即可),可以设置$except为/thinkphp5
          //对一个模块做免缓存操作,设置$except为/thinkphp5/public/index
          //针对模块内一个控制器免缓存,设置$except为/thinkphp5/public/index/Index
          //针对指定控制期的方法免缓存,设置$except为/thinkphp5/public/index/Index/indexTest
          if (0 === stripos($this->url(), $rule)) {
               //将$except的内容与url进行比对,若满足stripos函数处理后===0,即不再进行缓存
               return;
          }
     }
     //当前为针对URL类型
     $key = '__URL__';
} elseif (strpos($key, '|')) {
     //$key内包含|字符,通过下面代码判断,处理逻辑为|前是真实key,|后是一个函数名
     //函数对$key进行处理,获取新的$key值
     list($key, $fun) = explode('|', $key);
}

//若$key存在"__"两个下划线字符,则进行特殊规则替换
if (false !== strpos($key, '__')) {
     //类似于统一标识替换功能,比如常规__URL__会替换为对url信息经过MD5加密的密文
     $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key);
}
//经过url的md5加密,新请求可以根据自身url的md5加密值来查找是否已存在缓存

//若$key内包含请求参数
if (false !== strpos($key, ':')) {
     //获取请求参数
     $param = $this->param();
     //key-value键值对遍历请求参数
     foreach ($param as $item => $val) {
          //若值为string类型 && $key内包含当前item
          if (is_string($val) && false !== strpos($key, ':' . $item)) {
               //将:item 替换 成实际值
               $key = str_replace(':' . $item, $val, $key);
          }
     }
} elseif (strpos($key, ']')) {
     if ('[' . $this->ext() . ']' == $key) {
          // 缓存某个后缀的请求
          $key = md5($this->url());
     } else {
          return;
     }
}
//对$key值进行函数处理
if (isset($fun)) {
     //如果$fun变量存在,那么这一定是一个可以运行的函数
     //$key作为参数运行$fun方法,$key赋值新的内容
     $key = $fun($key);
}

//以下两个判断 若存在缓存不需要执行实际业务逻辑
//通过框架自定义的 HttpResponseException 将Response的实例抛出
//以实现后续 Response->send()的方法
if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) {
     //若请求头部包含if_modified_since,根据系统预设置的缓存到期时间 判断是否到期
     //若缓存仍在保质期内,则直接返回304状态码

     //创建Response对象,设置Status Code为304
     $response = Response::create()->code(304);
     //将Response实例作为参数 实例化HttpResponseException自定义异常类
     //作用就是为了中止当前方法,跳出到catch到指定位置。同时携带特定到Response对象
     throw new \think\exception\HttpResponseException($response);
} elseif (Cache::has($key)) {
     //比如前端Cache-Control:no-cache强制刷新,不会携带if_modified_since请求头部
     //进行Cache内查询$key对应缓存是否存在

     //第一次请求成功后会将缓存相关信息 通过Cache类写入到指定存储器,以$key作为标识
     //后续请求若Cache::has()方法判断$key对应到缓存数据存在,则调用Cache::get()方法获取
     //存储数据内容为:响应body、响应header
     //将body和header信息传入到新实例化到Response对象
     list($content, $header) = Cache::get($key);
     $response = Response::create($content)->header($header);

     //实例化HttpResponseException自定义异常类 抛出并在别处捕捉
     throw new \think\exception\HttpResponseException($response);
} else {
     //首次请求,将缓存对应字段 暂存cache内,后续会通过Cache类来写入

     //首次请求,未查找到已存在到缓存数据
     //当前请求满足缓存写入条件
     //将$key $expire $tag 存入到当前Response类到cache变量里
     //后续将通过Cache类来实现缓存文件到写入
     $this->cache = [$key, $expire, $tag];
}

这里做了两个分支判断
第一先判断请求头部是否包含if_modified_since,再根据预设的有效期期限时间,和当前时间进行比较,将客户端请求和服务器参数结合。
第二不存在if_modified_since头部,根据Cache类查找服务器端本地是否存在缓存,直接获取之前已缓存的数据。
以上两个条件都不满足,就做赋值记录操作

再看App::run()内的两句代码

    $data = self::exec($dispatch, $config);
} catch (HttpResponseException $exception) {
   $data = $exception->getResponse();
}

在检查缓存的过程中,若抛出HttpResponseException异常将被捕获,同时获取被传递的Response对象;否则执行exec方法进行实际的逻辑处理


结合Response类

将视线转移到Response的send()方法

//状态码为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['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);
          }
     }
}

若响应状态码为200,当前请求正常处理。判断是否有进行缓存的设置,对三个头部写入指定值,让客户端知道当前请求结果可缓存,但是能不能用还得继续请求确认。
注意must-revalidate参数,动态请求不等同静态资源,数据是否变化、数据是否可用,决定权仍应该在服务端。

此时若出现304到状态码又如何呢?会进行未发送头部的逻辑,发送304状态码到客户端,并执行头部重新赋值等操作。


总结

TP5自定义的路由规则看的很懵逼。

你可能感兴趣的:(PHP)