Design pattern in PHP: Observer Pattern

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

What problems can it solve?

Please imagine this situation: when you modify one of your class in your application, you have to modify another class maybe more. As you know, one of our goals as programmers should build components that can be altered or moved with minimal or no impact on other components. If every change you make to one component necessitates a ripple of changes elsewhere in the application, the task of programming can quickly become a spiral of bug-creation. We should loose coupling of our every component as far as we can.

Let’s see this code:

<?php


/**
 * logger class
 */
class Logger
{
	public function log($userid)
	{
		echo "{$userid} log in at:" . date('Y-m-d H:i:s') . "\n";
	}
}

/**
 * Access check
 */
class Permission
{
	
	public function check($userid)
	{
		echo "userid:{$userid} is allowed\n";
	}
}

/**
 * email notice
 */
class EmailNotice
{
	public function notice($userid, $ip)
	{
		echo "hello {$userid}: your account just log in at {$ip}\n";
	}
}

/**
 * deal login things
 */
class Login
{

	public $userid;

	public $ip;

	public function handle()
	{
		// log the request
		$logger = new Logger();
		$logger->log($this->userid);

		// permission
		$permission = new Permission();
		$permission->check($this->userid);

		// email notice
		$emailNotice = new EmailNotice();
		$emailNotice->notice($this->userid, $this->ip);
	}
}

In this case, the three class(Logger, EmailNotice, Statistics) and Login class have a hard-coded relationship which is not what we want clearly. And as more action added into logging, handle function will be larger and larger.

What observer pattern can do?

At the core of the Observer pattern is the unhooking of client elements (the observers) from a central class (the subject). When subject changes, all the observers should be notified. Let’s make it clear, we define class Login as subject and the 3 other class as observer. When Login::handle function is called, the 3 other classes have to known or be called too.

Implementation

Here is an example of observer pattern in PHP.

Step 1: Create subject and observer interface

// Subject
interface Subject {
	public function attach(Observer $observer);
	public function detach(Observer $observer);
	public function notify();
}

// Observer
interface Observer {
	public function update(Subject $subject);
}

Step 2: Create the subject class which utilizes subject interface

/**
 * deal login things
 */
class Login implements Subject
{

	public $userid;
	public $ip;

	/**
	 * store observer
	 * @var array
	 */
	protected $_observerStorage = [];


	/**
	 * add an observer
	 * @param  Observer $observer
	 * @return void
	 */
	public function attach(Observer $observer)
	{
		$this->_observerStorage[] = $observer;
	}

	/**
	 * remove an observer
	 * @param  Observer $observer
	 * @return void
	 */
	public function detach(Observer $observer)
	{
		foreach ($this->_observerStorage as $key => $value) {
			if ($observer === $value) {
				unset($this->_observerStorage[$key]);
				break;
			}
		}
	}

	/**
	 * notify every observer
	 * @return void
	 */
	public function notify()
	{
		foreach ($this->_observerStorage as $value) {
			$value->update($this);	
		}
	}

	/**
	 * @return void
	 */
	public function handle()
	{
		$this->notify();

		// do more
	}
}

Step 3: Create above 3 specific Observers

/**
 * logger class
 */
class Logger implements Observer
{
	public function update(Subject $subject)
	{
		echo "{$subject->userid} log in at:" . date('Y-m-d H:i:s') . "\n";
	}
}

/**
 * Access check
 */
class Permission implements Observer
{
	
	public function update(Subject $subject)
	{
		echo "userid:{$subject->userid} is allowed\n";
	}
}

/**
 * email notice
 */
class EmailNotice implements Observer
{
	public function update(Subject $subject)
	{
		echo "hello {$subject->userid}: your account just log in at {$subject->ip}\n";
	}
}

Step 4: create some instances

$login = new Login();
$login->userid = 123456;
$login->ip = '127.0.0.1';

// add observer
$login->attach(new Logger);
$login->attach(new Permission);
$login->attach(new EmailNotice);

$login->handle();

Step 5: run the script and check the result

$ php observercustimized.php
123456 log in at:2020-07-02 09:23:24
userid:123456 is allowed
hello 123456: your account just log in at 127.0.0.1

As observer pattern is so important and frequent-use, PHP has built it into its SPL. SPL is a set of tools that help with common largely object-oriented problems. Click here if you want to know more about SPL. Below is another implementation utilizing SPL and it works well.

<?php

/**
 * deal login things
 */
class Login implements SplSubject
{

	public $userid;
	public $ip;

	/**
	 * store observer
	 * @var SplObserverStorage
	 */
	protected $observerStorage;


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

	/**
	 * add an observer
	 * @param  SplObserver $observer
	 * @return void
	 */
	public function attach(SplObserver $observer)
	{
		$this->observerStorage->attach($observer);
	}

	/**
	 * remove an observer
	 * @param  SplObserver $observer
	 * @return void
	 */
	public function detach(SplObserver $observer)
	{
		$this->observerStorage->detach($observer);
	}

	/**
	 * notify every observer
	 * @return void
	 */
	public function notify()
	{
		foreach ($this->observerStorage as $value) {
			$value->update($this);	
		}
	}

	/**
	 * @return void
	 */
	public function handle()
	{
		$this->notify();

		// do more
	}
}
/**
 * logger class
 */
class Logger implements SplObserver
{
	public function update(SplSubject $subject)
	{
		echo "{$subject->userid} log in at:" . date('Y-m-d H:i:s') . "\n";
	}
}

/**
 * Access check
 */
class Permission implements SplObserver
{
	
	public function update(SplSubject $subject)
	{
		echo "userid:{$subject->userid} is allowed\n";
	}
}

/**
 * email notice
 */
class EmailNotice implements SplObserver
{
	public function update(SplSubject $subject)
	{
		echo "hello {$subject->userid}: your account just log in at {$subject->ip}\n";
	}
}


$login = new Login();
$login->userid = 123456;
$login->ip = '127.0.0.1';

// add observer
$login->attach(new Logger);
$login->attach(new Permission);
$login->attach(new EmailNotice);

$login->handle();

Pros of decorator pattern

  1. The observer and the subject are abstractly coupled.
  2. Establish a trigger mechanism.

Cons of decorator pattern

  1. If a subject has lots of observers, it may take a lot of time to notify all observers.
  2. If there is a circular dependency between observer and subject, the subject will trigger a circular call between them, which may cause the system to crash.
  3. The Observer doesn’t know that how it is triggered.

RSS