Using the Observer Design Pattern | PHP Code Examples in 2023

Last Updated on

CraftyTechie is reader-supported. When you buy through links on our site, we may earn an affiliate commission.

Using the Observer Pattern in PHP

The observer pattern is a behavioral design pattern that defines a one-to-many relationship between objects such that when the state of an object (publisher) changes, the other objects (subscribers) are notified.

Article Highlights

  • The observer pattern has a publisher object which updates all its subscribers when its state changes.
  • Benefits –  A change in the state of one entity is propagated to other entities.
  • Con – Sends updates in a random order which can be detrimental if observers have shared data dependency where sequence matters.

Observer Design Pattern PHP Code Example

<?php


interface Publisher {


   public function registerDisplay($display);
   public function removeDisplay($display);
   public function updateDisplays();
}


interface Observer {
   public function update($sportsNews, $politicsNews, $weatherNews);
}


/*
This class represents the publisher class now. NewsData implements publisher and also has an array of references to the subscriber objects.
*/
class NewsData implements Publisher {


   private $displays; //List of subscribers.


   private $sportsNews;
   private $politicsNews;
   private $weatherNews;


   public function __construct() {
       $this->displays = [];
   }


   public function registerDisplay($display) {
       $this->displays[$display->getID()] = $display; //Display has a unique ID used as array index. Helpful for removal.
   }


   public function removeDisplay($display) {
       unset($this->displays[$display->getID()]);
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getSportsNews() {
       return [
           "Team A vs Team B: Scorecard",
           "Team C out of Super 5 league",
           "New tournament schedule announced"
       ];
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getPoliticsNews() {
       return [
           "Mr.John elected as senator",
           "Government to revise the prices of gas",
           "Elections delays expected amidst political choas"
       ];
   }


    /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getWeatherNews() {
       return [
           "Heavy rains forecast from next week",
           "Snow storm expected in some parts of states",
           "Global Warming: Where are we heading"
       ];
   }


   /*
       This method runs when station pushes the latest news.
       This is a pseudo function and we don't have to worry about details on how
       it interact with the station
   */
   public function update() {
       $this->sportsNews = $this->getSportsNews();
       $this->politicsNews = $this->getPoliticsNews();
       $this->weatherNews = $this->getWeatherNews();


       $this->updateDisplays();
   }


   public function updateDisplays() {
       foreach($this->displays as $display) {
           $display->update($this->sportsNews, $this->politicsNews, $this->weatherNews);
       }
   }


}


class SportsDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "01";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Sports\n";
       foreach($sportsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


class PoliticsDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "02";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Politics\n";
       foreach($politicsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


class WeatherDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "03";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Weather\n";
       foreach($politicsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


$newsDataObject = new NewsData();


$sportsDisplay = new SportsDisplay();
$politicsDisplay = new PoliticsDisplay();
$weatherDisplay = new WeatherDisplay();


$newsDataObject->registerDisplay($sportsDisplay);
$newsDataObject->registerDisplay($politicsDisplay);
$newsDataObject->registerDisplay($weatherDisplay);


$newsDataObject->update();


/*
Sports
- Team A vs Team B: Scorecard
- Team C out of Super 5 league
- New tournament schedule announced
Politics
- Mr.John elected as senator
- Government to revise the prices of gas
- Elections delays expected amidst political choas
Weather
- Mr.John elected as senator
- Government to revise the prices of gas
- Elections delays expected amidst political choas
*/
?>

Table of Contents

observer pattern php

What is the Observer Pattern

“The observer pattern is a behavioral design pattern that defines a one-to-many relationship between objects such that when the state of an object (publisher) changes, the other objects (subscribers) are notified.”

observer pattern php

Observer Pattern Example

CentralSight is a news aggregator service that recently opened its API to application developers. A team of developers has been hired to create an application that would pull data from the CentralSight API and show three displays for politics, sports & weather news.

The API itself has two components: News Station and API. The API knows how to fetch data from the station (Developers don’t have to worry about this mechanism). Developers will add a third layer to this hierarchy by introducing an application (displays). We can also call use the term “presentational layer” for this layer.

The API knows how to update the application state by relaying the latest updates. The existing documentation will help developers understand the update logic in the API code. Here’s a top view of these layers.

news-station-app

Let’s also have an abstract overview of the NewsData class, which is the API object sitting between the two layers. 

news-data-object

Developers are now clear about the update logic in the NewsData. They will add code to the update() function because that’s the one being called whenever the station sends over updates. So, here’s the pseudocode of the first implementation.

<?php


/*
This class represents the API object that is supposed to get data from the station.
This is a pseudo method for demonstration purposes and therefore it will use dummy data.
*/
class NewsData {


   private $sportsDisplay;
   private $politicsDisplay;
   private $weatherDisplay;


   public function __construct() {
       $this->sportsDisplay = new SportsDisplay();
       $this->politicsDisplay = new PoliticsDisplay();
       $this->weatherDisplay = new WeatherDisplay();
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getSportsNews() {
       return [
           "Team A vs Team B: Scorecard",
           "Team C out of Super 5 league",
           "New tournament schedule announced"
       ];
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getPoliticsNews() {
       return [
           "Mr.John elected as a senator",
           "Government to revise the prices of gas",
           "Elections delays expected amidst political choas"
       ];
   }


    /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getWeatherNews() {
       return [
           "Heavy rains forecast from next week",
           "Snow storm expected in some parts of states",
           "Global Warming: Where are we heading"
       ];
   }


   /*
       This method runs when station pushes the latest news.
       This is a pseudo function and we don't have to worry about details on how
       it interact with the station
   */
   public function update() {


       $sportsUpdates = $this->getSportsNews();
       $politicsUpdates = $this->getPoliticsNews();
       $weatherUpdates = $this->getWeatherNews();


       /*
       This is how the current method send updates to the application.
       */
       $this->sportsDisplay->update($sportsUpdates);
       $this->politicsDisplay->update($politicsUpdates);
       $this->weatherDisplay->update($weatherUpdates);
   }


}


class SportsDisplay {


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($news) {
       echo "Sports\n";
       foreach($news as $n) {
           echo "- ".$n."\n";
       }
   }
}


class PoliticsDisplay {


   /*
       This method is supposed to update the application's display for politics updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($news) {
       echo "Politics\n";
       foreach($news as $n) {
           echo "- ".$n."\n";
       }
   }
}


class WeatherDisplay {


   /*
       This method is supposed to update the application's display for weather updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($news) {
       echo "Weather\n";
       foreach($news as $n) {
           echo "- ".$n."\n";
       }
   }
}

?>

Test driving this code yields the following output.

$newsDataObject = new NewsData();


$newsDataObject->update();


/*
Sports
- Team A vs Team B: Scorecard
- Team C out of Super 5 league
- New tournament schedule announced
Politics
- Mr.John elected as a senator
- Government to revise the prices of gas
- Elections delays expected amidst political choas
Weather
- Heavy rains forecast from next week
- Snow storm expected in some parts of states
- Global Warming: Where are we heading
*/

The NewsData uses concrete display classes and disburses updates by passing data to update() on every display class.

The pseudocode works fine, as the output shows. However, some aspects can be problematic from a design perspective.

Problem 🙁

Open-closed principle

The open-closed principle says, “A class should be closed to modification but open to extension.” Realistically, you cannot completely factor out modification. Still, the best you can do is minimize them so that a change doesn’t propel a chain reaction. 
The current implementation uses a concrete implementation and thus tightly couples the NewsData module with the application. These two are supposed to be independent layers with some form of communication. So, what are the downsides as a result?

Consider adding or removing displays or changing the concrete implementation that would affect the current understanding of the update() function. In both these scenarios, we must modify existing code and potentially introduce bugs as the application scales.

Lack of abstraction

“Program to interfaces (abstractions) not implementations” is a key to flexible design. This fact is somehow related to what we have just talked about. The current implementation refers to concrete instances and lacks a common interface.

Consequently, the NewsData binds to these instances, and any changes occurring to them can easily project out to the NewsData. However, a common structure for defining an interface for the display classes exists. So, developers will make sure to define one.

Runtime operations

The current implementation closes the door to adding or removing displays in the runtime. The lack of dynamic structure is a sign of rigid and inflexible design. A sophisticated enterprise-grade application usually expects dynamic behavior that is missing in this context.

Solution 🙂

Publisher & subscriber

There is an interesting analogy if you can think about Youtube. Viewers subscribe to their favorite channels. The viewers (subscribers) get notifications about new content uploaded to the channel.

We can use the term “Publisher” for the channel because it publishes the content. The viewers are “Subscribers” or “Observers” who receive updates about new content.

cat-channel

Pretty intuitive and interesting. But how does that relate to the subject here?

Introducing the observer pattern

Similar terminology translates as it is in the observer pattern. We have a publisher, the NewsData object. The display objects are “observers” or “subsribers”. 

There is a one-to-many relationship between the publisher and observers. It is a simple idea, and one publisher relays updates to one or more than one observers.

observer pattern php

In this design, the publisher class doesn’t program to concrete observer classes, just as we did before. Instead, the publisher class programs to the Observer interface. As long as a concrete class implements observer, the publisher can add it to its subscriber’s list and send updates without worrying about the underlying details.

The publisher class has an array of subscribers/observers. Observers can subscribe/unsubscribe in the runtime without affecting the publisher class. Besides, developers can add observers without modifying the existing code.

So, the observer pattern addresses our concerns with the former design.

Publisher & Subscriber Interface

The two important interfaces are Publisher and Subscriber. Observer the methods they declare. 

interface Publisher {


   public function registerDisplay($display);
   public function removeDisplay($display);
   public function updateDisplays();
}


interface Observer {
   public function update($sportsNews, $politicsNews, $weatherNews);
}

Quite intuitive, aren’t they? You may wonder why we are passing all three news data to update(). We will see why when we implement this interface.

NewsData: The publisher

The NewData class implements the Publisher interface. The most important features are:

  • It has an instance variable $displays which is supposed to be an array of subscribers.
  • registerDisplay() adds a display (subscriber).
  • removeDisplay() removes a display (subscriber) by an index, a unique display ID. 
  • updateDisplays() iterate through the $displays array calling update on every display (subscriber).
/*
This class represents the publisher class now. NewsData implements publisher and also has an array of references to the subscriber objects.
*/
class NewsData implements Publisher {


   private $displays; //List of subscribers.


   private $sportsNews;
   private $politicsNews;
   private $weatherNews;


   public function __construct() {
       $this->displays = [];
   }


   public function registerDisplay($display) {
       $this->displays[$display->getID()] = $display; //Display has a unique ID used as array index. Helpful for removal.
   }


   public function removeDisplay($display) {
       unset($this->displays[$display->getID()]);
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getSportsNews() {
       return [
           "Team A vs Team B: Scorecard",
           "Team C out of Super 5 league",
           "New tournament schedule announced"
       ];
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getPoliticsNews() {
       return [
           "Mr.John elected as a senator",
           "Government to revise the prices of gas",
           "Elections delays expected amidst political choas"
       ];
   }


    /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getWeatherNews() {
       return [
           "Heavy rains forecast from next week",
           "Snow storm expected in some parts of states",
           "Global Warming: Where are we heading"
       ];
   }


   /*
       This method runs when station pushes the latest news.
       This is a pseudo function and we don't have to worry about details on how
       it interact with the station
   */
   public function update() {
       $this->sportsNews = $this->getSportsNews();
       $this->politicsNews = $this->getPoliticsNews();
       $this->weatherNews = $this->getWeatherNews();


       $this->updateDisplays();
   }


   public function updateDisplays() {
       foreach($this->displays as $display) {
           $display->update($this->sportsNews, $this->politicsNews, $this->weatherNews);
       }
   }


}

Display classes: Observers

The display classes implement the Observer interface. The update() function now takes all three instances of data (sports, politics, weather), and that’s because the NewsData class now doesn’t know about the concrete instance. From a NewsData perspective, it calls update() on an Observer and doesn’t care about the specific type.

That adds some redundancy on the observer’s end, but it is a trade-off. Type-checking in NewsData class will defeat the purpose. We can also use a factory to deal with it, adding more complexity.

So, let’s continue without worrying much about these parameters now.

class SportsDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "01";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Sports\n";
       foreach($sportsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


class PoliticsDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "02";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Politics\n";
       foreach($politicsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


class WeatherDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "03";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Weather\n";
       foreach($weatherNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}

Time to check some updates

Let’s put the observer pattern to the test.

$newsDataObject = new NewsData();


$sportsDisplay = new SportsDisplay();
$politicsDisplay = new PoliticsDisplay();
$weatherDisplay = new WeatherDisplay();


$newsDataObject->registerDisplay($sportsDisplay);
$newsDataObject->registerDisplay($politicsDisplay);
$newsDataObject->registerDisplay($weatherDisplay);


$newsDataObject->update();


/*
Sports
- Team A vs Team B: Scorecard
- Team C out of Super 5 league
- New tournament schedule announced
Politics
- Mr.John elected as a senator
- Government to revise the prices of gas
- Elections delays expected amidst political choas
Weather
- Heavy rains forecast from next week
- Snow storm expected in some parts of states
- Global Warming: Where are we heading
*/

An Alternative to the Hollywood Principle

The current design relies on the Hollywood principle, which says, “Don’t call us, we will call you,” which means that the publisher sends over the update. The observers just wait for the publisher’s call. 

That’s one reason we passed all the news parameters to the update() function. An alternative approach is to open up the publisher’s getter methods for the public. The observers will call the appropriate getters on the publisher’s object.

The Observer interface changes as follows.

interface Observer {
   public function update();
}

The publisher calls update() on the observers without passing any parameters. The observers now have a reference to the NewsData object and call the appropriate getter method to fetch the data of their interest.

Benefits of the Observer Pattern

  • A change in the state of one entity is propagated to other entities.
  • Add or remove listeners or observers more easily. 
  • Establishes one-to-many relationships between entities without coupling them.

Complete Architecture | Observer Pattern in PHP

observer pattern php

Observer Pattern PHP Pseudocode Example

<?php


interface Publisher {


   public function registerDisplay($display);
   public function removeDisplay($display);
   public function updateDisplays();
}


interface Observer {
   public function update($sportsNews, $politicsNews, $weatherNews);
}


/*
This class represents the publisher class now. NewsData implements publisher and also has an array of references to the subscriber objects.
*/
class NewsData implements Publisher {


   private $displays; //List of subscribers.


   private $sportsNews;
   private $politicsNews;
   private $weatherNews;


   public function __construct() {
       $this->displays = [];
   }


   public function registerDisplay($display) {
       $this->displays[$display->getID()] = $display; //Display has a unique ID used as array index. Helpful for removal.
   }


   public function removeDisplay($display) {
       unset($this->displays[$display->getID()]);
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getSportsNews() {
       return [
           "Team A vs Team B: Scorecard",
           "Team C out of Super 5 league",
           "New tournament schedule announced"
       ];
   }


   /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getPoliticsNews() {
       return [
           "Mr.John elected as senator",
           "Government to revise the prices of gas",
           "Elections delays expected amidst political choas"
       ];
   }


    /*
   The pseudo method uses dummy data for demo purposes
   */
   private function getWeatherNews() {
       return [
           "Heavy rains forecast from next week",
           "Snow storm expected in some parts of states",
           "Global Warming: Where are we heading"
       ];
   }


   /*
       This method runs when station pushes the latest news.
       This is a pseudo function and we don't have to worry about details on how
       it interact with the station
   */
   public function update() {
       $this->sportsNews = $this->getSportsNews();
       $this->politicsNews = $this->getPoliticsNews();
       $this->weatherNews = $this->getWeatherNews();


       $this->updateDisplays();
   }


   public function updateDisplays() {
       foreach($this->displays as $display) {
           $display->update($this->sportsNews, $this->politicsNews, $this->weatherNews);
       }
   }


}


class SportsDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "01";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Sports\n";
       foreach($sportsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


class PoliticsDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "02";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Politics\n";
       foreach($politicsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


class WeatherDisplay implements Observer {


   private $ID;


   function __construct() {
       $this->ID = "03";
   }


   /*
       This method is supposed to update the application's display for sports updates.
       This is a pseudo method for demonstration purposes so the method will print the output
       to the console.
   */
   public function update($sportsNews, $politicsNews, $weatherNews) {
       echo "Weather\n";
       foreach($politicsNews as $n) {
           echo "- ".$n."\n";
       }
   }


   public function getID() {
       return $this->ID;
   }
}


$newsDataObject = new NewsData();


$sportsDisplay = new SportsDisplay();
$politicsDisplay = new PoliticsDisplay();
$weatherDisplay = new WeatherDisplay();


$newsDataObject->registerDisplay($sportsDisplay);
$newsDataObject->registerDisplay($politicsDisplay);
$newsDataObject->registerDisplay($weatherDisplay);


$newsDataObject->update();


/*
Sports
- Team A vs Team B: Scorecard
- Team C out of Super 5 league
- New tournament schedule announced
Politics
- Mr.John elected as senator
- Government to revise the prices of gas
- Elections delays expected amidst political choas
Weather
- Mr.John elected as senator
- Government to revise the prices of gas
- Elections delays expected amidst political choas
*/
?>

Pros and Cons of the Observer Pattern in PHP

ProsCons
Satisfies the open/closed principle: Open to adding more subscribers without modifying the existing code. We can also add more publishers.Sends updates in a random order which can be detrimental if observers have shared data dependency where sequence matters.
Publishers and observers are loosely coupled and can be reused.
Can add or remove observers in the runtime.

Where is the Observer Pattern Used

  • Applications with messaging and notification features.
  • Newsletters, content sharing, and social platforms.
  • In graphical user interfaces to propagate events to other elements (event listeners).

What is the Observer pattern?

The observer pattern is a behavioral design pattern that defines a one-to-many relationship between objects such that when the state of an object (publisher) changes, the other objects (subscribers) are notified.

Using the Observer Pattern in PHP Today

Phew! That’s a lot of content to absorb. Let’s have a rather summary of what we did in this article. This article is about the observer pattern, a behavioral design pattern that defines a one-to-many relationship between objects such that when the state of an object (publisher) changes, the other objects (subscribers) are notified.

The article features a news aggregator API CentralSight which has opened application APIs. Developers have been assigned to add a presentational layer, an application with three displays featuring sports, politics, and weather updates, receiving the data from the API.

The developers develop a solution that works but violates the fundamentals of flexible software design. Contemplating further, they identify a mechanism similar to what we regularly see when subscribing to a favorite channel and getting notifications when new content is live.

The observer pattern is based on this mechanism, where a publisher pushes notifications or updates, and the observers receive them. In this application, the NewsData object acts as a publisher, and the application’s displays act as observers.

With observer patterns, the publisher and observers act loosely coupled entities open to extension and closed to modification. This publisher is least interested in the concrete implementations because it programs to the Observer interface.

As a result, developers created a flexible and maintainable solution, which was the ultimate goal.

That’s all. See you in another design pattern article.

Books on Design Patterns

Want to learn more about Design Patterns? There are many great resources online. We recommend the following books for your collection as they both can teach you the theoretical and the application of using design patterns in your day-to-day programming. Feel free to use the following Amazon affiliate links if you’d like to purchase them and a way to support our efforts.

Design Patterns: Elements of Reusable Object-Oriented Software

Design Patterns: Elements of Reusable Object-Oriented Software book

This is the book that started it all. I believe that every programmer should have a referenced copy to this book at some point in their career. There have been many updates and excellent newer content through the years, but this is a classic. It still stands the test of time and is just as relevant for today as it was in the original printing in the 90s.

Check it out on amazon

Learning PHP Design Patterns

This is an excellent book to go beyond the theory and apply it to writing good PHP code. Learning PHP Design Patterns is published by the popular O’Reilly media company. O’Reilly consistently publishes some of the most useful reference material related to software development. They are known to provide materials that thoroughly cover a topic in a way that is simple to understand. I recommend this book to every PHP developer.

Check it out on amazon

Want to see our full review of books on design patterns? Read our huge review article on over 15 design pattern books.

Design Patterns in PHP Learning Series.

This article is part of our series of design patterns in PHP. We are going through all of the patterns and showing how they can help you build better applications. Browse through our full list of patterns below.

Did you find this article helpful?

Join the best weekly newsletter where I deliver content on building better web applications. I curate the best tips, strategies, news & resources to help you develop highly-scalable and results-driven applications.

Build Better Web Apps

I hope you're enjoying this article.

Get the best content on building better web apps delivered to you.