Laravel Model 优化 - 属性缓存(attribute cache) 优化

Siming

问题背景

接上一篇博客: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 魔术方法中,再进行处理。

<?php

namespace App\Traits;

trait AttributeCacheHelper
{
    private $cachedAttributes = [];

    public function __get($key)
    {
        if (array_key_exists($key, $this->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 函数,我们可以主动的对属性缓存进行数据刷新。

<?php

namespace App\Traits;

trait AttributeCacheHelper
{
    private $cachedAttributes = [];

    public function __get($key)
    {
        if (array_key_exists($key, $this->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 附加到模型中。

<?php

namespace App;

use App\Model;

class Wallet extends Model
{
    use WalletRepo, WalletResolver, AttributeCacheHelper;

    private $cacheable = [
        'balance'   =>'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,具体怎么做还是要看具体业务使用场景,来做不同的处理。

阅读 1.2k

Bug总工程师

301 声望
16 粉丝
0 条评论

Bug总工程师

301 声望
16 粉丝
文章目录
宣传栏