Caching data in the Model layer of Laravel

Posted by tmann on Thu, 26 Sep 2019 05:17:43 +0200

The article was forwarded from the professional Laravel developer community with the original link: https://learnku.com/laravel/t...

You may have cached model data before, but I'll show you a more sophisticated Lavell model caching technique that uses dynamic recording models, which I started with. RailsCasts Learned technology.

Using the unique cache key for the model, you can cache the attributes and associations on the model that are automatically updated when the model (or the associated model) is updated (and the cache fails). One benefit is that accessing the cached data is more reusable than the data cached in the controller because it is on the model rather than on a single controller methodMedium.

This is the main point of this technology:

Assuming you have many Comment Article models, given the Laravel blade template below, you can access the number of comments you get when routing to /article/:id as follows:

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

You can cache the count of comments in the controller, but when you have multiple one-time queries and data that need to be cached, the controller can become very bloated and ugly.Using the controller, it is not very convenient to access the cached data.

We can build a template that accesses the database only when the article is updated, and all code that accesses the model can get cached values:

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

Using Model Accessor, we can cache the comment count values based on the last article update.

So how do we update the column value of an article when comments are added or deleted?

Look at the touch method first.

Trigger of model

You can update the article's updated_at column value by using the model's 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

We can invalidate the cache with an updated timestamp value.But how do we trigger an update_at field to modify an article when adding or deleting a comment?

It happens that one of the properties in the Eloquent model is called $touches.Here's a general look at our review model:

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

The $touches property here is an array that contains the associated information that triggers a comment when it is created, saved, and deleted.

Cached Properties

Let's go back to the $article->cached_comments_count accessor first.The implementation of this method may look like the App\Article model:

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

We cached the model for 15 minutes using the cacheKey() method with a unique key value, then simply returned the comment count value in the closure method.

Note that we also used the Cache::rememberForever() method, which relies on the garbage collection policy of the cache mechanism to remove expired key values.I set a timer so that every 15-minute cache refresh interval, the cache has the highest hit rate in most areas of the time.

The cacheKey() method uses the unique key value of the model and invalidates the cache when the model is updated.Here is my cacheKey implementation code:

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

The model's cacheKey() method example output may return the following string information:

articles/1-1515650910

This key value consists of the table name, the model id value, and the timestamp value of the current updated_at.Once we trigger this model, the timestamp value will be updated and our model cache will fail accordingly.

The following is the complete code for the Article model:

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

Then there is the associated Comment model:

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

What's next?

I've shown you how to cache a simple comment count, but how do I cache all comments?

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

You can also choose to convert comments to an array instead of a serialization model, allowing only simple array access to the data on the front end:

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

Finally, I defined the cacheKey() method in the Article model, but you might want to define it through a trait named ProvidesModelCacheKey so that you can use it in a composite model or define methods for all model extensions in a basic model.You may even want to use contracts (interfaces) for models that implement the cacheKey() method.

I hope you've found this simple technology very useful!

Topics: PHP Database Laravel