Counting Unique Views in Laravel with Redis

March 8th, 2024

If you want to count unique views for models in your Laravel apps, you might reach for a database table. There's a much easier and faster way though, using Redis.

Prefer to watch? This article is available as a free screencast too, where I'll show you some extra tips, like being able to order by unique views.

If you're familiar with working with Redis in Laravel, feel free to skip this section. For everyone else, let's take a basic look at Redis in Laravel.

So, what's Redis?

"Redis is an open source, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, and sorted sets." - Laravel Documentation

Simply put, it's an in-memory data store where you can insert data, and get data back out. Because your data is stored in-memory, it's fast.

Laravel ships with a Redis connection driver that maps methods to Redis commands. Once you've installed Redis on your local development environment, you'll be able to run commands like this:

Redis::set('name', 'Alex');
Redis::get('name'); // Alex

If you're following along, make sure the code above works — and we're good to go.

Building out this functionality and tying it down to one model would be a shame since we'll probably want to log views for more models in our app in the future.

Create this trait somewhere in your application.

trait LogsViews
{
    public function logView()
    {
        Redis::pfadd(sprintf('%s.%s.views', $this->getTable(), $this->id), [request()->ip()]);
    }

    public function getViewCount()
    {
        return Redis::pfcount(sprintf('%s.%s.views', $this->getTable(), $this->id));
    }
}

Now use that trait on any of the models you need to log views for.

class Article extends Model
{
    use HasFactory;
    use LogsViews;
}

To log a view, we're doing this.

Redis::pfadd(sprintf('%s.%s.views', $this->getTable(), $this->id), [request()->ip()]);

Using sprintf, we build up the key we want to insert into. If we were doing this with an Article with an ID of 1, this would look like this.

articles.1.views

The value we insert here would be the IP address of the user as an example, but you can use any unique (ish) value.

So, once again, what's pfadd?

Rather than store all of these IP addresses in a Redis set, we're using Redis' HyperLogLog implementation instead. HyperLogLog is a probabilistic data structure that estimates the cardinality (the number of elements) of a set. This works out much faster than just counting on a set of data, particularly as more views are logged. The only trade-off is the potential for a very tiny margin of inaccuracy.

So, if we couple this with the getViewCount method in this trait which uses pfcount, we can get the cardinality (count) of the IP addresses we've stored.

return Redis::pfcount(sprintf('%s.%s.views', $this->getTable(), $this->id));

Basically, these two methods allow us to log this data, and get back a unique count of this data.

Here's a quick example of how we'd log a view for an article.

Route::get('/articles/{article}', function (Article $article) {
    $article->logView();

    return view('articles.show', [
        'article' => $article
    ]);
})->name('articles.show');

And of course, wherever you want to output unique views, you can use the getViewCount method.

{{ $article->getViewCount() }}

This seems too good to be true, but using Redis to log unique views instead of a database table frees you from extra overhead, and a ton of additional code.

If you'd like to learn about syncing this data back to your database periodically and being able to order your models by unique views, check out the free screencast which covers this, and a few more details.

Thanks for reading! If you found this article helpful, you might enjoy our practical screencasts too.
Author
Alex Garrett-Smith
Share :

Comments

No comments, yet. Be the first to leave a comment.