0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【CakePHP】CounterCacheでユーザーのフォロー数とフォロワー数をカウントする

Posted at

CounterCacheとは

例えば、記事に紐づくコメント数や、いいね数などhasManyで紐づくデータの数を記事のリストと同時に表示したいはずです。

そのようなときはSELECT COUNT(*)で取得する方法が考えられます。
しかし、記事データを取得する際に毎回SELECT COUNT(*)クエリが発行されるのははっきりいって地獄ですよね:confounded:
しかも、いいね数順にソートしたいときにもいいね数にインデックスを貼ることができないのでさらにパフォーマンスが悪化することが考えられますね...

そのような場合、**記事テーブルにコメント数いいね数カラムをもたせる(キャッシュさせる)**という方法がよく取られます。
あえて正規化を崩すような設計ですが、わざわざ結合や集計をすることなく取得することができ、さらにインデックスを貼ることもできるのでパフォーマンスの向上が見込まれます:laughing:

記事テーブルにカウント数をもたせる際のデメリット

パフォーマンスは向上しましたが、正規化を崩しているため、当然そのためのデメリットが存在します。
データが追加、削除されるたびに確実にカウント数が更新されるようにして置かなければなりません。
自分でそれを実装するときには処理の漏れがないように...トランザクションは...とかとか面倒なことを考えなれけばいけません:weary:...がでもCakePHPなら大丈夫、CounterCacheという便利なものがあります。:joy:

実際にやってみる

それでは記事のタイトルどおり、ユーザーに紐づくフォロー数とフォロワー数をカウントしてみます。
使用するテーブルはそれぞれこんな感じです。

UsersTable

列名 データ 長さ
ID INT 11
username VARCHAR 20
password VARCHAR 255
follow_count INT 11
follower_count INT 11

follow_countfollower_countが今回のキモですね。
CounterCacheにより更新されるフィールドはINT型である必要があります。

FollowsTable

列名 データ 長さ
user_id INT 11
follow_id INT 11

user_idがフォローしたユーザーのIDでfollow_idがフォローされたユーザーのIDとなります。

例えば、次のようなデータがあった場合、

user_id follow_id
1 2
2 1
3 1

ユーザー1はユーザー2のフォロワーという関係になり、また互いに相互フォローとなります。
この時、ユーザー1のUsersTablefollow_countが1、follower_countが2となりますね。

FollowsTable

さっそくFollowersTableを実装します。
bin/cake bake model Followsで生成したときはおそらくこんな感じのテーブルになっているはずです。

FollowsTable.php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\TableRegistry;

class FollowsTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('follows');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER',
        ]);
    }
    
    // Validationは省略
}

CouterCacheを利用するためには、更新するフィールドとアソシエーリョンを構築する必要があります。
すでにUsersTableとのアソシエーリョンは設定されていますが、このままだとフォロワー数をカウントすることができません。UsersTableとのリレーションを別名で追加します。

class FollowsTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('follows');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');

        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER',
        ]);
+
+       $this->belongsTo('FollowUsers', [
+            'className' => 'Users',
+            'foreignKey' => 'follow_id',
+            'joinType' => 'INNER',
+        ]);
    }
}

同一テーブルに対して複数のアソシエーリョンを構築するときには、別の名前で登録してclassNameでアソシエーリョン先のテーブルを指定します。

CounterCacheビヘイビアーを有効にする

アソシエーリョンが完了したところで、CounterCacheを有効にしましょう!
CounterCacheはビヘイビアーとして定義されています。

class FollowsTable extends Table
{
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('follows');
        $this->setDisplayField('id');
        $this->setPrimaryKey('id');
+
+       $this->addBehavior('CounterCache', [
+            'Users' => ['follow_count'],
+            'FollowUsers' => ['follower_count']
+        ]);

        $this->belongsTo('Users', [
            'foreignKey' => 'user_id',
            'joinType' => 'INNER',
        ]);

       $this->belongsTo('FollowUsers', [
            'className' => 'Users',
            'foreignKey' => 'follow_id',
            'joinType' => 'INNER',
        ]);
    }
}

これだけですべての設定は完了です:wink:

あとは。エンティティーが保存または削除されるたびにカウンターが更新されます。
ただし、updateAll()deleteAll() を使用するか、作成した SQL を実行するとカウンターは更新されないことに注意してください:confounded:

0
2
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
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?