LoginSignup
9
7

More than 3 years have passed since last update.

【Laravel】Redisを使った閲覧数のランキング機能

Posted at

キャッシュの管理にRedisを使って、ランキング機能をつくった際のメモです。


Laravel 5.8 Redis

環境は以下の通りです。
- laravel 5.8
- php 7.2
- predis 1.1

Redisのインストール

Redisを使うためにRedisのPHP拡張をインストールします。Laravel 5.8 Redis
ここではLaravelは5.8であるためpredisを使用しますが、'6.0'ではpredisは非推奨となっています。Laravel 6.x Redis
なので6.0の場合はphpredisを使用しましょう。(本記事ではphpredisは扱いません)

composerでpredisのインストール

predis/predisパッケージをcomposerでインストールします。

$ composer require predis/predis

Redisの設定

データベースの設定ファイルを編集します。自分の場合、redisをdockerコンテナで運用していたためホスト名はコンテナ名となっています。

database.php
'redis' => [

    'client' => env('REDIS_CLIENT', 'predis'),

    'default' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', 'redis'),
        'password' => env('REDIS_PASSWORD', null),
        'port' => env('REDIS_PORT', 6379),
        'database' => env('REDIS_DB', 0),
    ],
]
.env
CACHE_DRIVER=redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

動作確認

Tinkerを使って動作確認をしてみます。

$ php artisan tinker
Psy Shell v0.9.12 (PHP 7.3.14 — cli) by Justin Hileman
>>> use Redis
>>> Redis::set('test', 'TEST');
=> Predis\Response\Status {#3050}
>>> Redis::get('test')
=> "TEST"

設計

機能を大きく二つに分けると

キャッシュへの記事の閲覧数の保存
・keyに記事ごとのid、valueに閲覧数を保存
 記事の閲覧があった場合
 ->既にkeyの保存があれば、閲覧数をインクリメント(valueを+1)。
 ->まだkeyの保存がなければ、新しくkeyを作成し、value=1で保存。

キャッシュからランキングの結果を取得し、配列で返す
・すべてのkeyを取得し、valueの降順にソートした配列を返す。

RankingService

ランキング機能はRankingServiceクラスとし、ライブラリとして切り出します。

app\Libraries\RankingService
<?php
namespace app\Libraries;

use Illuminate\Http\Request;
use Redis;

class RankingService
{
    //keyの保存、閲覧数のインクリメント
    public function incrementViewRanking($id)
    {
        $key = "ranking-"."id:".$id;
        $value = Redis::get($key);

        if(empty($value)){
            Redis::set($key, "1");
            Redis::expire($key, 3600*24); 
        } else {
            Redis::set($key, $value + 1);
        }
    }

    //ランキング結果を配列で取得
    public function getRankingAll()
    {
        $keys = Redis::keys('ranking-*');
        $results = Array();

        if(empty($keys) != true){
            for($i = 0; $i < sizeof($keys); $i++){
                $id = explode(':', $keys[$i])[1];
                $point = Redis::get('ranking-id:'. $id);
                $results[$id] = $point;
            }
            arsort($results, SORT_NUMERIC);
        }
        return $results;
    }
}

keyの保存、閲覧数のインクリメント

public function incrementViewRanking($id)
{
    $key = "ranking-"."id:".$id;
    $value = Redis::get($key);

    if(empty($value)){
        Redis::set($key, "1");
        Redis::expire($key, 3600*24); 
    } else {
        Redis::set($key, $value + 1);
    }
}

incrementViewRankingでは閲覧のあった記事のidを引数として受け取り閲覧数の管理を行います。
一つずつ見ていきます。

$key = "ranking-"."id:".$id;
$value = Redis::get($key);

keyからvalueの取得をします。
RedisではRedis::get()でvalueの取得ができます。

if(empty($value)){
    Redis::set($key, "1");
    Redis::expire($key, 3600*24); 
} else {
    Redis::set($key, $value + 1);
}

keyがあればvalueをインクリメントします。
なければ記事のidをranking-id:$idとしてキーを保存します。
Redis::expire()でキャッシュする時間を指定します。今回は3600秒×24で24時間に指定します。

ランキング結果の取得

public function getRankingAll()
{
    $keys = Redis::keys('ranking-*');
    $results = Array();

    if(!empty($keys)){
        for($i = 0; $i < sizeof($keys); $i++){
            $id = explode(':', $keys[$i])[1];
            $point = Redis::get('ranking-id:'. $id);
            $results[$id] = $point;
        }
        arsort($results, SORT_NUMERIC);
    }
    return $results;
}

getRankingAllではidと閲覧数をランキングの順で配列に格納します。
保存したすべてのkeyから、arsort()によってvalueの降順にソートしています。

Redis::keys('ranking-*')はkeyの取得です。*でワイルドカードの指定ができます。

ここは正直ごちゃごちゃしてるので改善の余地がありそう。

各コントローラ内での使用

閲覧数のインクリメント

記事の表示があるたびにインクリメントを行うので、incrementViewRanking()はコントローラの表示メソッド内で使います。

    public function show(Article $article)
    {
        $ranking = new RankingService;
        $ranking->incrementViewRanking($article->id);  //インクリメント

        $movie->getArticle($article->id);  //記事の取得

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

ランキングの取得

    public function ranking(Article $article)
    {
        $ranking = new RankingService;
        $results = $ranking->getRankingAll();
        $article_ranking = $article->getArticleRanking($results);

        return view('article.ranking', [
            'article_ranking' => $article_ranking
        ]);
    }

getRankingAll()で取得したランキングの配列から記事を取得します。

    public function getArticleRanking(Array $results)
    {
        $article_ids = array_keys($results);
        $ids_order = implode(',', $article_ids);
        $article_ranking = $this->whereIn('id', $article_ids)
                                ->orderByRaw(DB::raw("FIELD(id, $ids_order)"))
                                ->paginate(10);
        return $article_ranking;
    }

配列の順序のまま記事を取得したいので、こんな風に生のクエリを書くことになりました。普通にwhereInだけで取得すると自動的にLaravelがid順に並べ替えてしまうのです。
ごちゃごちゃしてますね。

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7