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,具體怎麼作仍是要看具體業務使用場景,來作不一樣的處理。

相關文章
相關標籤/搜索