在 Laravel 的 Model 层做数据缓存

翻译 Summer ⋅ 于 2周前 ⋅ 最后回复由 半夏 19小时前 ⋅ 1976 阅读 ⋅ 原文地址
这是一篇社区协同翻译的文章,已完成翻译,更多信息请点击 协同翻译介绍

file

您在此之前可能就已经缓存过模型数据,但是我将向您展示一个使用动态记录模型的更精细的Laravel模型缓存技术,这是我一开始在 RailsCasts学习到的技术。

使用模型的唯一缓存键,您可以缓存模型(或关联模型)更新时自动更新(以及缓存失效)的模型上的属性和关联,一个好处是访问缓存的数据比在控制器中缓存的数据更具可复用性,因为它在模型上而不是在单个控制器方法中。

科大大 翻译于 2周前

这是这个技术的要点:

假设你有很多个 Comment 的 Article 模型,给定下面的Laravel blade 模板,你就可以像下面这样访问 /article/:id 路由时得到评论的数量:

<h3>$article->comments->count() {{ str_plural('Comment', $article->comments->count())</h3>

您可以在控制器中缓存评论的计数,但是当您有多个需要缓存的一次性查询和数据时,控制器会变得非常臃肿难看。使用控制器,访问缓存的数据也不是很方便。

科大大 翻译于 2周前

我们可以构建一个模板,它仅在文章更新时访问数据库,并且访问该模型的所有代码都可以获取缓存值:

<h3>$article->cached_comments_count {{ str_plural('Comment', $article->cached_comments_count)</h3>

通过使用模型访问器,我们可以缓存基于最后一次文章更新的评论计数值。

因此,在评论新增或删除时我们该怎么更新文章的 updated_at 列值呢?

先进入 touch 方法看看。

bigqiang 翻译于 2周前

模型的触发

可以通过使用模型的 touch() 方法来更新文章的 updated_at 列值:

$ php artisan tinker

>>> $article = \App\Article::first();
=> App\Article {#746
     id: 1,
     title: "Hello World",
     body: "The Body",
     created_at: "2018-01-11 05:16:51",
     updated_at: "2018-01-11 05:51:07",
   }
>>> $article->updated_at->timestamp
=> 1515649867
>>> $article->touch();
=> true
>>> $article->updated_at->timestamp
=> 1515650910

我们可以用更新的 timestamp 值使缓存失效。不过在新增或删除一个评论时,我们怎么触发修改文章的 updated_at 字段呢?

碰巧 Eloquent 模型中有一个属性就叫 $touches 。下面是我们的评论模型的大概样子:

<?php

namespace App;

use App\Article;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    protected $guarded = [];

    protected $touches = ['article'];

    public function article()
    {
        return $this->belongsTo(Article::class);
    }
}

这里的 $touches 属性是个数组,包含了在评论的创建、保存和删除时会引起“触发”的关联信息。

bigqiang 翻译于 2周前

缓存的属性

我们先回到 $article->cached_comments_count 访问器。该方法的实现可能象 App\Article 模型中的样子:

public function getCachedCommentsCountAttribute()
{
    return Cache::remember($this->cacheKey() . ':comments_count', 15, function () {
        return $this->comments->count();
    });
}

我们使用唯一键值的 cacheKey() 方法缓存模型 15 分钟,然后简单地在闭包方法中返回评论计数值。

注意,我们也可以用 Cache::rememberForever() 方法,靠着缓存机制的垃圾回收策略以删除过期的键值。我设置了一个定时器,以便在每隔 15 分钟的缓存刷新间隔里,缓存可在该时间的多数范围内有最高的命中率。

cacheKey() 方法要用到模型的唯一键值,并且在模型更新时对应缓存失效。下面是我的 cacheKey 实现代码:

public function cacheKey()
{
    return sprintf(
        "%s/%s-%s",
        $this->getTable(),
        $this->getKey(),
        $this->updated_at->timestamp
    );
}

模型的 cacheKey() 方法示例输出结果可能返回下面的字串信息:

articles/1-1515650910

这个键值是由表名、模型id值及当前 updated_at 的 timestamp 值组成。一旦我们触发这个模型,timestamp 值就会更新,并且我们的模型缓存就会相应地失效。

以下是 Article 模型的完整代码:

<?php

namespace App;

use App\Comment;
use Illuminate\Support\Facades\Cache;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public function cacheKey()
    {
        return sprintf(
            "%s/%s-%s",
            $this->getTable(),
            $this->getKey(),
            $this->updated_at->timestamp
        );
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    public function getCachedCommentsCountAttribute()
    {
        return Cache::remember($this->cacheKey() . ':comments_count', 15, function () {
            return $this->comments->count();
        });
    }
}

然后是关联的 Comment 模型:

<?php

namespace App;

use App\Article;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    protected $guarded = [];

    protected $touches = ['article'];

    public function article()
    {
        return $this->belongsTo(Article::class);
    }
}
bigqiang 翻译于 2周前

还有什么?

我已经向你展示了如何缓存一个简单的评论计数值,那所有的评论该怎么缓存呢?

public function getCachedCommentsAttribute()
{
    return Cache::remember($this->cacheKey() . ':comments', 15, function () {
        return $this->comments;
    });
}

你也可以把评论转成一个数组而不用把模型序列化,仅允许前端访问这个简单数组数据:

public function getCachedCommentsAttribute()
{
    return Cache::remember($this->cacheKey() . ':comments', 15, function () {
        return $this->comments->toArray();
    });
}

最后,我在 Article 模型中定义了 cacheKey() 方法,不过你也可以通过一个名字象 ProvidesModelCacheKey 这样的 trait 来定义该方法,这样就可以在多个模型中使用它,或是在一个基类模型中定义该方法,然后在我们其他模型中继承。甚至可以使用一个模型的契约(接口)实现cacheKey() 方法。

我希望你发现这个小技巧对你有用。

bigqiang 翻译于 2周前

查看其他 1 个版本

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

本帖已被设为精华帖!
回复数量: 2
  • ab0029
    2周前

    思路不错,放到Repository里去做会更好,Repository分两个类,CacheRepository和ModelRepository。CacheRepository的不命中从ModelRepository里获取数据,两个类拥有相同的方法或者使用__call方法

  • 半夏 coding
    2周前

    看完之后我的理解是比通常在模型中写缓存方法多一个模型更新和删除时自动更新缓存数据吗?

暂无评论~~
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!
Ctrl+Enter