Patterns For PHP
Index Patterns News Forums

Registry

From Patterns For PHP

Contents

Introduction

The Registry is one of those Patterns where once you understand it, it seems so incredibly useful. Think of it as a basket; using a Registry you can add data (both values and objects) to the basket, and retrieve them as required from other parts of an application. Since all such data is handled by a single Registry object, it makes passing data and objects around an application far more simple than passing all such values as individual parameters to a constructor or setup method. In a similar fashion, a Registry can replace the need to have every object you need global access to acting as a Singleton.

The Registry itself can either be passed around as a parameter or it can be turned into a Singleton to enable global access to it from anywhere in an application. Where it is used to replace numerous Singletons, it can replace the need to know each individual class name which often makes it easier to reuse classes in other applications.

Definition

Definition: The Registry Pattern allows the storing and retrieval of data and objects which need to be accessed globally in a single centralised object.

A PHP 5 Example

At a minimum a Registry allows data to be registered and retrieved, often allowing the setting of a label for the value being registered. One of the most common types of data to use a Registry for are objects for which global access from other objects is required.

This simple PHP 5 example implements a private array to store objects and four methods for registering, unregistering, retrieving and checking the existence of objects.

php
class My_Registry {
 
    private $store = array();
 
    public function __construct() {
 
    }
 
    public function register($label, $object) {
        if(!isset($this->store[$label]))
        {
            $this->store[$label] = $object;
        }
    }
 
    public function unregister($label) {
        if(isset($this->store[$label]))
        {
            unset($this->store[$label]);
        }
    }
 
    public function get($label) {
        if(isset($this->store[$label]))
        {
            return $this->store[$label];
        }
        return false;
    }
 
    public function has($label) {
        return isset($this->store[$label]);
    }
 
}

In addition to the traditional methods, as of PHP 5.1 you can take advantage of the full suite of magic methods to allow.

php
class My_Registry {
 
    private $store = array();
 
    public function __construct() {
 
    }
 
    public function __set($label, $object) {
        if(!isset($this->store[$label]))
        {
            $this->store[$label] = $object;
        }
    }
 
    public function __unset($label) {
        if(isset($this->store[$label]))
        {
            unset($this->store[$label]);
        }
    }
 
    public function __get($label) {
        if(isset($this->store[$label]))
        {
            return $this->store[$label];
        }
        return false;
    }
 
    public function __isset($label) {
        if(isset($this->store[$label]))
        {
            return true;
        }
        return false;
    }
 
}

This could then be used as:

php
$reg = new My_Registry();
$reg->myObject = new SomeObject();
if (isset($reg->myObject)) {
    // there is a myObject value
} else {
    // there is not a myObject value
}
$obj = $reg->myObject;
unset($reg->myObject);

Implementing the Registry Pattern

Our Registry class "My_Registry" is actually very simple. It supplies methods for registering and unregistering objects, fetching an object known by a "label" string, and in addition lets us check if any object has been registered under a particular label. All objects which are registered are stored in a simple associative array indexed by the labels set at registration. Using labels is optional - one could also retrieve objects by checking their class type via is_a() although this limits the number of objects that can be registered to one of each type.

With the Registry now setup, we can start using it in other objects. I'll quickly note that unlike the Singleton Pattern examples, the Settings class (below) is no longer a Singleton. By using a Registry we can avoid using multiple Singletons to enable global access to objects. Our example is the same as the one for the Singleton Pattern except we have replaced the use of a Singleton with our new Registry.

php
class DB_Abstraction_MYSQLI {
 
    private $settings = null;
 
    public function __construct($registry) {
        // Settings already exists with imported settings data
        // Fetch pre-existing object using the Registry
        $this->settings = $registry->get('settings');
    }
 
    public function connect() {
        $conn = mysqli_connect(
            $this->settings->get('db.host'),
            $this->settings->get('db.username'),
            $this->settings->get('db.password'),
            $this->settings->get('db.database')
        );
        return $conn;
    }
 
    // ...
 
}

We can use this class as follows, see how we setup the Registry at the start. We register a Settings object which can later be retrieved for use in other objects.

Using the My_Registry class is as simple as:

php
// setup the registry
$registry = new My_Registry();
 
// instantiate an object we need global access to (Settings)
$settings = new Settings();
$settings->import('/app/settings.ini');
 
// register the object for global access
$registry->register('settings', $settings);
 
// create our abstraction object for use (retrieves Settings from My_Registry)
$db = new DB_Abstraction_MYSQLI($registry);

The Singleton Registry

In the example above, we passed the Registry in as a constructor parameter. An alternative method, which removes the need for constructor parameters is to make the My_Registry object a Singleton. As is common with Design Patterns, combining two or more Patterns often leads to even more useful solutions.

The changes required for our My_Registry class to follow the Singleton Pattern is simple. We just add a new My_Registry::getInstance() method to use a new static class property My_Registry::thisInstance, and make the class constructor private.

php
class My_Registry {
 
    private $store = array();
 
    static private $thisInstance = null;
 
    private function __construct() {
 
    }
 
    static public function getInstance() {
        if(self::$thisInstance == null)
        {
            self::$thisInstance = new My_Registry();
        }
        return self::$thisInstance;
    }
 
    public function register($label, $object) {
        if(!isset($this->store[$label]))
        {
            $this->store[$label] = $object;
        }
    }
 
    // ...
 
}

To use the Registry we now utilise its Singleton method:

php
// instantiate the registry using Singleton method
$registry = My_Registry::getInstance();
 
$settings = new Settings();
$settings->import('/app/settings.ini');
 
$registry->register('settings', $settings);
 
// no need to pass Registry as a parameter
$db = new DB_Abstraction_MYSQLI();

The change to our Database Abstraction class is equally simple:

php
class DB_Abstraction_MYSQLI {
 
    private $settings = null;
 
    public function __construct() {
        // get Singleton My_Registry object
        $registry = My_Registry::getInstance();
        $this->settings = $registry->get('settings');
 
   }
 
   // ...
 
}

Disadvantages

Patterns are not perfect in all situations and the Registry pattern is no exception. It helps simplify our source code but there are issues to watch out for. For one thing, the use of a Singleton Registry replaces numerous Singletons but still has a global nature. This is warning enough to be careful using the Registry.

Passing it as a constructor may often be preferable since it restricts access to the Registry to those objects which need it. This may not be possible in all cases however.

The Registry poses another issue when it comes to Unit Testing. When unit testing, a developer may setup arbitrary test data in a Registry which the class being tested will utilise once instantiated as an object. However when we implement a Singleton Registry we end up with a situation where creating new Registry objects is not possible - instead every call to My_Registry::getInstance() will return a reference to the exact same object each time - complete with any previously set data.

There are a few solutions to this problem. One could implement a clear() method to delete all Registry values for each new test, or one could implement an array stack (basically a multidimensional array) with non-test data stored to array key 0, and arbitrary test data stored to subsequent numbered keys while providing a mechanism for switching between each stack member. This sounds horribly complicated, however a simple example of our Registry with a stack follows (borrowed liberally from http://www.phppatterns.com).

php
class My_Registry {
 
    private $store = array(array()); // array[0]=>array[...]
 
    static private $thisInstance = null;
 
    private function __construct() {
 
    }
 
 
    static public function getInstance() {
        if(self::$thisInstance == null)
        {
            self::$thisInstance = new My_Registry();
        }
        return self::$thisInstance;
    }
 
    public function register($label, $object) {
        if(!isset($this->store[0][$label]))
        {
            $this->store[0][$label] = $object;
        }
    }
 
    public function unregister($label) {
        if(isset($this->store[0][$label]))
        {
            unset($this->store[0][$label]);
        }
    }
 
    public function get($label) {
        if(isset($this->store[0][$label]))
        {
            return $this->store[0][$label];
        }
        return false;
    }
 
    public function has($label) {
        return isset($this->store[0][$label];
    }
 
    // Test friendly isolation methods for original data
 
    public function backup() {
        // move $store[0] to $store[1]
        array_unshift($this->store, array());
    }
 
    public function restore() {
        // remove $store[0], restore $store[1] to it pre-backup() position
        array_shift($this->store);
    }
 
}

The main difference in the above is that original data is now stored to My_Registry::store[0] which is the first element in a multidimensional array. This lets us shift original data to another numbered key and back - thus emulating a easy backup and restore ability. These method will ensure that Unit Tests accompanying our source code will not be contaminated by data previously added to a Singleton Registry.

Conclusion

We have now covered the Registry Pattern and it close relative, the Singleton Registry, in some detail. We have seen how this Pattern can help remove the need for many objects to utilise the Singleton Pattern while at the same time enabling us additional control over how to inject objects into other objects as dependencies. The Registry is a common solution for allowing objects to access other objects without needing to pass them all as individual parameters to calling object's constructor method. We have also demonstrated how the Registry can be adapted to facilitate Unit Testing of classes using the Registry without being impacted by data set in prior tests.

Having now covered two methods of handling the inclusion of objects into other objects, the third Pattern remaining to be explored is the ServiceLocator. Another method coming into popular discussion in PHP forums is Dependency Injection which we may cover in the future.

MediaWiki
GNU Free Documentation License 1.2