最近看《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作为动态请求,能使用缓存吗?就当能使用,又有什么用呢?我个人觉得业务场景各种情况都有,不少请求类型都是获取一些固定不变或长时间不变的配置项。我们先要知道原来可以做,至于要不要做,就看各自的选择了。
说来惭愧,在看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的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自定义的路由规则看的很懵逼。