PHP Observer Design Pattern Explained (Easy to understand)

PHP Observer Design Pattern Explained (Easy to understand) Thumbnail image

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
  1. SplSubject {
  2. /* Methods */
  3. abstract public void attach ( SplObserver $observer )
  4. abstract public void detach ( SplObserver $observer )
  5. abstract public void notify ( void )
  6. }
SplObserver
  1. SplObserver {
  2. /* Methods */
  3. abstract public void update ( SplSubject $subject )
  4. }

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

  1. /**
  2.  * Class Comment
  3.  */
  4. class AddedComment implements SplSubject
  5. {
  6.  
  7.     /**
  8.      * Array of the observers
  9.      *
  10.      * @var array
  11.      */
  12.     protected $observers = [];
  13.  
  14.     /**
  15.      * The comment text that was just added for our pretend blog comment
  16.      * @var string
  17.      */
  18.     public $comment_text;
  19.     /**
  20.      * The ID for the blog post that this just added blog comment relates to
  21.      * @var int
  22.      */
  23.     public $post_id;
  24.  
  25.     /**
  26.      * Comment constructor - save the $comment_text (for the recently submitted comment) and the $post_id that this blog comment relates to.
  27.      * @param $comment_text
  28.      * @param $post_id
  29.      */
  30.     public function __construct($comment_text, $post_id)
  31.     {
  32.  
  33.         $this->comment_text = $comment_text;
  34.         $this->post_id = $post_id;
  35.  
  36.     }
  37.  
  38.     /**
  39.      * Add an observer (such as EmailAuthor, EmailOtherCommentators or IncrementCommentCount) to $this->observers so we can cycle through them later
  40.      * @param SplObserver $observer
  41.      * @return AddedComment
  42.      */
  43.     public function attach(SplObserver $observer)
  44.     {
  45.         $key = spl_object_hash($observer);
  46.         $this->observers[$key] = $observer;
  47.  
  48.         return $this;
  49.     }
  50.  
  51.     /**
  52.      * Remove an observer from $this->observers
  53.      * @param SplObserver $observer
  54.      */
  55.     public function detach(SplObserver $observer)
  56.     {
  57.         $key = spl_object_hash($observer);
  58.         unset($this->observers[$key]);
  59.     }
  60.  
  61.     /**
  62.      * Go through all of the $this->observers and fire the ->update() method.
  63.      *
  64.      * (In Laravel and other frameworks this would often be called the ->handle() method.)
  65.      *
  66.      * @return mixed
  67.      */
  68.     public function notify()
  69.     {
  70.         /** @var SplObserver $observer */
  71.         foreach ($this->observers as $observer) {
  72.             $observer->update($this);
  73.         }
  74.     }
  75. }

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.

  1. /**
  2.  * Class EmailAuthor
  3.  * When ->update is called it should email the author of the blog post id.
  4.  *
  5.  */
  6. class EmailAuthor implements SplObserver
  7. {
  8.     public function update(SplSubject $subject)
  9.     {
  10.         echo __METHOD__ . " Emailing the author of post id: " . $subject->post_id . " that someone commented with : " . $subject->comment_text . "\n";
  11.     }
  12. }
  13.  
  14. /**
  15.  * Class EmailOtherCommentators
  16.  * When ->update() is called it should email other comment authors who have also commented on this blog post
  17.  */
  18. class EmailOtherCommentators implements SplObserver
  19. {
  20.     public function update(SplSubject $subject)
  21.     {
  22.         echo __METHOD__ . " Emailing all other comment authors who commented on " . $subject->post_id . " that someone commented with : " . $subject->comment_text . "\n";
  23.     }
  24. }
  25.  
  26. /**
  27.  * Class IncrementCommentCount
  28.  * Add 1 to the comment count column for the blog post.
  29.  *
  30.  * update blogposts.comment_count = comment_count + 1 where id = ?
  31.  */
  32. class IncrementCommentCount implements SplObserver
  33. {
  34.     public function update(SplSubject $subject)
  35.     {
  36.         echo __METHOD__ . " Updating comment count to + 1 for blog post id: " . $subject->post_id ."\n";
  37.     }
  38. }

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.

  1. $new_comment = 'hello, world';
  2. $blog_post_id = 123;
  3. // create a blog post here...
  4. echo "Created Blog Post\n";
  5.  
  6. // 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.
  7.  
  8. echo "Adding observers to subject\n";
  9. $addedComment = new AddedComment($new_comment, $blog_post_id); // << the subject
  10. $addedComment->attach(new IncrementCommentCount())->attach(new EmailOtherCommentators())->attach(new EmailAuthor());  // << adding the 3 observers
  11.  
  12. echo "Now going to notify() them...\n"
  13. $addedComment->notify();
  14.  
  15. 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

webdevetc profile pic
webdevetc

I am a 29 year old freelance backend web developer from London, mostly focusing on PHP and Laravel lately. This is my site - I mostly write about PHP here. Contact me here (especially for any contracting jobs early 2019 in London ;) ).

Leave a Comment