Hướng dẫn Laravel Model Caching

2642

Có thể trước đây bạn đã từng cache một số data model trên controller, nhưng giờ tôi sẽ chỉ cho bạn một kỹ thuật cache Laravel model sử dụng model Active Record phức tạp hơn một chút. Đây là kỹ thuật tôi học được từ RailsCasts.

Bằng việc sử dụng cache key riêng biệt lên model, bạn có thể cache các properties và associations tự động update lên model của mình (và cache hết hiệu lực) khi model (hoặc model liên quan) được update. Một lợi ích khác đó là việc truy cập vào data được cache trên model tiện lợi hơn việc cache data trên controller.

Dưới đây là phần lý giải của kỹ thuật này:

Xem như bạn có một model Article mà có chứa nhiều model Comment. Khi được cho template Laravel blade như dưới đây, bạn có thể nhận về số comment như thế trên route /article/:id:

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

Bạn có thể cache phần comment count trong controller, nhưng controller có thể trở nên khá tệ khi bạn có nhiều query và data cần phải cache. Việc sử dụng controller, truy cập vào data được cache cũng không được thuận tiện cho lắm.

Chúng ta có thể xây dựng một template mà chỉ thực hiện việc truy cập database khi article được update, và bất kì code nào có access vào model đều có thể lấy giá trị được cache:

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

Chúng ta sẽ cache lượng comment count dựa theo lần cuối mà article được update.

Vậy bằng cách nào mà chúng ta update cột updated_at của article khi một comment mới được thêm vào hoặc xóa đi?

Sử dụng phương thức touch.

>>> Xem thêm: Laravel, bạn đã viết đúng?

Touching Models

Việc sử dụng method touch() của model, chúng ta có thể update cột updated_at của article:

$ 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 timestamp đã update để invalidate cache, nhưng làm sao để đụng tới trường updated_at của article khi chúng ta thêm hoặc bỏ đi comment?

Việc này cũng xảy ra tương tự khi model Eloquent có một thành phần gọi là $touches. Comment model của chúng tôi được diễn giải 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);
    }
}

$touches là một mảng chứa một liên kết sẽ được “touch” khi một comment được tạo ra, lưu về hoặc xóa đi.

Thuộc tính Cached (cached attribute)

Quay lại phần $article->cached_comments_count. Phần thực hiện có thể trông như thế này trên model AppArticle:

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

Chúng ta đang cache model trong khoảng 15 phút bằng method cacheKey() riêng biệt và quay lại phần đếm comment trong phần closure.

Lưu ý rằng chúng ta có thể sử dụng method  Cache::rememberForever() và dựa vào thùng rác tạm trên bộ nhớ đệm để loại bỏ key cũ. Tôi đã cài đếm giờ để cache sẽ được truy cập mọi lúc, với một cache mới mỗi 15 phút.

method cacheKey() cần phải làm trên model riêng biệt, và vô hiệu cache khi model được update. Dưới đây là phần ứng dụng cacheKey của tôi:

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

Một ouput mẫu của method cacheKey() có thể cho ra kết quả như sau:

articles/1-1515650910

Mấu chốt là tên bảng, model id, và updated_at. Một khi đụng tới model, timestamp sẽ được update, và cache model sẽ được vô hiệu hóa ngay.

Dưới đây là model Article đầy đủ:

<?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à model Comment liên quan:

<?php

namespace App;

use AppArticle;
use IlluminateDatabaseEloquentModel;

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

    protected $touches = ['article'];

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

Tiếp đến là gì?

Tôi đã chỉ ra cách cache một hàm đếm comment đơn giản, nhưng làm thế nào để cache tất cả comment?

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

Bạn cũng có thể chọn chuyển đổi comment thành array:

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

Cuối cùng, tôi define method cacheKey() vào Article model, nhưng bạn nên define method này qua ProvidesModelCacheKey để có thể dùng trên nhiều model hoặc define method trên model gốc mà tất cả các model khác kế thừa. Thậm chí bạn còn muốn dùng contract (interface) cho các model để áp dụng method cacheKey().

Tham khảo thêm các vị trí tuyển dụng lập trình Laravel lương cao cho bạn.

TopDev via Laravel News