Laravel ORM 對 Model::find 方法進行快取

Y_xx發表於2019-02-16

前段時間做專案時候,想要在不改變方法簽名的情況下,給 Model::find 方法做個快取。而且想要做到即插即用。

1.先看一下當我們呼叫 find 方法時,框架幹了什麼?

找到 IlluminateDatabaseEloquentModel 的程式碼,搜尋 find,沒有該方法。看來是走了 __callStatic 這個魔術方法。該方法裡只有一行程式碼:

return (new static)->$method(...$parameters);

static 指的是呼叫該靜態方法的類(如果使用的是 UserModel::find(1),則 static 就代表 UserModel 類)。看來是例項化了一個物件,並呼叫了成員方法。

2.分析如何優雅地在中間插一腳

為了能夠在呼叫 find 時候,先走我們的快取,所以我們需要覆蓋 __callStatic 方法,並檢測如果是 find 方法,則優先返回快取中的資料。

另外,為了能夠達到即插即用的效果,我們使用繼承的方式,而是使用了 Trait。核心邏輯如下:

public static function create($data = null){
    if ($data == null){
        return null;
    }
    $instance = new static;
    foreach ($data as $key => $value){
        $instance[$key] = $value;
    }
    return $instance;
}

/**
 * 如果方法是 find($id, $nocache)
 *
 * @param  string  $method
 * @param  array  $parameters
 * @return mixed
 */
public static function __callStatic($method, $parameters)
{
    if ($method == `find`){
        // 從快取中獲取資料
        $obj = static::create(json_decode(Redis::get(static::getCacheKey($parameters[0])), true));
        if (null == $obj){
            $obj = (new static)->$method(...$parameters);
            if (null == $obj){
                return null;
            } else {
                $key = static::getCacheKey($parameters[0]);
                // 設定快取及過期時間
                Redis::set($key, $obj);
                Redis::expire($key, static::$expire_time);
                return $obj;
            }
        } else {
            $obj->exists = true;
            return $obj;
        }
    } else if($method == `findNoCache`){
        $method = `find`;
        return (new static)->$method(...$parameters);
    }

    return (new static)->$method(...$parameters);
}

private static function getCacheKey($id){
    $name = str_replace(`\`, `:`, __CLASS__);
    return "{$name}:{$id}";
}

大體邏輯上面已經介紹過了:覆蓋 __callStatic 方法,判斷如果是呼叫 find ,則走快取(無快取,查詢後需要設定快取)。另新增 findNoCache 方法。

3.細節補充

當修改(或刪除)資料(呼叫 save 方法)時需要刪除已快取的內容。

private static function clearCache($id){
    Redis::del(self::getCacheKey($id));
}

/**
 * when save, should clear cache
 * @param array $options
 */
public function save(array $options = []){
    static::clearCache($this[$this->primaryKey]);
    return parent::save($options);
}

// delete 方法我暫時寫,內容類似 save 方法

如何使用。在需要使用 find 快取的 Model 類裡,加上一行就夠了。

class User extends BaseModel
{
    use MemoryCacheTrait;
}

快去試試吧。

相關文章