Question

The Standard PHP Library includes what some resources call a reference implementation of the Observer pattern, by way of the SplSubject and SplObserver classes. For the life of me, I can't figure out how these are very useful with no way to pass actual events or any other information along with the notifications:

class MySubject implements SplSubject {
    protected $_observers = [];

    public function attach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        $this->_observers[$id] = $observer;
    }

    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);

        if (isset($this->_observers[$id])) {
            unset($this->_observers[$id]);
        }
    }

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

class MyObserver implements SplObserver {
    public function update(SplSubject $subject) {
        // something happened with $subject, but what
        // was it???
    }
}

$subject = new MySubject();
$observer = new MyObserver();

$subject->attach($observer);
$subject->notify();

It seems like these interfaces are pretty much useless for any real world problem. Can someone enlighten me?


Edit:

Here's my biggest problem with the interface (although there are others):

public function update(SplSubject $subject, Event $event) { /* ... */ }

...nets the following fatal error:

PHP Fatal error:  Declaration of MyObserver::update() must be compatible with SplObserver::update(SplSubject $SplSubject)

Edit #2:

Making the additional parameters optional by giving them defaults prevents the fatal error and provides a way to pass context, making implementations worthwhile. I wasn't previously aware of this, so this pretty much answers my question. The solution is to pass your own event/message data, and check for its existance inside SplObserver::update().

Was it helpful?

Solution 4

You can implement the update method with an optional parameter and still satisfy the SplSubject interface.

class MyObserver implements SplObserver {
    public function update(SplSubject $subject, $eventData = null) {
        if (is_null($eventData))
            // carefull
    }
}

OTHER TIPS

It seems like these interfaces are pretty much useless for any real world problem. Can someone enlighten me?

Interface

While abstract classes let you provide some measure of implementation, interfaces are pure templates. An interface can only define functionality; it can never implement it. An interface is declared with the interface keyword. It can contain properties and method declarations, but not method bodies.

Interface Use case

For example if you want to your project should support different databases . so that you can change your database in future its better to use interfaces that contains property procedures in class file with out altering objects

By itself, interfaces are not very useful because You cannot create instances of interfaces but Interfaces are instrumental in enforcing object oriented design methodologies which in real sense makes your live easier as a programmer because the foremost incentive for object oriented programming is encapsulation (You don't care how a capability is implemented. You, as a programmer, are exposed only to the interface. This is also a good way to watch after the system architecture)

SplSubject & SplObserver

Orthogonality is a virtue , One of objectives as programmers should be to build components that can be altered or moved with minimal impact on other components.

If every change you make to one component necessitates a ripple of changes elsewhere in the codebase, the task of development can quickly become a spiral of bug creation and elimination.

There is no special feature of SplSubject and SplObserver because thy are both interface to implement the Observer Design Pattern.

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. It is mainly used to implement distributed event handling systems

  • The Observer pattern defines an one-to-many dependency between a subject object and any number of observer objects so that when the subject object changes state, all its observer objects are notified and updated automatically.
  • The Observer pattern essentially allows an unlimited number of objects to observe or listen to events in the observed object (or subject) by registering themselves. After observers are registered to an event, the subject will notify them when the event is fired.
  • The subject handles this by storing an observer collection and iterating through it when the event occurs in order to notify each observer.
  • Observer Pattern registers observers with a subject.
  • You might have multiple observers. Subject must keep a list of registered observers and when event occurs it fires (provides notification) all registered observers.
  • Unregister also possible when we do not need any observer.

Example 1. Interest Rate Notification System for a Loan

$loan = new Loan("Mortage", "Citi Bank", 20.5);
$loan->attach(new Online());
$loan->attach(new SMS());
$loan->attach(new Email());

echo "<pre>";
$loan->setIntrest(17.5);

Output

Online    : Post online about modified Intrest rate of : 17.50
Send SMS  : Send SMS to premium subscribers : 17.50
Send Email: Notify mailing list : 17.50

Example 2. Simple User Register Monitor

$users = new Users();

new Audit($users);
new Logger($users);
new Security($users);

$users->addUser("John");
$users->addUser("Smith");
$users->addUser("Admin");

Output

Audit    : Notify Audit about John
Log      : User John Create at Wed, 12 Dec 12 12:36:46 +0100
Audit    : Notify Audit about Smith
Log      : User Smith Create at Wed, 12 Dec 12 12:36:46 +0100
Audit    : Notify Audit about Admin
Log      : User Admin Create at Wed, 12 Dec 12 12:36:46 +0100
Security : Alert trying to create Admin

Advantage of Observer Design Pattern: Main advantage is loose coupling between objects called observer and observable. The subject only know the list of observers it don’t care about how they have their implementation.All the observers are notified by subject in a single event call as Broadcast communication

Disadvantage of Observer Design Pattern:

  • The disadvantage is that the sometime if any problem comes, debugging becomes very difficult because flow of control is implicitly between observers and observable we can predict that now observer is going to fire and if there is chain between observers then debugging become more complex.
  • Another issue is Memory management when dealing with large observers

Common Classes

abstract class Observable implements SplSubject {
    protected $_observers = [];

    public function attach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        $this->_observers[$id] = $observer;
    }

    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);

        if (isset($this->_observers[$id])) {
            unset($this->_observers[$id]);
        }
    }

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



abstract class Observer implements SplObserver {
    private $observer;

    function __construct(SplSubject $observer) {
        $this->observer = $observer;
        $this->observer->attach($this);
    }
}

Load Example Classes

class Loan extends Observable {
    private $bank;
    private $intrest;
    private $name;

    function __construct($name, $bank, $intrest) {
        $this->name = $name;
        $this->bank = $bank;
        $this->intrest = $intrest;
    }

    function setIntrest($intrest) {
        $this->intrest = $intrest;
        $this->notify();
    }

    function getIntrest() {
        return $this->intrest;
    }
}

class Online implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Online    : Post online about modified Intrest rate of : %0.2f\n",$loan->getIntrest());
    }
}

class SMS implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Send SMS  : Send SMS to premium subscribers : %0.2f\n",$loan->getIntrest());
    }
}

class Email implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Send Email: Notify mailing list : %0.2f\n",$loan->getIntrest());
    }
}

User Register Example Classes

class Users extends Observable {
    private $name;

    function addUser($name) {
        $this->name = $name;
        $this->notify();
    }

    function getName() {
        return $this->name;
    }
}
class Audit extends Observer {

    public function update(SplSubject $subject) {
        printf("Audit    : Notify Autify about %s\n", $subject->getName());
    }
}
class Logger extends Observer {

    public function update(SplSubject $subject) {
        printf("Log      : User %s Create at %s\n", $subject->getName(),date(DATE_RFC822));
    }
}
class Security extends Observer {
    public function update(SplSubject $subject) {
        if($subject->getName() == "Admin")
        {
            printf("Security : Alert trying to create Admin\n");
        }
    }
}

It's quite simple: the subject/observer pattern is not useful for an event system.

The observer pattern doesn't lend itself to saying, "This thing was updated by X". Instead it simply says that it was updated. I have actually created a flexible mediator class that could be leveraged for an event system. Depending on your needs, a more rigid API might be helpful, but you can use that as inspiration.

So when is the subject/observer pattern useful?

It's a fairly common pattern when updating a GUI because some object changed. It doesn't really need to know what changed it or why, just that it needs to update. The nature of HTTP doesn't really lend itself to this particular pattern because your PHP code isn't tied directly to the HTML. You have to make a new request to update it.

In short, the Subject/Observer pattern isn't really that useful in PHP. Furthermore, the interface is not that useful because you have use instanceof to get the proper subject type. I'd just write my own interface and not deal with it.

These two interfaces have no magic functionality attached to them, so implementing them does nothing. They're really only used for reference purposes. There are other PHP internal interfaces like this such as SeekableIterator. There is no magic functionality attached to the seek method, and you have to implement it yourself.

There are some PHP internal interfaces and such as Traversable that get special functionality, but this is not the case for SplSubject and SplObserver -- it's essentially just a suggested interface for an implementation of the Observer pattern.

As for what happened, that information is not part of the interface since it's not abstract. It's up to you to implement.

interface Event extends SplSubject {
   public function getEventData();
}

class MyEvent implements Event {
   //MySubject implementation above
   public function getEventData() {
      return "this kind of event happened";
   }
}

You could also ignore the Event interface entirely or just use instanceof checks (ugly) to see what kind of "Subject" is being passed to the method.

As for a real world example, this link provides one, although the use of SplObserver/SplSubject are not strictly necessary; they are just interfaces after all. Essentially, you could have ExceptionHandler subject class and some observers, for example, Mailer. You can use set_exception_handler(array($handler, 'notify')); and any exception that is thrown notifies all observers (e.g. Mailer, which sends an email about the exception that was caught -- you would have to get the exception from some other method/member of ExceptionHandler).

EDIT: I see from the comments that you plan to use another argument to to update to pass the event as a separate object. I guess that's all right, but my suggestion is just to not separate the Subject and Event concepts and give the Subject the ability to either contain event data or be the event data itself. You would have to check that the event object you receive is not null.

As any interface it is useless until you implement it. By implementing those you can have event driven application

Imagine you have an event "applicationStart" you need to run 10 functions on it.

function applicationStart() {
   // Some other logic 
   fnCall1();
   fnCall2();
   fnCall3();
   fnCall4();
   fnCall5();
   fnCall6();
   fnCall7();
   fnCall8();
   fnCall9();
   fnCall10();
   // Some other logic 
}

Now imagine you need to test this function you would trigger dependency on all other 10 functions.

If you use SplSubject/SplObserver:

function applicationStart() {
    // Logic
    $Subject->notify();
    // Logic
}

Now when you test it you just need to make sure that you trigger event. Without execution of other functions.

Plus code looks cleaner as you do not polute it with business logic that does not belong there. And one nice easy place to add triggers

Take a look at https://github.com/thephpleague/event it does the job very well. I think it's the best package today for this purposes. I also doesn't see any value in

public function notify(/* without args */) {

With league/event you will have following. For example I have email list and want to handle events when new email is added to the list.

class EmailList
{
    const EVENT_ADD_SUBSCRIBER = 'email_list.add_subscriber';
    public function __construct($name, $subscribers = [])
    {
        // do your stuff 
        $this->emitter = new Emitter(); 
    }


    /**
     * Adds event listeners to this list
     * @param $event
     * @param $listener
     */
     public function addListener($event, $listener)
     {
         $this->emitter->addListener($event, $listener);
     } 

    /**
     * Adds subscriber to the list
     * @param Subscriber $subscriber
     */
    public function addSubscriber(Subscriber $subscriber)
    {
        // do your stuff 
        $this->emitter->emit(static::EVENT_ADD_SUBSCRIBER, $subscriber);
    }
}

// then in your code
$emailList = new EmailList();
$emailList->addListener(EmailList::EVENT_ADD_SUBSCRIBER, function($eventName, $subscriber) {
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top