Problem

Trước đây, có thể bạn thường lưu trữ dữ liệu của Model trong Controller sau đó đổ ra view, nhưng hôm nay mình sẽ giới thiệu một kỹ thuật mới có thể thay thế việc bạn vẫn làm theo thông lệ đó là Model Caching trong Laravel, sử dụng các mô hình Active Record nhỏ hơn. Đây là một kỹ thuật mà đã xuất hiện và được ứng dụng trong RailsCast.
Sử dụng một cache key duy nhất trong Model, bạn có thể lưu các property và các association của Model vào Cache và nó sẽ cập nhật tự động trong cache khi các property và các association đó trong Model được update. Một lợi ích phụ là việc truy cập vào dữ liệu được lưu trữ trong cache dễ dàng và tốn ít thời gian hơn là dữ liệu bộ nhớ đệm trong bộ điều khiển bởi vì nó nằm trong Model thay vì trong một action duy nhất trong Controller.
Hãy cùng tìm hiểu ví dụ dưới đây để hiểu rõ hơn nhé:
Giả sử bạn có Article model, mỗi model có nhiều Comment models, với blade template, nếu muốn lấy số lượng comment của một article, bạn sẽ phải làm như sau:

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

Với cách này, bạn sẽ lấy comment vào trong Controller, nhưng với cách này, sẽ khá tốn thời gian và công sức vì có thể để lấy ra số lượng comment đó, bạn có thể cần dùng nhiều câu query. Như vậy, mỗi lần lấy ra số lượng comment (kể cả khi không có gì thay đổi) bạn cần phải lặp đi lặp lại đoạn query đó, đúng không nào?
Thay vì cách đó, chúng ta có thể dựng một template khác để lấy số lượng comment của article, và nó sẽ chỉ query vào database khi Article đc update:

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

Bằng cách sử dụng Model Accessor, chúng ta sẽ lấy được số lượng comment dựa vào lần gần đây nhất Article đó được update. Đến đây bạn có thể đang băn khoăn rằng chúng ta sẽ update trường updated_at như thế nào khi mà một comment được thêm vào hoặc xóa đi.
Sau đây, mình sẽ giới thiệu với bạn Touch method.

Touching Models

Sử dụng method touch của model, chúng ta có thể update trường updated_at của article đó. Mình sẽ thử bằng tinker cho nhanh nhé:

$ php artisan tinker

>>> $article = AppArticle::first();
=> AppArticle {#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

Chúng ta có thể sử dụng mốc thời gian được update để vô hiệu hóa cache vì dữ liệu đó đã bị cũ. Với cách này, Laravel đã cung cấp cho chúng ta một property $$ouches. Chúng ta sẽ có model Comment như sau:

<?php

namespace App;

use AppArticle;
use IlluminateDatabaseEloquentModel;

class Comment extends Model
{
    protected $guarded = ;

    protected $touches = ['article'];

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

$$ouches property là một array chứa các associations mà nó sẽ được cập nhật khi một comment được tạo ra, hoặc xóa đi.

The cached Attribute

Giờ chúng ta hãy quay lại đoạn code mà chúng ta lấy ra các số lượng comment khi dùng model accessor: $$rticle->cached_comments_count Để sử dụng được đoạn này, trong model Article của bạn cần định nghĩa như sau:

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

Chúng ta đang lấy dữ liệu của model bằng cách sử dụng hàm cacheKey() và dễ dàng trả ra số lượng comment bên trong Closure.
Chú ý là bạn có thể sử dụng Cache::rememberForever() và dựa vào bộ nhớ đệm để xóa đi các dữ liệu cũ. Mình đã đặt một bộ đếm thời gian để bộ nhớ cache sẽ được nhấn hầu hết thời gian, với một “refresh cache” mỗi mười lăm phút. Hàm cacheKey() cần để làm cho Model unique và làm mất hiệu lực bộ nhớ cache khi Model được cập nhật. Dưới đây là đoạn mình triển khai cacheKey()

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

Output của đoạn code này sẽ là một string, với format như sau:

articles/1-1515650910

Key đó chính là sự kết hợp của tên table, model id và mốc thời gian updated_at. Một khi chúng ta truy cập vào model, trường “updated_at” sẽ được update và dữ liệu trong cache model cũ sẽ bị vô hiệu hóa ngay lập tức. Và đây là đầy đủ Article model mà bạn có thể tham khảo:

<?php

namespace App;

use AppComment;
use IlluminateSupportFacadesCache;
use IlluminateDatabaseEloquentModel;

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();
        });
    }
}

và Comment model với relationship:

<?php

namespace App;

use AppArticle;
use IlluminateDatabaseEloquentModel;

class Comment extends Model
{
    protected $guarded = ;

    protected $touches = ['article'];

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

Summary

Trên đây là ví dụ đơn giản về cách lưu số lượng comment vào trong cache thay vì mỗi lần chúng ta sẽ chạy những câu query phức tạp, ta có thể chỉ định nó chỉ chạy khi model được update. Hi vọng bài viết có thể cho bạn thêm 1 sự lựa chọn mỗi khi phải lấy dữ liệu từ database.

Reference:

https://laravel-news.com/laravel-model-caching
https://laravel.com/docs/5.5/cache