PHP Patterns: The Observer Pattern (continued)

[ continued from part 1 of PHP Patterns: The Observer Pattern ]


Implementation

The Observer pattern removes the dependence of a subject class upon the components
with which it must communicate. It does this by reversing the dependency relationship.
The interested objects must opt in in order to receive notifications. They only
guarantee that part of their interface concerned with receiving messages from the
subject. This means that the subject (UploadManager in this case) needs
no specialized knowledge of its observers. It must store references to them, and call
an update() method on each one, but this is as far as the relationship
need go it’s a relationship of quantity rather than quality.

In our example, our UploadManager class was inappropriately intimate
with three other classes: Logger, Informer and
PartnershipRelay. To turn these into Observer classes, we either need
to have them extend a common superclass or implement a common interface. The latter
option gives us much more flexibility. It guarantees type and functionality without
preventing the classes from subclassing different parents.

An observer interface is typically very simple:


interface UploadObserver {

    function update(UploadManager $man, $message);
}

In this case, our Observer classes must implement an update() method. The subject
passes a reference to itself along to update() together with a $message
string. The observer class can use this reference to call methods on the subject in order
to acquire further state information. Here’s a dummy implementation of
UploadObserver:


class PartnershipRelay implements UploadObserver {

    function update(UploadManager $obs, $msg) {

        if (!$obs->status()) {
            print
__CLASS__.": informing partner: error code: {$obs->status()}\n";
        }
    }
}

Notice the way that the update() method calls back to the
UploadManager class, invoking its status() method.

So how is an Observer class invoked? The subject stores references to any interested
observers and cycles through the list, calling update()
UploadManager modified to act as a subject for observers:


class UploadManager {

    private $user;
    private
$repDir;
    private
$status = 0;
    private
$observers = array();

    const   UM_UPLOAD_ERROR = 1;
    const   
UM_QUOTA_ERROR  = 2;

    function __construct(User $user, $repository) {
        
$this->user = $user;
        
$this->repDir = $repository;
    }

    function attach(UploadObserver $obs) {
        
$this->observers["$obs"] = $obs;
    }

    function detach(UploadObserver $obs) {
        
delete($this->observers["$obs"]);
    }

    protected function notify($message) {

        foreach ($this->observers as $obs) {
            
$obs->update($this, $message);
        }
    }

    function processFile(UploadFile $file) {

        $file->moveTo($this->repDir);

        $this->assert(($file->size() < $this->user->available()), 'User quota exceeded', self::UM_QUOTA_ERROR);

        $this->assert((!$errorcode = $file->errored()), "File upload error: $errorcode", self::UM_UPLOAD_ERROR);

        $this->status = 0;
        
$this->notify($this, "successful upload");
        
$this->user->setStored($this->user->stored()+$file->size());
        return
true;
    }

    function assert($condition, $msg, $code) {

        if (!$condition) {
            
$this->status = $code;
            
$this->notify($this, "$msg ($code)");
            throw new
UploadFileException($msg, $code);
        }
    }

    function status() {
        return
$this->status;
    }
}

The key methods to look at here are attach(), detach(),
and notify(). attach() and detach() handle
adding and removing observers. We use a little trick here. Objects quoted in string
context resolve to a unique identifier (even if __toString() is defined).
You can use this fact to build keys for an associative array. The notify()
method cycles through all attached observers, calling update() on each. The
UploadManager class calls notify() whenever it has something
important to report on upload and on error, in this case.

So we have lost no functionality here in fact we have gained the opportunity to add
lots more functionality without the need to amend the UploadManager class
to accommodate it. UploadManager no longer has any specific knowledge of
its observers apart from the fact that they implement UploadObserver and
therefore have callable update() methods. Figure 4 illustrates the clean up.

Figure 4

Figure 4 may look complicated at first glance, but the relationships are really very
simple. We might add an Informer object to an UploadManager
using attach(). Then, when a user uploads a music file,
UploadManager invokes notify(). notify()
cycles through all UploadObserver objects in its list, including, of
course, our Informer object. It calls update() on each one,
passing on a reference to itself and a message string. Informer::update()
needs more information about the UploadManager object’s state, so it uses
the provided reference to call status(). If an error status is found,
Informer knows to send a warning mail to an administrator.

In design terms, this is a radical change. UploadManager now deals only
with the UploadObserver type, whereas before it had to know about many
different classes, each with subtle differences. It is thus decoupled from these classes,
boosting reusability. You can also add new observers at runtime, which makes the class
extensible.

As before, let’s run our classes through their paces:


try {
    
$user = new User(4000);
    
$mgr = new UploadManager($user, '/tmp');

    $mgr->attach(new Informer());
    
$mgr->attach(new Logger());
    
$mgr->attach(new PartnershipRelay());

    $file = new MockUploadFile("test.gif");
    
//$file->setErrorCode(UPLOAD_ERR_PARTIAL);
    //$file->setSize(5000); // TOO HIGH!!

    if ($mgr->processFile($file)) {
         print
"OK\n";
    }

} catch (Exception $e) {
    print
"error: ".$e->getMessage()."\n";
}

This outputs the following:

Logger: logging: Object id #2 / code: 0
PartnershipRelay: informing partner: error code: 0
OK

If we uncomment one of the mischievous sabotage lines however, the output looks something
like this:

Informer: mailing warning: Object id #2 / code: 1
Logger: logging: Object id #2 / code: 1
error: File upload error: 3

The observer objects responding to the subject’s state, rather than any
decision-making in the subject, determine the difference in behavior.


Discussion

The Observer pattern is not without its variations and issues.

One issue to address is the extent to which you generalize observer and observed
behavior. It is tempting to create interfaces that define observer functionality at the
most general possible level. You might create Observer and Observed
interfaces, for example:


interface Observer {

    function update(Observed $observed, $msg);
}

interface Observed {

    function attach(Observer $observer);

    function detach(Observer $observer);

    function notify();
}

This is a satisfying approach, and avoids the iffy smell conjured by multiple separate
and repetitious Observer implementations. It can mean extra work, however. The
Observer::update() method can only require a Observed object.
When the time comes to implement update() the signature may be of limited
use, because all you’re guaranteed in the Observed object are
attach(), detach() and notify() methods. These
won’t help you query the subject’s state. You will need to use the instanceof
operator to test the type of the $observed argument, only acting on the
call if the right Observed object has been provided and the expected state
methods are therefore present. By creating a specific observer implementation for
UploadManager, on the other hand, I was able to do all my type checking in the
UploadObserver::update() method.

In some circumstances, of course, the generic approach could work for you. You may
create observers that behave in different ways according to the Observed
subtype they are passed
for example. Certainly, a Logger class will want to talk to instances of
many different classes in a system. Ultimately the needs of your system will determine
how generic you should make your Observer classes.

In this article, I have used a ‘pull’ (GOF) approach. This is neat, because you don’t
need to think too hard ahead of time about the information you pass to your observer
objects. Instead you just pass them an instance of the observed class and let them call
state methods to find out where things stand. An alternative is to use a ‘push’ method,
in which you provide more information in the call to update(). The
disadvantage of this is that you end up passing round information that isn’t always used,
and you need to be clear
ahead of time as to which information is relevant. Changing the argument list to
accommodate the needs of a new observer could involve adjustments to existing classes. On
the other hand, the pull model has its own problems. In order to allow observers to glean
useful state information, you may find yourself exposing some of the subject class’ guts
that you would otherwise have kept private.

Another variation on this pattern centers on the notify() method. I have
had the subject class
call this when anything of import occurs. However you can also expose the method, and
allow client classes to call it after having worked with the subject. This can be useful
in cases where many small changes of state take place. A client class calling methods on
the subject can wait until it has finished a block of work before calling
notify(), just as a user might wait until she has finished a set of changes
before invoking commit on a version control system. Unfortunately, by relinquishing
responsibility for calling notify(), you lose any guarantee that the method
is called at all. This is a
serious drawback: we can no longer ensure that all components that register will receive
the updates they expect. The nature of your application should be your guide here,
although on the whole I would favor making the subject call its own notify()
method.


Typical Pattern Characteristics

The Observer pattern
illustrates some core pattern objectives and principles. It is driven by the need to
divorce a component from too intimate a knowledge of its peers. In decoupling the
component from context you make your code less error prone and more extensible.

By moving interested components to implement a common interface, we confirm the primacy
of interface over implementation. The subject class uses the observer interface as a
means of broadcasting its updates, no longer concerned with individual subtypes and their
various strategies for communication.

We can also see the importance of composition in this pattern. The subject class
aggregates observers, and in so doing it provides a mechanism by which very different
effects can be generated at runtime. Behind the observer interface we can create new
observer implementations and drop them into place without changing the subject itself.
Many of the best patterns share this characteristic favoring flexibility through
composition at runtime over static, hard-coded functionality.


About the Author

Matt Zandstra is a
developer, teacher and writer specializing in object-oriented Internet applications. He
currently works as an engineer at Yahoo! where he helps develop a core template
management system. He is the author of SAMS Teach Yourself PHP in 24 Hours, and the
recently released PHP 5: Objects, Patterns and Practice ( "http://www.amazon.com/exec/obidos/tg/detail/-/1590593804"
target="_blank">http://www.amazon.com/exec/obidos/tg/detail/-/1590593804
). He has
written articles on PHP for Linux Magazine, IBM, Zend.com and at bgz-consultants.com.

Published: February 25th, 2006 at 9:12
Categories: Uncategorized
Tags: , , , , ,

3 comments to “PHP Patterns: The Observer Pattern (continued)”

you define the notify method as

protected function notify($message) {

but then call it like so

$this->notify($this, "successful upload");

why are you passing two arguments here? it fails for me on 5.2.1. but then, so does the "$obj" trick.

You could as well have relied on SplSubject and SplObserver

The "$obs" trick described above does not work for PHP 5.2+, but spl_object_hash( $obs ) does.