快取 Laravel 模型的小問題

moodrain發表於2022-03-09

快取巢狀關聯

最近在公司專案中想要快取 Laravel 的模型,因為載入了很多巢狀關聯的模型,使用 json 系列函式並不能輕鬆地將 json 重新轉回模型物件,所以使用了 serialize 系列函式

$json = $redis->get($cacheKey);
$user = $json ? new User(json_decode($json, true)) : User::find($id);
// 模型沒有關聯時,可以直接從 json 取得資料填充新模型
$ser = $redis->get($cacheKey);
$user = $ser ? unserialize($ser) : User::with('a.b')->find($id);

反序列化不呼叫構造方法

起初並沒有什麼問題,從快取中取出反序列化後就能像一般的模型正常使用,直到我需要在模型的建構函式中初始化一些屬性。因為 unserialize 並不會呼叫構造方法,所以在訪問這些屬性時是並未初始化的空值。在反序列化時觸發的 __wakeup 魔術方法中也要做屬性的初始化操作,完成後問題就解決了

public function __construct(array $attributes = [])
{
    parent::__construct($attributes);
    $this->srv = new UserService();
}
public function __wakeup()
{
    parent::__wakeup();
    $this->srv = new UserService();
}

全域性靜態初始化方法 booted

我之後注意到 Laravel 提供了一個靜態的全域性初始化方法可供重寫 booted,如果需要初始化的屬性可以是靜態時,這樣做或許更優雅吧

protected static function booted()
{
    self::$srv = new UserService();
}

如果 IDE 沒有方法提示,可以加註釋

/* @var UserService */
static $srv;

呼叫 refresh 的問題

在拿到快取的模型後,如果需要重新從資料庫讀取資料,呼叫 refresh 只會讀取模型本身的關聯,不會讀取巢狀的關聯。需要重新 load 一遍關聯,或者在對應模型的 with 陣列加入每次需要帶上的關聯

$user->refresh();
$user->load('a.b');

好像有人也發現了這個問題,提了 issue Eloquent - nested relations lost when calling refresh on model,Dries Vints(Laravel 專案維護者)回覆是“這似乎是一個問題而不是 bug,建議到社群討論”。我想了下,覺得他是對的,因為 with 上面附帶的關聯時作用於那次查詢的,跟查詢出來的模型沒有關係。不看查詢只看模型的話,要重新載入關聯,那麼只能自己 load 所需的關聯,或者在模型的 with 屬性帶上關聯


備註
  • Laravel 模型快取框架有很多,站裡也有教程。不過公司用的是原生 PHP,只是我為了方便單獨引入 Laravel 的 Eloquent 模型,所以那些快取框架無法使用
  • 我對 Laravel 模型用法的理解可能有偏差,如果有錯誤請指出,有更好的方法請賜教
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章