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

问题背景

接上一篇博客:Laravel Model 优化 - 添加属性缓存(attribute cache)php

以前实现的AttributeCacheHelper,虽然实现解决每次请求中动态属性重复触发SQL执行的问题,可是也引入了一个新的问题,增长了代码量,维护成本也增长了,添加100个动态属性,就要实现200个函数,使用数量大起来,算是个噩梦了。segmentfault

实现缓存映射

下面尝试改进这个问题,现将全部的缓存属性整合到一个数组中,经过cacheKey去指向解析函数。数组

首先实现一个数组 cacheable 用于管理attribute与实体函数的映射。缓存

如balance的属性会映射到refreshBalance函数上。函数

private $cacheable = [
        'posts_count' => 'postCount',
        'balancde'    => 'refreshBalance',
];

如何处理这个属性映射呢,可使用 PHP 提供的魔术方法 __ get 来进行处理,而后修改attribute cache helper的 __get 函数,首先会去检查这个属性是否在缓存映射数组中,若是存在的话,直接去缓存中获取该数据,若是数据不存在,则执行映射函数来获取执行后的结果,并缓存起来,反之,若是属性不存在缓存映射数组中,则转发到model自己的 __get 魔术方法中,再进行处理。post

<?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 附加到模型中。this

<?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;
    }
}

执行结果。spa

$wallet = Wallet::find(1);
for ($i = 0; $i < 10000; $i++) {
    // 只执行一次真实有效查询
    dump($wallet->balance);
}

// 从新获取查询结果,执行SQL
$balance = $wallet->refreshAttributeCache('balance');
dd($balance);

结尾

以上就解决掉了当初初版的attribute cache helper 形成的定义函数比较多,比较难维护,没法主动刷新缓存的弱点,固然也能够反向操做,将无需缓存的属性标注出来,其他的属性都进行缓存起来,我的比较提倡按需所取,hhhh,具体怎么作仍是要看具体业务使用场景,来作不一样的处理。

相关文章
相关标签/搜索