Implementing Follow/Unfollow Functionality with Redis and MySQL in PHP
This article explains how to design and implement a follow/unfollow feature using a MySQL table for persistence and Redis ordered‑set commands (zAdd, zRem, zRange, zCard, zScore) in PHP, covering database schema, code examples, pagination, and status detection.
When building an API that requires a follow/unfollow feature, using MySQL alone can be inefficient, so the author proposes a hybrid solution that stores relationship data in MySQL and caches it in Redis for fast access.
Database table
The MySQL table shc_sns stores the follower ID, the followed ID, the timestamp, and a follow state (1 for one‑way, 2 for mutual). The table definition is:
DROP TABLE IF EXISTS `shc_sns`;
CREATE TABLE `shc_sns` (
`sns_id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id值',
`sns_frommid` int(11) NOT NULL COMMENT '会员id',
`sns_tomid` int(11) NOT NULL COMMENT '朋友id',
`sns_addtime` int(11) NOT NULL COMMENT '添加时间',
`sns_followstate` tinyint(1) NOT NULL DEFAULT '1' COMMENT '关注状态 1为单方关注 2为双方关注',
PRIMARY KEY (`sns_id`),
KEY `FROMMID` (`sns_frommid`) USING BTREE,
KEY `TOMID` (`sns_tomid`,`sns_frommid`) USING BTREE,
KEY `FRIEND_IDS` (`sns_tomid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=196 DEFAULT CHARSET=utf8 COMMENT='好友数据表';Using Redis to implement follow
The author uses Redis sorted sets (zAdd) to store follow and fan lists, using the current timestamp as the score to enable pagination.
public function addFollowsFansById($data) {
if (empty($data)) return false;
$data['sns_addtime'] = time();
if ($data['sns_followstate'] == 1) { // single‑side follow
$result = $this->addFollowFan($data);
if ($result) {
$this->cache->redis->zAdd('shc_follow:' . $data['sns_frommid'], time(), $data['sns_tomid']);
$this->cache->redis->zAdd('shc_fans:' . $data['sns_tomid'], time(), $data['sns_frommid']);
}
return true;
} elseif ($data['sns_followstate'] == 2) { // mutual follow
$this->db->trans_begin();
$this->addFollowFan($data);
$this->editFollowFans([
'sns_frommid' => $data['sns_tomid'],
'sns_tomid' => $data['sns_frommid']
], ['sns_followstate' => 2]);
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
return false;
} else {
$this->db->trans_commit();
$this->cache->redis->zAdd('shc_follow:' . $data['sns_frommid'], time(), $data['sns_tomid']);
$this->cache->redis->zAdd('shc_fans:' . $data['sns_tomid'], time(), $data['sns_frommid']);
return true;
}
} else {
return false;
}
}Unfollow implementation
Unfollow removes entries from both sorted sets using zRem, handling both single‑side and mutual cases with database transactions.
public function deleteFollowFansById($user_id, $follow_id, $state) {
if ($state == 1) {
$result = $this->deleteFollowFans(['sns_frommid' => $user_id, 'sns_tomid' => $follow_id]);
if ($result) {
$this->cache->redis->zRem('shc_fans:' . $follow_id, $user_id);
}
return true;
} elseif ($state == 2) {
$this->db->trans_begin();
$this->deleteFollowFans(['sns_frommid' => $user_id, 'sns_tomid' => $follow_id]);
$this->editFollowFans(['sns_frommid' => $follow_id, 'sns_tomid' => $user_id], ['sns_followstate' => 1]);
if ($this->db->trans_status() === false) {
$this->db->trans_rollback();
return false;
} else {
$this->cache->redis->zRem('shc_follow:' . $user_id, $follow_id);
$this->cache->redis->zRem('shc_fans:' . $follow_id, $user_id);
$this->db->trans_commit();
return true;
}
} else {
return false;
}
}Retrieving follow/fan lists
Lists are fetched with zRevRange (or zRange) using pagination parameters calculated from the requested page and page size.
public function getFollowsListById($user_id, $limit = array()) {
$follow_list = $this->cache->redis->zRevRange('shc_follow:' . $user_id,
$limit['current_page'],
$limit['current_page'] + $limit['page_size'] - 1);
if ($follow_list) {
$member_list = $this->Member_Model->getMemberListByIdS($follow_list, 'user_id,user_nickname,user_portrait');
return $member_list ? $member_list : $this->getFollowsListByDb(['a.sns_frommid' => $user_id], 'b.user_id,b.user_nickname,b.user_portrait', $limit);
}
}Counting follows/fans
The Redis zCard command returns the size of the ordered set, giving the number of follows or fans.
Follow status detection
By checking the existence of a member in the sorted sets with zScore, the system can determine whether the relationship is one‑way, the opposite way, or mutual.
Conclusion
Using Redis for follow data offloads read/write pressure from MySQL, enables fast pagination, and simplifies status checks, while the MySQL table remains the source of truth for persistence.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Laravel Tech Community
Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
