在Laravel的Model层做数据缓存的实现


Posted in PHP onSeptember 26, 2019

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

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

这是这个技术的要点:

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

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

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

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

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

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

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

先进入 touch 方法看看。

模型的触发

可以通过使用模型的 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 属性是个数组,包含了在评论的创建、保存和删除时会引起“触发”的关联信息。

缓存的属性

我们先回到 $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);
  }
}

接下来做什么?

我已经向你展示了如何缓存一个简单的评论计数,但是如何缓存所有的评论呢?

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() 方法的模型使用使用契约(接口)。

我希望你已经发现这个简单的技术是十分有用的!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
PHP的FTP学习(一)[转自奥索]
Oct 09 PHP
PHP 类型转换函数intval
Jun 20 PHP
php 论坛采集程序 模拟登陆,抓取页面 实现代码
Jul 09 PHP
php在window iis的莫名问题的测试方法
May 14 PHP
解析php中两种缩放图片的函数,为图片添加水印
Jun 14 PHP
PHP实现今天是星期几的几种写法
Sep 26 PHP
php计算整个目录大小的方法
Jun 19 PHP
详解PHP编码转换函数应用技巧
Oct 22 PHP
phpstudy默认不支持64位php的解决方法
Feb 20 PHP
Thinkphp事务操作实例(推荐)
Apr 01 PHP
Joomla框架实现字符串截取的方法示例
Jul 18 PHP
thinkphp5 加载静态资源路径与常量的方法
Dec 24 PHP
PHP的静态方法与普通方法用法实例分析
Sep 26 #PHP
tp5框架无刷新分页实现方法分析
Sep 26 #PHP
php判断目录存在的简单方法
Sep 26 #PHP
php 策略模式原理与应用深入理解
Sep 25 #PHP
php策略模式简单示例分析【区别于工厂模式】
Sep 25 #PHP
PHP 观察者模式深入理解与应用分析
Sep 25 #PHP
php模式设计之观察者模式应用实例分析
Sep 25 #PHP
You might like
php5数字型字符串加解密代码
2008/04/24 PHP
Search File Contents PHP 搜索目录文本内容的代码
2010/02/21 PHP
PHP命名空间(Namespace)的使用详解
2013/05/04 PHP
PHP开发框架kohana中处理ajax请求的例子
2014/07/14 PHP
php数组键值用法实例分析
2015/02/27 PHP
关于php微信订阅号开发之token验证后自动发送消息给订阅号但是没有消息返回的问题
2015/12/21 PHP
PHP邮箱验证示例教程
2016/06/01 PHP
PHP的AES加密算法完整实例
2016/07/20 PHP
php变量与JS变量实现不通过跳转直接交互的方法
2017/08/25 PHP
jquery显示和隐藏div特效实例
2013/02/27 Javascript
Jquery实现图片放大镜效果的思路及代码(自写)
2013/10/18 Javascript
javascript中局部变量和全局变量的区别详解
2015/02/27 Javascript
jQuery子窗体取得父窗体元素的方法
2015/05/11 Javascript
JavaScript中的lastIndexOf()方法使用详解
2015/06/06 Javascript
基于JavaScript实现鼠标向下滑动加载div的代码
2016/08/31 Javascript
在js中做数字字符串补0(js补零)
2017/03/25 Javascript
微信小程序结合mock.js实现后台模拟及调试
2019/03/28 Javascript
vue 使用 v-model 双向绑定父子组件的值遇见的问题及解决方案
2021/03/01 Vue.js
[01:32]DOTA2上海特锦赛现场采访:最想COS的英雄
2016/03/25 DOTA
[47:39]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 LGD vs OPTIC
2018/03/31 DOTA
[01:18]PWL开团时刻DAY10——一拳超人
2020/11/11 DOTA
50行代码实现贪吃蛇(具体思路及代码)
2013/04/27 Python
python爬虫入门教程--HTML文本的解析库BeautifulSoup(四)
2017/05/25 Python
解决python 未发现数据源名称并且未指定默认驱动程序的问题
2018/12/07 Python
Python面向对象之类和对象属性的增删改查操作示例
2018/12/14 Python
Python切片操作去除字符串首尾的空格
2019/04/22 Python
python 公共方法汇总解析
2019/09/16 Python
计算pytorch标准化(Normalize)所需要数据集的均值和方差实例
2020/01/15 Python
Boden美国官网:英伦原创时装品牌
2017/07/03 全球购物
澳大利亚在线家具店:Luxo Living
2019/03/24 全球购物
俄罗斯最大的在线手表商店:Bestwatch.ru
2020/01/11 全球购物
社区工作者感言
2014/03/02 职场文书
尼克胡哲观后感
2015/06/08 职场文书
小学生作文写作技巧100例,非常实用!
2019/07/08 职场文书
pytorch model.cuda()花费时间很长的解决
2021/06/01 Python
Linux中文件的基本属性介绍
2022/06/01 Servers