Design Pattern: Observer Pattern in PHP

Observer pattern implements a publish/subscribe mechanism between objects. Some subscribing objects want to observe the state change of a specific object, and the observer pattern enables the publishing object(subject) to notify other objects about changes.

This article demonstrates Observer pattern implementations in PHP. Check the implementation details and examples.

Implementation

PHP has built-in interfaces – SplObserver and SplSubject for the observer implementation.

Here we are discussing 2 methods – one with the built-in interfaces, and another with our own interface so that we can add some flexibility to the implementation.

Method #1: Using Built-in Interfaces

PHP has predefined interfaces SplObserver and SplSubject for the implementation. Let’s check the predefined interfaces first-

// Interface for the subject class
interface SplSubject {
    // Add a new observer to the observer list
    public function attach(\SplObserver $observer): void;

    // Remove an existing observer from the list
    public function detach(\SplObserver $observer): void;

    // Notify observers/subscribsers about state change
    public function notify(): void;
}

// Interface for the obsers
interface SplObserver {
    // Receive update/notification from the subject
    public function update(\SplSubject $subject): void;
}

Follow the steps below for the implementation-

  • Define your subject class. Implement interface \SplSubject for the class. Define a list of observer objects, the type of the list is SplObjectStorage (can be an array if required). Define all the states and utility methods for the class. When any state change in the class, call the update method, to notify the observers.
  • Define observer classes. Implement \SplObserver interface for the class.
  • In the client create a new object of the subject class. Create new observer objects and attach those to the subject.

Check the following example of simple observer implementation using built-in observer interfaces-

Use of the \SplObjectStorage class is not directly related to the observer partner. This is used to manage a list of observer objects easily.

<?php
// Observer pattern in PHP
// Using built in PHP observer interfaces

// Concrete subject class
class ConcreteSubject implements \SplSubject {
    public int $state;
    private \SplObjectStorage $observerList;

    public function __construct() {
        $this->observerList = new \SplObjectStorage;
    }

    public function setState(int $state): void {
        $this->state = $state;
        $this->notify();
    }

    public function attach(\SplObserver $observer): void {
        $this->observerList->attach($observer);
    }

    public function detach(\SplObserver $observer): void {
        $this->observerList->detach($observer);
    }

    public function notify(): void {
        foreach ($this->observerList as $observer) {
            $observer->update($this);
        }
    }
}

// Concrete observer
class ObserverOne implements \SplObserver {
    public function update(\SplSubject $subject): void {
        echo "Received update in ObserverOne: " . $subject->state . "\n";
    }
}

// Concrete observer
class ObserverTwo implements \SplObserver {
    public function update(\SplSubject $subject): void {
        echo "Received update in ObserverTwo: " . $subject->state . "\n";
    }
}


// Demo
$subject = new ConcreteSubject();
$subject->attach(new ObserverOne());
$subject->attach(new ObserverTwo());


echo "Setting subject state value to: 10\n";

$subject->setState(10);


echo "Setting subject state value to: 999\n";

$subject->setState(999);

Output will be as follows-

Setting subject state value to: 10

Received update in ObserverOne: 10
Received update in ObserverTwo: 10


Setting subject state value to: 999

Received update in ObserverOne: 999
Received update in ObserverTwo: 999

Method #2: Custom Observer

Use this custom observer implementation if your observer and/or subscriber classes are complex and the built-in interfaces do not serve your requirements.

  • Define a subject interface. Declare methods for adding new observer and notifying about updates.
  • Create a concrete subject class and implement the subject interface. Maintain a list of observers, and whenever there is a change call the notification method. In the notification method loop through the list of the observers and notify those.
  • Create interface (or abstract classes) for the observers. Declare abstract method for receiving the updates.
  • Create observer class, and implement the interface. 
<?php
// Simple Observer pattern in PHP

// Interface for the subject
interface Subject {
    function getState(): int;

    // Add new observer for the subject state
    function attachObserver(Observer $observer): void;

    // Notify all observers attached to the subject
    function notifyObservers(): void;
}

// Concrete subject class
class ConcreteSubject implements Subject {
    private int $state;
    private array $observerList = [];


    public function setState(int $state): void {
        $this->state = $state;
        $this->notifyObservers();
    }

    public function getState(): int {
        return $this->state;
    }

    public function attachObserver(Observer $observer): void {
        array_push($this->observerList, $observer);
    }

    public function notifyObservers(): void {
        foreach ($this->observerList as $observer) {
            $observer->update();
        }
    }
}

// Observer
abstract class Observer {
    protected ?Subject $subject;

    abstract function update(): void;
}

// Concrete observer
class ObserverOne extends Observer {
    public function __construct(Subject $subject) {
        $this->subject = $subject;
        $this->subject->attachObserver($this);
    }

    public function update(): void {
        echo "Received in ObserverOne: " . $this->subject->getState() . "\n";
    }
}

// Concrete observer
class ObserverTwo extends Observer {
    public function __construct(Subject $subject) {
        $this->subject = $subject;
        $this->subject->attachObserver($this);
    }

    public function update(): void {
        echo "Received in ObserverTwo: " . $this->subject?->getState() . "\n";
    }
}


// Demo
$subject = new ConcreteSubject();
new ObserverOne($subject);
new ObserverTwo($subject);


echo "Setting subject state value to: 10\n";

$subject->setState(10);


echo "Setting subject state value to: 999\n";

$subject->setState(999);

Output of this code-

Setting subject state value to: 10
Received in ObserverOne: 10
Received in ObserverTwo: 10


Setting subject state value to: 999
Received in ObserverOne: 999
Received in ObserverTwo: 999

Examples

Check the following examples of Observer pattern implementation.

Example #1: Player Observer

In this example we are consider a player score update notification. We have multiple observer classes for referee, commentator, and scoreboard.

Player score observer/subscriber diagram

Player [Subject]

  • Create file “Player.php”.
  • Define class “Player”.
  • Implement \SplSubject interface for the class.
  • Define property “$observers” of type \SplObjectStorage (this can be an array if you want). This is used to maintain a list of observers.
  • Define property “$score”. also define a method for setting the score. In the method implementation, call the notify method.
<?php
// Player.php

namespace BigBoxCode\DesignPattern\Observer\Player;

class Player implements \SplSubject {
    public string $name;
    public int $score;

    private \SplObjectStorage $observers;

    public function __construct(string $name) {
        $this->name = $name;

        $this->observers = new \SplObjectStorage;
    }

    public function setScore(int $score): void {
        $this->score = $score;
        $this->notify();
    }

    public function attach(\SplObserver $observer): void {
        $this->observers->attach($observer);
    }

    public function detach(\SplObserver $observer): void {
        $this->observers->detach($observer);
    }

    public function notify(): void {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
}

Referee Class [Observer]

  • Create file “Referee.php”.
  • Define class “Referee”.
  • Implement interface \SplObserver for the class.
  • In the method “update” perform any operation that we want to do on score change.
<?php
// Refaree.php

namespace BigBoxCode\DesignPattern\Observer\Player;

class Referee implements \SplObserver {
    public function __construct(private string $code) {

    }

    public function update(\SplSubject $player): void {
        echo "\nPlayer update received in Referee : " . $this->code . "\n";
        echo "Player name: " . $player->name . "\n";
        echo "score: " . $player->score . "\n";
    }
}

Commentator Class [Observer]

  • Create file “Commenentator.php”.
  • Define class “Commentator”.
  • Implement interface \SplObserver for the class, in “update” method perform operations related to score change.
<?php
// Commentator.php

namespace BigBoxCode\DesignPattern\Observer\Player;


class Commentator implements \SplObserver {
    public function __construct(private string $name) {

    }

    public function update(\SplSubject $player): void {
        echo "\nPlayer update received in Commentator : " . $this->name . "\n";
        echo "Player name: " . $player->name . "\n";
        echo "score: " . $player->score . "\n";
    }
}

Scoreboard Class [Observer]

  • Create file “ScoreBoard.php”.
  • Create class “ScoreBoard”.
  • Implement interface \SplObserver for the class, in “update” method perform operations related to score change.
<?php
// ScoreBoard.php

namespace BigBoxCode\DesignPattern\Observer\Player;


class ScoreBoard implements \SplObserver {
    public function update(\SplSubject $player): void {
        echo "\nPlayer update received in Score Board -\n";
        echo "Player name: " . $player->name . "\n";
        echo "score: " . $player->score . "\n";
    }
}

Demo

Create player class. then create some observer classes of type “Referee”, “Commentator”, “ScoreBoard”. Then attach these observer classes to the player.

Now, whenever the player score changes, the subscribers will be notified.

<?php
// demo.php

require __DIR__ . '/../../vendor/autoload.php';

use BigBoxCode\DesignPattern\Observer\Player\Commentator;
use BigBoxCode\DesignPattern\Observer\Player\Player;
use BigBoxCode\DesignPattern\Observer\Player\Referee;
use BigBoxCode\DesignPattern\Observer\Player\ScoreBoard;

// Referees
$referee1 = new Referee("REF #1");
$referee2 = new Referee("REF #2");

// Commentators
$commentator1 = new Commentator("John Doe");

// Score Boards
$scoreBoard = new ScoreBoard();

// Create players and attached observers
$playerA = new Player("Player A");

$playerA->attach($referee1);
$playerA->attach($referee2);
$playerA->attach($commentator1);
$playerA->attach($scoreBoard);

// Create another player and attach observers
$playerB = new Player("Player B");

$playerB->attach($referee1);
// Do not attach Referee #2 for demo purpose
// $playerA->attach($referee2);

$playerB->attach($commentator1);
$playerB->attach($scoreBoard);

// Change/set sccore for the players
echo "\nSet/Change 'Player A' score to - 1\n";
$playerA->setScore(1);

echo "\nSet/Change 'Player A' score to - 5\n";
$playerA->setScore(5);


echo "\nSet/Change 'Player B' score to - 3\n";
$playerB->setScore(3);

echo "\nSet/Change 'Player A' score to - 9\n";
$playerA->setScore(9);

Output

We will get the following output from the above implementation-

Set/Change 'Player A' score to - 1

Player update received in Referee : REF #1
Player name: Player A
score: 1

Player update received in Referee : REF #2
Player name: Player A
score: 1

Player update received in Commentator : John Doe
Player name: Player A
score: 1

Player update received in Score Board -
Player name: Player A
score: 1

---------------------------------------------------------------------

Set/Change 'Player A' score to - 5

Player update received in Referee : REF #1
Player name: Player A
score: 5

Player update received in Referee : REF #2
Player name: Player A
score: 5

Player update received in Commentator : John Doe
Player name: Player A
score: 5

Player update received in Score Board -
Player name: Player A
score: 5

---------------------------------------------------------------------

Set/Change 'Player B' score to - 3

Player update received in Referee : REF #1
Player name: Player B
score: 3

Player update received in Commentator : John Doe
Player name: Player B
score: 3

Player update received in Score Board -
Player name: Player B
score: 3

---------------------------------------------------------------------

Set/Change 'Player A' score to - 9

Player update received in Referee : REF #1
Player name: Player A
score: 9

Player update received in Referee : REF #2
Player name: Player A
score: 9

Player update received in Commentator : John Doe
Player name: Player A
score: 9

Player update received in Score Board -
Player name: Player A
score: 9

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Observer pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.