Observer Pattern - A Real World Example
Within the observer pattern, there are subjects, and there are observers. When something specific happens to the subject, all of the observers are notified. Generally, the subject should know nothing about the observers' implementation, other then the fact that it's an observer.
When implemented correctly, observers can be attached and detached from the subject during runtime. PHP ships with some interfaces to help build this design pattern, SplSubject and SplObserver, although, sometimes it's easier to just write your own.
This pattern came mightily useful on the comment system of this website. When someone leaves a comment, three emails get fired off:
- I get an email
- The commenter gets an email confirmation
- If the comment was a reply to another comment, the author of the original comment gets notified
I have made an observer for each of these cases.
In my codebase, I have a service for handling comments, which acts as the subject, and I've attached all three observers to it.
Let's take a look at the implementation:
Subject - Service_Comment
class Service_Comment
extends Keplin_Service_Abstract
implements SplSubject
{
protected $_form;
protected $_comment;
protected $_post;
protected $_observers;
public function __construct(Model_Post $post)
{
$this->_post = $post;
$this->enableCache();
}
public function create($data)
{
$form = $this->getForm();
if($form->isValid($data))
{
$comment = new Model_Comment($data);
$comment->comment = nl2br($comment->comment);
$comment->parent_id = $data['parent_id'];
$comment->ip_address = $_SERVER['REMOTE_ADDR'];
$comment->post_id = $this->_post->id;
$comment->status = 0;
$comment->date_added = date("Y-m-d H:i:s");
$this->setComment($comment);
$mapper = Keplin_Model_Mapper_Factory::create('Comment', $this->_enable_caching);
$mapper->save($comment);
$this->notify();
$form->clear();
$this->_message('comment');
}
else
{
$this->_message('form_errors');
}
}
//Other functions...
public function attach(SplObserver $observer)
{
$id = spl_object_hash($observer);
$this->_observers[$id] = $observer;
}
public function detach(SplObserver $observer)
{
$id = spl_object_hash($observer);
unset($this->_observers[$id]);
}
public function notify()
{
foreach($this->_observers as $observer)
{
$observer->update($this);
}
}
}
Shown above, the comment service implements SplSubject, and can attach and detach observers. When a comment is created, the notify() function is fired off, and each of the attached observers are updated.
Observer - Keplin_Mail_Comment
class Keplin_Mail_Comment
extends Keplin_Mail
implements SplObserver
{
public function update(SplSubject $subject)
{
$comment = $subject->getComment();
$this->send($comment, $subject->getPost());
}
public function send(Model_Comment $comment, Model_Post $post)
{
//Sends Mail
}
}
Observer - Keplin_Mail_Commenter
class Keplin_Mail_Commenter
extends Keplin_Mail
implements SplObserver
{
public function update(SplSubject $subject)
{
$comment = $subject->getComment();
if($comment->parent_id)
{
$mapper = new Model_Mapper_Comment();
$parent_comment = $mapper->getComment($comment->parent_id);
$this->send($parent_comment, $subject->getPost());
}
}
public function send(Model_Comment $comment, Model_Post $post)
{
//Sends Mail
}
}
Observer - Keplin_Mail_Author
class Keplin_Mail_Author
extends Keplin_Mail
implements SplObserver
{
public function update(SplSubject $subject)
{
$this->send($subject->getComment(), $subject->getPost());
}
public function send(Model_Comment $comment, Model_Post $post)
{
//Sends Mail
}
}
The three classes above send off the emails. They all implement SplObserver, so they have an update() function.
Now lets take a look at the instantiation of my subject:
$service = new Service_Comment($post); $service->attach(new Keplin_Mail_Author()); $service->attach(new Keplin_Mail_Commenter()); $service->attach(new Keplin_Mail_Comment()); //Later on, if a comment is made $service->create($data);
The best part is, if I wanted to create a new observer, I could easily do it without touching any of my current code. Also, if my system had a set of users, I'd be able to attach and detach these observers at runtime depending on the current user's preferences.
To view the code as a whole, you can access my GitHub repository



Chris - September 28th, 2010 @ 12:49am reply
soccerjersey - May 19th, 2011 @ 12:02am reply