缓存 Eloquent 关联查询结果

11

我该如何缓存这个 Eloquent 查询:

dd($user->roles);
因为上面的代码会在某种程度上触发 $user->roles() 查询,我猜测。
我已经尝试过这个方法:
    public function roles() {
        return \Cache::remember('user_' . $this->id . '_roles', 10, function() {
            return $this->hasMany('App\Role');
        });
    }

但是它不起作用,因为它必须返回一个数组,而不是Eloquent查询。

有什么建议吗?


你尝试过 https://github.com/GeneaLabs/laravel-model-caching 吗?它可能可以满足你的需求。完全透明:这是我编写的一个包。 - mike.bronner
3个回答

31

这里是一个现代 Laravel 的更新方法:

// Define your relation
public function bookmarks(): HasMany
{
    return $this->hasMany(Bookmark::class);
}

// Define the cache key (as its used in multiple places)
protected function getBookmarksCacheKey(): string
{
    return sprintf('user-%d-bookmarks', $this->id);
}

// Provide a cache clearing mechanism
public function clearBookmarksCache(): bool
{
    return Cache::forget($this->getBookmarksCacheKey());
}

// Override the relation property getter
// It will return the cached collection when it exists, otherwise getting a fresh one from the database
// It then populates the relation with that collection for use elsewhere
public function getBookmarksAttribute(): Collection
{
    // If the relation is already loaded and set to the current instance of model, return it
    if ($this->relationLoaded('bookmarks')) {
        return $this->getRelationValue('bookmarks');
    }

    // Get the relation from the cache, or load it from the datasource and set to the cache
    $bookmarks = Cache::rememberForever($this->getBookmarksCacheKey(), function () {
        return $this->getRelationValue('bookmarks');
    });

    // Set the relation to the current instance of model
    $this->setRelation('bookmarks', $bookmarks);

    return $bookmarks;
}

这是最完整的答案,做得很好。然而,额外的 !$this->relationLoaded 检查是多余的,因为在给 $bookmarks 变量赋值之前已经检查过了。 - Gytis Tamulynas
这正是我在寻找的…谢谢! - JasonJensenDev
谢谢!然而,至少在 Laravel 9 中,不需要 if ($this->relationLoaded('bookmarks')) { 块,因为 getRelationValue 方法会自动检查。 - Neo
这是一个很棒的解决方案,通过修改(移除relationLoaded检查)并在缓存中添加TTL,特别适用于书签等场景,同时也可以让清理系统有机会在你停止使用或重命名时进行整理。 - Elliot
这是一个非常好而完整的答案,但在我看来,clearBookmarksCache() 方法的一个不错的补充是加入以下代码:$this->unsetRelation('bookmarks');否则,之前检索到的值仍然可以通过 $this->bookmarks 访问,并且不会从数据库重新获取。这可能会导致应用程序混乱。 - crocodile2u

6

无法将关系存储在缓存中。您需要缓存从数据库检索到的实际数据。因此,您将拥有类似以下内容的内容:

public function roles()
{
    return \Cache::remember('user_' . $this->id . '_roles', 10, function()
    {
        return $this->hasMany('App\Role')->get()->toArray();
    });
}

现在你需要将其视为方法来访问,而不是属性,因为它不再返回关系(Eloquent会抛出异常):

$user->roles();

现在您应该得到了想要的数组。

1
但这是正确的方式吗?我猜“hasMany”关系总是必须返回“hasMany”属性?... - FooBar
如果您想让关系的缓存结果无缝获取,这很好。roles方法不需要返回关系对象。但是,如果您想在某些地方使用关系而不进行缓存,则可以拥有一个返回常规关系的roles方法,以及另一个例如cachedRoles的方法,该方法返回缓存数据。这应该使区别更加明显。 - Bogdan
当我尝试使用这段代码时,出现了错误。我得到的错误信息是 在数组上调用成员函数addEagerConstraints() - Jaylen

2
如果您想将用户及其角色缓存起来,可以使用以下方式:
$user = User::find(1);
$user->load('roles');
Cache::put('users_'.$user->id, $user, 10);

我不知道为什么,但你需要在这里使用 load 而不是 with。如果你使用了 with,你会得到一个错误,提示你无法缓存 PDO 实例。

1
它之所以这样工作,是因为您现在正在进行“惰性渴望加载”。您正在加载已获取的Eloquent模型的相关模型。 - Doom5
你如何将它转换回具有角色关系的用户模型? - Matrix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接