Tuesday 15 March 2011

Singletons and the Zend Registry

Preface

Hi all, I know it's been an eternity since I have last posted on my blog, but life sometimes has a habit of getting in the way. I am going to be writing a series of posts on optimising code within the Zend Framework starting, rather illogically I guess with using the singleton design pattern and the Zend Registry system.

Singleton patterns?

First of all, what are singleton patterns and why do they help you write more efficient code? Basically a singleton is a class that can only ever have one instance in memory at one time, or in object-orientated programming parlance, only allows one object to be instantiated at a time. Generally singelton patterns are useful where an object is used to co-ordinate many parts of a system (from Singleton pattern on wikipedia). In the context of optimising our PHP code, it can be very useful to ensure that a class that is expensive, in terms of CPU cycles or memory to run is only run once and the instance is stored somewhere until it is needed.

Creating singletons

Good examples of a case where using the singleton pattern is advantageous, are classes that perform multiple database queries on instantiation. Examples where I have come across this are classes that dynamically generate Zend Navigation objects from the database and cache objects. Let's look at one of these two examples in more detail.

class productNavigation
{
    // Holds the Zend_Navigation object
    protected $_container;

    public function __construct()
    {
        $model = new Models_Some_Model();
        $items = $model->getAll();

        // Instantiate a Zend_Navigation
        $this->_container = new Zend_Navigation();

        if($items)
        {
            // Loop through items and add to container
            foreach($items as $item)
            {
                $this->getContainer()>addPage($item);
            }
        }
    }

    // Getter function
    public function getContainer()
    {
        return $this->_container;
    }
}


In this rather silly example you can see that every time we need to get the navigation container, for instance when building breadcrumbs, a sitemap or a menu system the code has to perform a number of database queries, create a new Zend Navigation object and add pages to it. A better way of doing it would be to instantiate the object once and store it. To do this we have two methods, the 'traditional' way and using the Zend Registry.

The traditional way to create a singleton class is to use static methods. Declare the constructor as private (or protected) to prevent the object being instantiated normally and create a public static method to retrieve an instance of the object, creating it if it does not already exist.

class productNavigation
{
    // Holds the Zend_Navigation object
    protected $_container;
    // Holds the instance
    private static $instance;

    // Set to be private to stop normal instantiation
    private function __construct()
    {
        $model = new Models_Some_Model();
        $items = $model->getAll();

        // Instantiate a Zend_Navigation
        $this->_container = new Zend_Navigation();

        if($items)
        {
            // Loop through items and add to container
            foreach($items as $item)
            {
                $this->getContainer()>addPage($item);
            }
        }
    }

    // Getter function
    public function getContainer()
    {
        return $this->_container;
    }
    
    //  Singleton method
    public static function get()
    {
        if(!isset(self::$instance))
        {
            // Create new instance if it is not already instantiated
            self::$instance = new productNavigation();
        }
        
        return self::$instance;
    }
}

Using Zend_Registry

The second way is by using the Zend Registry. The Zend Registry is basically a store of objects that can be retrieved at will by using an associated key. In this example you would not need to modify the class in any way, simply create and store an instance in your bootstrap or using a controller plugin and retrieve the instance as and when needed as the following example rather crudely demonstrates.

// Bootstrap code
$navigation = new productNavigation();
Zend_Registry::set('navigation', $navigation);

// In a controller, for example
$navigation = Zend_Registry::get('navigation')->getContainer();


There are other strategies for doing all of this, but these are the most common and can help streamline your bloated code. In another post I will describe processes for identifying areas of your code that are creating bottlenecks.