Skip to content

Instantly share code, notes, and snippets.

@MichelBartz
Created January 30, 2011 21:07
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save MichelBartz/803251 to your computer and use it in GitHub Desktop.
Save MichelBartz/803251 to your computer and use it in GitHub Desktop.
Nested Comments using Redis. Rely on the Owlient Redis extension
<?php
/**
* A Nested Comments manager using Redis only
* @author Michel Bartz <michel.bartz@manwin.com>
* @date 01/28/2011
*/
class Comments
{
private $_redis;
public function __construct() {
$this->_redis = new Redis();
$this->_redis->connect("localhost", 6379);
}
/**
* Save a new comment
* @param int $itemId Item on which the comment is made
* @param String $comment The comment itself
* @param String $username The username of the comment author
* @param int $userId The user id of the comment author
* @param int $parentId The ID of the direct parent comment, 0 if none.
* @return int The comment ID
*/
public function save($itemId, $comment, $username, $userId, $parentId = 0) {
//To force the re-fetching of the comment tree on the next getComments() call
$this->_redis->del("item:" . $itemId. " :parsedComments");
$isMasterComment = false;
if(!$parentId) {
$isMasterComment = true;
$parentId = $itemId;
} else {
$this->_redis->hSet("comment:" . $parentId, "hasChildren", 1);
}
$data = array("id" => $this->_getCommentId(),
"parentId" => $parentId,
"user_id" => $userId,
"username" => $username,
"content" => $comment,
"itemId" => $itemId,
"hasChildren" => 0,
"time" => time());
if($isMasterComment) {
$this->_redis->rPush("item:" . $itemId . ":comments", $data['id']);
} else {
$this->_redis->rPush("thread:" . $parentId, $data['id']);
}
$this->_redis->hMSet("comment:" . $data['id'], $data);
return $data['id'];
}
/**
* Returns all the comments attached to a given Item ID
* @param int $itemId The item id
* @return array The comments
*/
public function getComments($itemId) {
$parsedComments = $this->_redis->get("item:" . $itemId. " :parsedComments");
if(!$parsedComments) {
$data = $this->_multiFetch("item:" . $itemId . ":comments");
$parsedComments = $this->_processComments($data);
$this->_redis->set("item:" . $itemId. " :parsedComments", serialize($parsedComments));
} else {
$parsedComments = unserialize($parsedComments);
}
return $parsedComments;
}
/**
* Recursivly fetch the comment tree
* @param array $comments The first comment node
* @return array
*/
private function _processComments($comments) {
$curr = array();
foreach($comments as $comment) {
if($comment['hasChildren'] === "1") {
$data = $this->_multiFetch("thread:" . $comment['id']);
$curr[$comment['id']] = $this->_processComments($data);
}
$curr[$comment['parentId']][] = $comment;
}
return $curr;
}
/**
* Returns the comments according to a list of IDs stored in a Redis List
* @param String $keyName the Redis List key name
* @return array
*/
private function _multiFetch($keyName) {
$commentList = $this->_redis->lRange($keyName, 0, -1);
$this->_redis->multi();
foreach($commentList as $commentId) {
$this->_redis->hGetAll("comment:" . $commentId);
}
return $this->_redis->exec();
}
/**
* Generates a unique ID for the comment
* return String
*/
private function _getCommentId() {
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
mt_rand( 0, 0xffff ),
mt_rand( 0, 0x0fff ) | 0x4000,
mt_rand( 0, 0x3fff ) | 0x8000,
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
//Could also be
//return $this->_redis->incr("comments:id:next");
}
}
<?php
include "Comments.php";
$comments = new Comments();
$id = $comments->save(1, "Redis is awesome!", "Mikushi", 42);
$otherId = $comments->save(1, "i know!", "Asiendra", 43, $id);
$comments->save(1, "yeeha!", "Mikushi", 42, $otherId);
$comments->save(1, "mehehehe!", "Pythian", 44, $otherId);
$comments->save(1, "I'm jaleous!", "MongoDB", 45, $id);
$comments->save(1, "Me too!", "Memcached", 45, $id);
$comments->save(1, "Redis is the shit!", "Me", 46);
$comments->save(1, "Correction, Redis is The shit!", "OtherMe", 47);
$myComments = $comments->getComments(1);
printComments($myComments[1], 0, $myComments);
function printComments($thread, $margin, $container) {
foreach($thread as $comment) {
echo "<div style='border: 1px solid black; padding: 4px; margin-left: ".$margin."px'>" . $comment['username'] . " - " . $comment['content'] . "</div>";
if($comment['hasChildren'] === "1") {
printComments($container[$comment['id']][$comment['id']], $margin+20, $container[$comment['id']]);
}
}
}
@scottchasin
Copy link

So, how would you go about deleting a node from the tree? (Especially if it has children).

@Loupi
Copy link

Loupi commented May 22, 2012

I made a Ruby version based on this Gist. Here it is: https://gist.github.com/2766062

@Loupi
Copy link

Loupi commented May 25, 2012

I made a C# version based on this Gist. Here it is: https://gist.github.com/2788831

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment