Categories


Loading feed
Loading feed
Loading feed

PHP Patterns: The Observer Pattern


Introduction

The Observer pattern is perhaps most often encountered in traditional graphical user interface desktop applications. It is an excellent way of ensuring that disparate display components reflect changes at a system's core. As we shall see, though, it remains a powerful technique in a Web-oriented environment.

In this article I show you how to use the Observer pattern to build a flexible broadcast-style relationship between a central component and the objects that care about it.

First though, in preparation for a running example, I take a look at object mocking a great technique for test-driving code.

Example Code and an Unexpected Bonus

In this article, as you might expect, I have cooked up an example to illustrate my points. This is always a difficult task. The example needs to be credible enough that a reader might imagine it out there in the real world. On the other hand, one must sacrifice much that would be essential in code deployed in the wild. The first to go is usually error checking. Nobody wants to plough through thickets of try/catch clauses to get at the simple point buried within. The same is true of incidental functionality. In an article about the way that objects and classes interact, the process of talking to a database or of parsing an XML document is often irrelevant. Such functionality can often be replaced with simple print statements ('update db', 'reading XML document').

You might find a disclaimer of this kind in any one of hundreds of technical articles. Tutorial code is geared towards understanding and not deployment. In this case, though, it serves to illustrate a point. That is: interface should outrank implementation in object-oriented programming. In designing your code, you might try creating objects that do nothing at all but print their intended actions. Focus on the classes in your system and their relationships. Then focus on the operations they must define. Get that right, and implementation code will often fall into place naturally.

Another important benefit you can derive from this approach lies in testing. Unit testing itself is a topic for another article. Nevertheless, you should consider strategies for building code that automates class testing. Of all the problems you might encounter in testing a component, arguably the greatest is context. An object rarely lives in isolation, and it may need to work with others that speak to databases, the file system, or HTTP requests. How do you reproduce these conditions in a test? One way is to ensure that an actual HTTP request takes place, and a real database is on hand, stocked up with valid data. This approach saddles you with the overhead of recreating a real world context for the class you are testing, dragging your focus in the wrong direction.

If you're coding to an interface, however, another option opens up. You can create fake objects. If the code that talks to your database is nicely encapsulated behind a clear interface, then there should be nothing to stop you from creating a class that shares the same ancestry as your database class, but serves up hard-coded data for testing purposes.

The example for this article concerns file uploads. A class, UploadManager, works with UploadFile objects. Here is the UploadFile abstract superclass and a simplified implementation:

abstract class UploadFile {

    abstract function
size();
    abstract function
name();
    abstract function
errored();
    abstract function
moveTo($str);
}

class
UploadFileException extends Exception {}

class
UploadFileImpl extends UploadFile {

    private
$formFieldName;
    private
$fsPath = null;

    function
__construct($formFieldName) {

        if (!isset(
$_FILES[$formFieldName])) {
            throw new
UploadFileException("unknown $formFieldName");
        }
        
$this->formFieldName = $formFieldName;
    }

    function
size() {
        return
$_FILES[$this->formFieldName]['size'];
    }

    function
name() {
        return
$_FILES[$this->formFieldName]['name'];
    }

    function
errored() {
        return
$_FILES[$this->formFieldName]['error'];
    }

    function
path() {
        return
$this->fsPath;
    }

    function
moveTo($perm_loc) {

        if (!
is_null($this->fsPath)) {
            return
null;
        }

        if (
is_dir($perm_loc)) {
            
$perm_loc .= DIRECTORY_SEPARATOR.$this->name();
        }

        
move_uploaded_file($_FILES[$this->formFieldName]['tmp_name'], $perm_loc);

        if (
is_file($perm_loc)) {
            
$this->fsPath = realpath($perm_loc);
            return
$this->fsPath;
        }

        return
null;
    }
}

The UploadFileImpl class is a simple wrapper around an element of the $_FILES array which is automatically populated by the PHP engine when an HTTP upload occurs. The class provides information about the uploaded file, and supports moving it from its temporary holding location to a more permanent home on the file system.

This class is simple and compact enough to use in examples, but it suffers from one great drawback when used in tests and demonstrations. It requires a file upload to have taken place. The fact that the UploadFileImpl class extends an abstract superclass provides us with an opportunity. We can create a dummy upload class that extends the same parent. Any client should be able to use this interchangeably with the real thing. Such dummy classes are known as mocks. Here is MockUploadFile:

class MockUploadFile extends UploadFile {

    private
$size = 3000;
    private
$fspath = null;
    private
$name = 'mock.gif';
    private
$errorCode = null;
    private
$moveToResult = true;

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

    function
setSize($size) {
        
$this->size = $size;
    }

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

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

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

    function
setErrorCode($code) {
        
$this->errorCode = $code;
    }

    function
errored() {
        return
$this->errorCode;
    }

    function
path() {
        return
$this->fsPath;
    }

    function
setMoveToFail() {
        
$this->moveToFail = true;
    }

    function
moveTo($path) {

        if (
$this->errored() || !is_null($this->fsPath)) {
            return
null;
        }

        return
$this->fsPath = $path;
    }
}

Notice that this mock class is configurable. We can set key values, we can even cause operations to fail if we want to. Because they can be set up to create error conditions, such classes can act like spies or agents provocateurs in the enemy camp. We can send them in to cause trouble, then sit back and watch the ensuing mayhem unfold.

Figure 1 shows the UploadFile type together with an unsuspecting client class. The client is passed an UploadFile object and remains blissfully unaware of the particular implementation with which it works. This is why it is usually a good idea to pass objects around a system rather than have them instantiated directly by the classes that use them. Of course objects must be instantiated somewhere, and according to some logic. We will look at some of these strategies in a later article.

Figure 1

The Observer Pattern

Described by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) in their seminal catalog, Design Patterns. Elements of Reusable Object-Oriented Software (Addison Wesley, 1995), this pattern is a fantastic example of decoupling in action.

One of the great objectives of the object-oriented programmer is to create orthogonal components. That means high cohesion (each component is bound up with itself and a core purpose) and loose coupling (the components are as independent of their context as possible). This buys us components that can be re-used easily, and perhaps more importantly it can lead to systems that are flexible and less prone to bugs. Where by contrast the wider system is allowed to take root in a component, so too does a web of interdependency. If a component is too cozy with its context, it can't easily be pulled away from it. Furthermore, making a change in one part of a system can lead to unintended consequences elsewhere.

We've all encountered systems that pepper SQL statements across all components. Such promiscuous coding is tempting. It's easier to query a database as and when you need to than to devise a centralized strategy. The wages of sin, however, are toil and error. A simple database schema change may require changes right across the system, with the inevitable risk that an obscure statement or two is missed. Missed, that is, until the morning you present your fantastic new application to a client or an investor.

Decoupling, then, is the process by which you disentangle components from their contexts, and the Observer pattern is one strategy you should have in your decoupling toolbox.

Overview

The Observer pattern defines a mechanism by which components can opt-in to receive messages when a target object (confusingly known as the subject) changes its state. The subject need have no knowledge of the components 'watching' it for events, beyond the fact that they are there and support an interface for notification.

The Problem

Imagine you are building a music community site that lets users upload their work for sharing and assessment. For this purpose you have created the UploadFile classes we have already seen, and now we need to manage the upload. There are some legitimate issues that an UploadManager class should attend to. It should:

  • check that a user hasn't filled up her quota of space
  • invoke UploadFile::moveTo()
  • check for upload errors
  • update the user's disk space information
Here is an implementation:

class UploadManager {

    private
$user;
    private
$repDir;
    private
$status = 0;

    const   
UM_UPLOAD_ERROR = 1;
    const   
UM_QUOTA_ERROR  = 2;

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

    function
processFile(UploadFile $file) {

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

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

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

        
$this->status = 0;
        
$this->user->setStored($this->user->stored()+$file->size());
        return
true;
    }

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

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

The heart of this class is the processFile() method. It checks available quota space, attempts to install the uploaded file, checks for errors, and updates the user's storage information.

Here is a simple User class that works with UploadManager:

class User {

    private
$quota;
    private
$stored = 0;

    function
__construct($quota = 50000) {
        
$this->quota = $quota;
    }

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

    function
available() {
        return (
$this->quota - $this->stored);
    }

    function
setStored($amount) {
        
$this->stored = $amount;
    }

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

As you can see, this class has been cut back to the bone so that it only supports file storage functionality. We can query a User object to find out about its quota, and the amount of storage it has in use and available. UploadManager calls these methods to ensure that the current user has adequate space to store the submitted clip.

Here's some code that puts the classes through their paces:

$user = new User(4000);
$mgr = new UploadManager($user, '/tmp');
$file = new MockUploadFile("test.gif");

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

We create a new User object with a storage quota of 4000 and pass it in to the UploadManager constructor method. Finally we create a file object and invoke UploadManager::processFile(). This sample simply outputs 'OK'. We can make things more interesting, though by poisoning our MockUploadFile object. This sets the file size beyond the user's quota:

//...
$file->setSize(5000); // TOO HIGH!!

if ($mgr->processFile($file)) {
    print
"OK\n";
}
  
// output:
// Fatal error: Uncaught exception
// 'UploadFileException' with message
// 'User quota exceeded' in...

Here we fake a file upload error:

$file->setErrorCode(UPLOAD_ERR_PARTIAL);

if (
$mgr->processFile($file)) {
    print
"OK\n";
}
  
// output: Fatal error: Uncaught exception
// 'UploadFileException' with message
// 'File upload error: 3' in...

So far the core classes in the example have remained reasonably focused upon their own concerns. Perhaps UploadManager might usefully delegate a little more to User, but that is an easy change to make at any time. Coupling is loose, in that no component instantiates another, leaving ample opportunity for class switching behind clear, well defined interfaces. Figure 2 illustrates the usage relationships.

Figure 2

Now let's introduce some flies into the ointment. The business group find more money for the project and extend your brief. From now on, all uploads are to be logged. Notification of failed transactions must be sent by mail to designated personnel. Successful uploads should schedule a message to be sent via Web services to a third party partner, but only while the partnership deal continues.

It is often new features that kill a design. UploadManager is certainly a useful place to hang all these functions, but what does this spell for the system as a whole? Logging might be something that you could expect business objects to know about, but the ever-changing details of corporate partnership details may push things a bit. It is your job to accommodate this logic, but how can you do it flexibly? Here is an amended version of the class's key methods:

   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);

        if (
PARTNERSHIP_DEAL) {
            
$relay = new PartnershipRelay();
            
$relay->inform($file, $user);
        }

        
Logger::instance()->log($file->name().": loaded");

        
$this->status = 0;
        
$this->user->setStored($this->user->stored() + $file->size());
        return
true;
    }

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

        if (!
$condition) {
            
$this->status = $code;
            
Logger::instance()->error($msg, $code);
            
$informer = new Informer();
            
$informer->mailwarning($msg, $code);
            throw new
UploadFileException($msg, $code);
        }
    }

As you can see, the UploadManager class has become mired by its context. It is relying upon a constant, PARTNERSHIP_DEAL, as well as making some direct instantiations. We could fix these problems by having objects passed in, or served up by factory methods (more on these in future articles), but a core issue will remain: UploadManager knows too much. The class has become site specific. We couldn't easily pick it up and drop it into another similar system because it now relies explicitly upon too many classes and methods that are incidental to its core purpose. Figure 3 illustrates the problem.

Figure 3

So, given that we need to inform these objects when an event occurs, how do we remove the direct dependency of UploadManager upon all these classes?

[ continued in part 2 of PHP Patterns: The Observer Pattern ]

Comments


Saturday, September 1, 2007
UML PART OF THE TUTORIAL
5:38PM PDT · vladimirg