Вы, вероятно, кэшировали некоторые данные модели в контроллере раньше, но я собираюсь показать вам технику кэширования моделей Laravel, которая немного более детализирована с использованием моделей Active Record. Это техника, о которой я впервые узнал на RailsCasts.
Используя уникальный ключ кэша в модели, можно кэшировать свойства и связи в моделях, которые автоматически обновляются (и кэш становится недействительным) при обновлении модели (или связанной модели). Дополнительным преимуществом является то, что доступ к кэшированным данным является более переносимым, чем кэширование данных в контроллере, поскольку они находятся в модели, а не в одном методе контроллера.
Вот суть методики:
Допустим, у вас есть Article
модель, которая имеет много Comment
моделей. Учитывая следующий шаблон blade Laravel, вы можете получить количество комментариев следующим образом в своем /article/:id
маршруте:
<h3>{{ str_plural('Comment', $article->comments->count()) }}</h3>
Вы можете кэшировать количество комментариев в контроллере, но контроллер может стать довольно уродливым, когда у вас есть несколько одноразовых запросов и данных, которые вам нужно кэшировать. Использование контроллера для доступа к кэшированным данным также не очень портативно.
Мы можем создать шаблон, который будет попадать в базу данных только при обновлении статьи, и любой код, имеющий доступ к модели, может получить кэшированное значение:
<h3>{{ str_plural('Comment', $article->cached_comments_count) }}</h3>
Используя метод доступа к модели, мы будем кэшировать количество комментариев на основе последнего обновления статьи.
Итак, как мы обновляем колонку статьи updated_at
при добавлении или удалении нового комментария?
Введите сенсорный метод.
Касание моделей
Используя метод моделиtouch()
, мы можем обновить столбец статьи:
$ 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
Мы можем использовать обновленную метку времени, чтобы сделать кэш недействительным, но как мы можем повлиять на updated_at
поле статьиupdated_at
, когда мы добавляем или удаляем комментарий?
Так уж вышло, что модели Eloquent имеют свойство под названием $touches
. Вот как может выглядеть наша модель комментариев:
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()
метод и просто возвращая количество комментариев внутри замыкания.
Обратите внимание, что мы также можем использовать этот Cache::rememberForever()
метод и полагаться на сборку мусора нашим механизмом кэширования для удаления устаревших ключей. Я установил таймер таким образом, чтобы кэш попадал большую часть времени, а новый кэш появлялся каждые пятнадцать минут.
Метод cacheKey()
должен сделать модель уникальной и сделать кэш недействительным при обновлении модели. Вот моя cacheKey
реализация:Пример выходных данных для метода модели может возвращать следующее строковое представление:
public function cacheKey()
{
return sprintf(
"%s/%s-%s",
$this->getTable(),
$this->getKey(),
$this->updated_at->timestamp
);
}
articles/1-1515650910
Ключ — это имя таблицы, идентификатор модели cacheKey()
и текущая updated_at
метка времени. Как только мы коснемся модели, метка времени будет обновлена, и наш кэш модели будет соответствующим образом признан недействительным.
Article
Вот модель, если она полная:И связанная Comment
модель:
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();
});
}
}
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;
});
}
Вы также можете преобразовать комментарии в массив вместо сериализации моделей, чтобы разрешить только простой доступ к массиву данных на фронтенде:Наконец, я определил cacheKey()
метод в модели, но вы захотите определить этот метод с помощью типажа, называемого чем-то вроде ProvidesModelCacheKey
того, что вы можете использовать в нескольких моделях или определить метод в Article
базовой модели,
public function getCachedCommentsAttribute()
{
return Cache::remember($this->cacheKey() . ':comments', 15, function () {
return $this->comments->toArray();
});
}
которую расширяют все наши модели. Возможно, вы даже захотите использовать контракт (интерфейс) для моделей, реализующих cacheKey()
метод.
Надеюсь, эта простая техника оказалась для вас полезной!