Skip to content

Commit

Permalink
Refactor Thread denormalization
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar committed Jun 27, 2011
1 parent 0d449bc commit b074426
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 41 deletions.
27 changes: 26 additions & 1 deletion Document/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ abstract class Message extends AbstractMessage
/**
* Tells if this participant has read this message
*
* @return bool
* @param ParticipantInterface $participant
* @return boolean
*/
public function isReadByParticipant(ParticipantInterface $participant)
{
Expand All @@ -34,4 +35,28 @@ public function setIsReadByParticipant(ParticipantInterface $participant, $isRea
{
$this->isReadByParticipant[$participant->getId()] = (boolean) $isRead;
}

/**
* Ensures that each participant has an isRead flag
*
* @param array $participants list of ParticipantInterface
*/
public function ensureIsReadByParticipant(array $participants)
{
foreach ($participants as $participant) {
if (!isset($this->isReadByParticipant[$participant->getId()])) {
$this->isReadByParticipant[$participant->getId()] = false;
}
}
}

/**
* Gets the created at timestamp
*
* @return int
*/
public function getTimestamp()
{
return $this->getCreatedAt()->getTimestamp();
}
}
128 changes: 95 additions & 33 deletions Document/Thread.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function getMessages()
public function addMessage(MessageInterface $message)
{
$this->messages->add($message);
$this->denormalize($message);
$this->denormalize();
}

/**
Expand Down Expand Up @@ -148,52 +148,114 @@ public function setIsDeletedByParticipant(ParticipantInterface $participant, $is
}

/**
* Performs denormalization tricks
* based on a message belonging to this thread.
* Updates participants and last message dates.
*
* Take it easy, this code is tested in Tests\Document\ThreadTest ;)
* DENORMALIZATION
*
* @param MessageInterface $message
* All following methods are relative to denormalization
*/

/**
* Performs denormalization tricks
*/
protected function denormalize()
{
$this->doParticipants();
$this->doKeywords();
$this->doEnsureMessagesIsRead();
$this->doDatesOfLastMessageWrittenByParticipant();
$this->doDatesOfLastMessageWrittenByOtherParticipant();
$this->doEnsureIsDeletedByParticipant();
}

/**
* Ensures that the thread participants are up to date
*/
protected function doParticipants()
{
foreach ($this->getMessages() as $message) {
$this->addParticipant($message->getSender());
}
}

/**
* Adds all messages contents to the keywords property
*/
protected function doKeywords()
{
$keywords = $this->getSubject();

foreach ($this->getMessages() as $message) {
$keywords .= ' '.$message->getBody();
}

// we only need each word once
$this->keywords = implode(' ', array_unique(str_word_count(strtolower($keywords), 1)));
}

/**
* Ensures that every message has a isRead flag for each participant
*/
protected function denormalize(MessageInterface $message)
protected function doEnsureMessagesIsRead()
{
$sender = $message->getSender();
$this->addParticipant($sender);
$message->setIsReadByParticipant($sender, true);
foreach ($this->getMessages() as $message) {
$message->setIsReadByParticipant($message->getSender(), true);
$message->ensureIsReadByParticipant($this->getParticipants());
}
}

// Update the last message dates if needed
$messageTs = $message->getCreatedAt()->getTimestamp();
$senderId = $sender->getId();
foreach ($this->participants as $participant) {
/**
* Update the dates of last message written by participant
*/
protected function doDatesOfLastMessageWrittenByParticipant()
{
$this->datesOfLastMessageWrittenByParticipant = $this->greaterMessageTimestampForCondition(
$this->datesOfLastMessageWrittenByParticipant,
function($participantId, $senderId) { return $participantId === $senderId; }
);
}

/**
* Update the dates of last message written by other participants
*/
protected function doDatesOfLastMessageWrittenByOtherParticipant()
{
$this->datesOfLastMessageWrittenByOtherParticipant = $this->greaterMessageTimestampForCondition(
$this->datesOfLastMessageWrittenByOtherParticipant,
function($participantId, $senderId) { return $participantId !== $senderId; }
);
}

/**
* Gets dates of last message for each participant, depending on the condition
*
* @param array $dates
* @param \Closure $condition
* @return array
*/
protected function greaterMessageTimestampForCondition(array $dates, \Closure $condition)
{
foreach ($this->getParticipants() as $participant) {
$participantId = $participant->getId();
if ($participantId != $senderId) {
if (!isset($this->datesOfLastMessageWrittenByOtherParticipant[$participantId]) || $this->datesOfLastMessageWrittenByOtherParticipant[$participantId] < $messageTs) {
$this->datesOfLastMessageWrittenByOtherParticipant[$participantId] = $messageTs;
$date = isset($dates[$participantId]) ? $dates[$participantId] : 0;
foreach ($this->getMessages() as $message) {
if ($condition($participantId, $message->getSender()->getId())) {
$date = max($date, $message->getTimestamp());
}
$message->setIsReadByParticipant($participant, false);
} elseif (!isset($this->datesOfLastMessageWrittenByParticipant[$participantId]) || $this->datesOfLastMessageWrittenByParticipant[$participantId] < $messageTs) {
$this->datesOfLastMessageWrittenByParticipant[$participantId] = $messageTs;
}
if (!array_key_exists($participantId, $this->isDeletedByParticipant)) {
$this->isDeletedByParticipant[$participantId] = false;
}
$dates[$participantId] = $date;
}
// having theses sorted by user does not harm, and it makes unit testing easier
ksort($this->datesOfLastMessageWrittenByParticipant);
ksort($this->datesOfLastMessageWrittenByOtherParticipant);

$this->denormalizeKeywords();
return $dates;
}

/**
* Adds all messages contents to the keywords property
* Ensures that each participant has an isDeleted flag
*/
public function denormalizeKeywords()
protected function doEnsureIsDeletedByParticipant()
{
$this->keywords = $this->getSubject();
foreach ($this->getMessages() as $message) {
$this->keywords .= ' '.$message->getBody();
foreach ($this->getParticipants() as $participant) {
if (!isset($this->isDeletedByParticipant[$participant->getId()])) {
$this->isDeletedByParticipant[$participant->getId()] = false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

use DateTime;
use Ornicar\MessageBundle\Model\ParticipantInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Ornicar\MessageBundle\Model\MessageInterface;

class ThreadDenormalizationTest extends \PHPUnit_Framework_TestCase
class ThreadDenormalizerTest extends \PHPUnit_Framework_TestCase
{
protected $dates;

Expand All @@ -29,6 +31,8 @@ public function testDenormalize()
* First message
*/
$message = $this->createMessageMock($user1, $user2, $this->dates[0]);
$thread->setSubject('Test thread subject');
$thread->addParticipant($user2);
$thread->addMessage($message);

$this->assertSame(array($user1, $user2), $thread->getParticipants());
Expand Down Expand Up @@ -69,26 +73,27 @@ public function testDenormalize()

protected function createMessageMock($sender, $recipient, DateTime $date)
{
$message = $this->getMockBuilder('Ornicar\MessageBundle\Model\MessageInterface')
->disableOriginalConstructor(true)
$message = $this->getMockBuilder('Ornicar\MessageBundle\Document\Message')
->getMock();

$message->expects($this->once())
$message->expects($this->atLeastOnce())
->method('getSender')
->will($this->returnValue($sender));
$message->expects($this->once())
$message->expects($this->atLeastOnce())
->method('getRecipient')
->will($this->returnValue($recipient));
$message->expects($this->once())
$message->expects($this->atLeastOnce())
->method('getCreatedAt')
->will($this->returnValue($date));
$message->expects($this->once())
->method('ensureIsReadByParticipant');

return $message;
}

protected function createParticipantMock($id)
{
$user = $this->getMockBuilder('ParticipantInterface')
$user = $this->getMockBuilder('Ornicar\MessageBundle\Model\ParticipantInterface')
->disableOriginalConstructor(true)
->getMock();

Expand All @@ -111,4 +116,26 @@ public function getDatesOfLastMessageWrittenByOtherParticipant()
{
return $this->datesOfLastMessageWrittenByOtherParticipant;
}

public function addMessage(MessageInterface $message)
{
parent::addMessage($message);

$this->sortDenormalizedProperties();
}

/**
* Sort denormalized properties to ease testing
*/
protected function sortDenormalizedProperties()
{
ksort($this->isDeletedByParticipant);
ksort($this->datesOfLastMessageWrittenByParticipant);
ksort($this->datesOfLastMessageWrittenByOtherParticipant);
$participants = $this->participants->toArray();
usort($participants, function(ParticipantInterface $p1, ParticipantInterface $p2) {
return $p1->getId() > $p2->getId();
});
$this->participants = new ArrayCollection($participants);
}
}

0 comments on commit b074426

Please sign in to comment.