PHP Observer Design Pattern

The observer design pattern is very commonly used in PHP projects.

The basic idea is that one object (the 'subject') will send a notification to an 'observer' if the subject's state changes. You can create a 'one-to-many' dependency (one subject, but many dependencies change).

The Standard PHP Library actually provides a built in observer / subject interfaces: SplObserver and SplObserver.

SplSubject
<?php
interface SplSubject {
/* Methods */
public function attach ( SplObserver $observer );
public function detach ( SplObserver $observer );
public function notify (  );
}
SplObserver
<?php
interface SplObserver {
/* Methods */
public function update ( SplSubject $subject ) 
}

How does it work?

For the rest of this example I'm going to pretend we have a blog system. Every time a blog comment is added to a blog post, it should do a few things (email the blog post author, increment the 'number of comments' count, email all other commenters that another comment was added).

The Subject

This has a list of all the observers, which it will later cycle through (foreach) and fire each one's ::update() method

<?php
/**
 * Class Comment
 */
class AddedComment implements SplSubject
{
    /**
     * Array of the observers
     *
     * @var array
     */
    protected $observers = [];
    /**
     * The comment text that was just added for our pretend blog comment
     * @var string
     */
    public $comment_text;
    /**
     * The ID for the blog post that this just added blog comment relates to
     * @var int
     */
    public $post_id;
    /**
     * Comment constructor - save the $comment_text (for the recently submitted comment) and the $post_id that this blog comment relates to.
     * @param $comment_text
     * @param $post_id
     */
    public function __construct($comment_text, $post_id)
    {
        $this->comment_text = $comment_text;
        $this->post_id = $post_id;
    }
    /**
     * Add an observer (such as EmailAuthor, EmailOtherCommentators or IncrementCommentCount) to $this->observers so we can cycle through them later
     * @param SplObserver $observer
     * @return AddedComment
     */
    public function attach(SplObserver $observer)
    {
        $key = spl_object_hash($observer);
        $this->observers[$key] = $observer;
        return $this;
    }
    /**
     * Remove an observer from $this->observers
     * @param SplObserver $observer
     */
    public function detach(SplObserver $observer)
    {
        $key = spl_object_hash($observer);
        unset($this->observers[$key]);
    }
    /**
     * Go through all of the $this->observers and fire the ->update() method.
     *
     * (In Laravel and other frameworks this would often be called the ->handle() method.)
     *
     * @return mixed
     */
    public function notify()
    {
        /** @var SplObserver $observer */
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
}

The important parts for this basic example are the attach() method (so we can add the observers later) and notify() function (so we can go through each observer and fire the ::update() method on them.

The observers

For this example I'm going to use three observers. They are all very similar in this example.

<?
/**
 * Class EmailAuthor
 * When ->update is called it should email the author of the blog post id.
 *
 */
class EmailAuthor implements SplObserver
{
    public function update(SplSubject $subject)
    {
        echo __METHOD__ . " Emailing the author of post id: " . $subject->post_id . " that someone commented with : " . $subject->comment_text . "\n";
    }
}
/**
 * Class EmailOtherCommentators
 * When ->update() is called it should email other comment authors who have also commented on this blog post
 */
class EmailOtherCommentators implements SplObserver
{
    public function update(SplSubject $subject)
    {
        echo __METHOD__ . " Emailing all other comment authors who commented on " . $subject->post_id . " that someone commented with : " . $subject->comment_text . "\n";
    }
}
/**
 * Class IncrementCommentCount
 * Add 1 to the comment count column for the blog post.
 *
 * update blogposts.comment_count = comment_count + 1 where id = ?
 */
class IncrementCommentCount implements SplObserver
{
    public function update(SplSubject $subject)
    {
        echo __METHOD__ . " Updating comment count to + 1 for blog post id: " . $subject->post_id ."\n";
    }
}

As each one implements SplSubject they must have a update() method. In these example classes they don't do anything but echo some output, but obviously, in the real world they would do something more useful.

And the piece that puts it all together

The following code now attaches 3 observers (emailAuthor, etc) to the subject (AddedComment()). Then fires notify() which cycles through all 3 of the attached observers and fires the notify() method.

<?php
$new_comment = 'hello, world';
$blog_post_id = 123;
// create a blog post here...
echo "Created Blog Post\n"; 
// you could actually save the blog post in an observer too BTW. But often in the real world, I find this won't work as well, as you need to actually send the whole BlogPostComment (or whatever object you have) to the observers and it just makes things clearer if you have already created and saved that item in the DB already.
echo "Adding observers to subject\n";
$addedComment = new AddedComment($new_comment, $blog_post_id); // << the subject
$addedComment->attach(new IncrementCommentCount())->attach(new EmailOtherCommentators())->attach(new EmailAuthor());  // << adding the 3 observers
echo "Now going to notify() them...\n"
$addedComment->notify();
echo "Done\n";

This will produce the following output when run:

Created blog post...
Adding observers to subject
Now going to notify() them...
IncrementCommentCount::update Updating comment count to + 1 for blog post id: 123
EmailOtherCommenters::update Emailing all other comment authors who commented on 123 that someone commented with : hello, world
EmailAuthor::update Emailing the author of post id: 123 that someone commented with : hello, world
done

Observer Pattern in the real world

This was a basic example that has been simplified for the purposes of this blog post. The observer pattern is used all of the time in the real world. Often it won't actually implement SplObserver/SplSubject - there will be custom implementations of these ideas.

Laravel uses them for many things, including events (subjects) and eventlisteners (observers), and the slightly more complicated observers.

You should use the observer pattern when you want one action to update many others (one-to-many). In the example above, 1 blog comment was added, which in turn fired off 3 observers.

Design pattern type: Behavioural design pattern

Comments PHP Observer Design Pattern