问题背景
接上一篇博客:Laravel Model 优化 - 添加属性缓存(attribute cache)
之前实现的AttributeCacheHelper,虽然实现解决每次请求中动态属性重复触发SQL执行的问题,但是也引入了一个新的问题,增加了代码量,维护成本也增加了,添加100个动态属性,就要实现200个函数,使用数量大起来,算是个噩梦了。
实现缓存映射
下面尝试改进这个问题,现将所有的缓存属性整合到一个数组中,通过cacheKey去指向解析函数。
首先实现一个数组 cacheable 用于管理attribute与实体函数的映射。
如balance的属性会映射到refreshBalance函数上。
private $cacheable = [
'posts_count' => 'postCount',
'balancde' => 'refreshBalance',
];
如何处理这个属性映射呢,可以使用 PHP 提供的魔术方法 __ get 来进行处理,然后修改attribute cache helper的 __get 函数,首先会去检查这个属性是否在缓存映射数组中,如果存在的话,直接去缓存中获取该数据,如果数据不存在,则执行映射函数来获取执行后的结果,并缓存起来,反之,如果属性不存在缓存映射数组中,则转发到model本身的 __get 魔术方法中,再进行处理。
cacheable)) {
return $this->getCachedAttribute($key, [$this, $this->cacheable[$key]]);
}
return parent::__get($key);
}
public function getCachedAttribute(string $key, callable $callable)
{
if (!array_key_exists($key, $this->cachedAttributes)) {
$this->setCachedAttribute($key, call_user_func($callable));
}
return $this->cachedAttributes[$key];
}
public function setCachedAttribute(string $key, $value)
{
return $this->cachedAttributes[$key] = $value;
}
public function refresh()
{
$this->cachedAttributes = [];
return parent::refresh();
}
}
完成以上操作后,改进的后属性缓存,便无需定义两个函数了,只需要定义一个映射函数,然后将其指向到需要构造的动态属性中即可。
实现缓存刷新
那么问题又来了,如果实现主动刷新缓存数据呢?尤其是在支付、提现等场景中,往往最后一步都需要主动刷新缓存,再核实一遍,那么再添加一个 refreshAttributeCache 函数,我们可以主动的对属性缓存进行数据刷新。
cacheable)) {
return $this->getCachedAttribute($key, [$this, $this->cacheable[$key]]);
}
return parent::__get($key);
}
public function getCachedAttribute(string $key, callable $callable, $refresh = false)
{
if (!array_key_exists($key, $this->cachedAttributes) || $refresh) {
$this->setCachedAttribute($key, call_user_func($callable));
}
return $this->cachedAttributes[$key];
}
public function setCachedAttribute(string $key, $value)
{
return $this->cachedAttributes[$key] = $value;
}
public function refresh()
{
$this->cachedAttributes = [];
return parent::refresh();
}
public function refreshAttributeCache($key)
{
if (array_key_exists($key, $this->cacheable)) {
return $this->getCachedAttribute($key, [$this, $this->cacheable[$key]], true);
}
}
}
运行测试
将 AttributeCacheHelper 附加到模型中。
'refreshBalance'
];
public function refreshBalance()
{
$lastTransaction = $this->transactions()->latest('id')->select('balance')->first();
$balance = !is_null($lastTransaction) ? $lastTransaction->balance : 0;
return $balance;
}
}
执行结果。
$wallet = Wallet::find(1);
for ($i = 0; $i < 10000; $i++) {
// 只执行一次真实有效查询
dump($wallet->balance);
}
// 重新获取查询结果,执行SQL
$balance = $wallet->refreshAttributeCache('balance');
dd($balance);
结尾
以上就解决掉了当初第一版的attribute cache helper 造成的定义函数比较多,比较难维护,无法主动刷新缓存的弱点,当然也可以反向操作,将无需缓存的属性标注出来,其余的属性都进行缓存起来,个人比较提倡按需所取,hhhh,具体怎么做还是要看具体业务使用场景,来做不同的处理。