Scenario

Assuming your application is running smoothly and all of the HeadX (Refers to HeadLink, HeadMeta, HeadScript, HeadStyle and HeadTitle collectively) have been setup. Suddenly, an exception occurs and your ErrorController has been activated.

You need to use the HeadX to render your error page but those HeadX needs to be cleared of all the previous unwanted setup before we can start using them to render the page. Somehow, there is no clear-related method in the HeadX to do this simply. If this situation, or somewhat related, applies to you, maybe the following article is for you.

Some background

You can skip this part actually. But if you are interested in the technical details, you can continue reading. This post is written based on Zend Framework v1.7.4.

Zend_View_Helper_Placeholder_Container_Standalone class

If you were to open up the Zend/View/Helper/HeadX.php, you will find that those classes actually extend Zend_View_Helper_Placeholder_Container_Standalone class (We shall term it as Standalone class from now on).

Zend_View_Helper_Abstract (implements . . .)
 +- Zend_View_Helper_Placeholder_Container_Standalone (implements . . .)
     |- Zend_View_Helper_HeadLink
     |- Zend_View_Helper_HeadMeta
     |- Zend_View_Helper_HeadScript
     |- Zend_View_Helper_HeadStyle
     +- Zend_View_Helper_HeadTitle

In this Standalone class, there are a few protected variables that are of interest to us as follows.

  1. $_container: This variable holds a reference to an instance of Zend_View_Helper_Placeholder_Container_Abstract class, which essentially is an extended ArrayObject class.

    ArrayObject
     +- Zend_View_Helper_Placeholder_Container_Abstract
         +- Zend_View_Helper_Placeholder_Container
    
  2. $_registry: This variable holds a reference to an instance of Zend_View_Helper_Placeholder_Registry class, which is sort of a Singleton using Zend_Registry.
  3. $_regKey: This variable is overridden by each of the respective HeadX classes and is assigned a unique key, in this case, the class name itself. You can open up the individual Zend/View/Helper/HeadX.php to find this key.

I have extracted the relevant code from the Standalone class as follows. As you can see with reference to the above mentioned protected variables, the methods are actually quite simple and the important method here is __construct. It is using a common placeholder registry to obtain a container reference for the respective HeadX class that we are using.

Using HeadLink as an example, you will find $_regKey is assigned with the key ‘Zend_View_Helper_HeadLink’ in Zend/View/Helper/HeadLink.php. Essentially, this key will retrieve a unique common container reference purely used by the HeadLink class.

public function __construct()
{
    $this->setRegistry(Zend_View_Helper_Placeholder_Registry::getRegistry());
    $this->setContainer($this->getRegistry()->getContainer($this->_regKey));
}

public function setRegistry(Zend_View_Helper_Placeholder_Registry $registry)
{
    $this->_registry = $registry;
    return $this;
}

public function getRegistry()
{
    return $this->_registry;
}

public function setContainer(Zend_View_Helper_Placeholder_Container_Abstract $container)
{
    $this->_container = $container;
    return $this;
}

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

Zend_View_Helper_Placeholder_Registry class

From Zend_View_Helper_Placeholder_Registry class, I have extracted the relevant code used by Standalone class constructor. As you can see, the $_regKey in Standalone class is used to retrieve an unique instance of the container in getContainer method. And if container is not created yet, a new instance will be created by createContainer method. The container will be an instance of Zend_View_Helper_Placeholder_Container which extends Zend_View_Helper_Placeholder_Container_Abstract.

protected $_containerClass = 'Zend_View_Helper_Placeholder_Container';

public function getContainer($key)
{
    $key = (string) $key;
    if (isset($this->_items[$key])) {
        return $this->_items[$key];
    }

    $container = $this->createContainer($key);

    return $container;
}

public function createContainer($key, array $value = array())
{
    $key = (string) $key;

    $this->_items[$key] = new $this->_containerClass(array());
    return $this->_items[$key];
}

Zend_View_Helper_Placeholder_Container class

At this point, we now know placeholder registry is the central repository for the containers and Standalone class retrieves the reference from it. We also know that the container is an instance of Zend_View_Helper_Placeholder_Container class, which exposes methods from both it’s _Abstrast parent class and ArrayObject class.

If you were to open up the Zend/View/Helper/HeadX.php files, you will find that operations (such as append, prepend) on the container, returned by getContainer method, is performed using methods exposed by Zend_View_Helper_Placeholder_Container class.

However, inside Zend/View/Helper/HeadTitle.php, you will find that it did not use getContainer method at all and we are able to call set, append and prepend on the instance of Standalone class itself, which does not have those methods. The trick to this is the following __call method in the Standalone class, which indirectly exposes container methods as well.

public function __call($method, $args)
{
    $container = $this->getContainer();
    if (method_exists($container, $method)) {
        $return = call_user_func_array(array($container, $method), $args);
        if ($return === $container) {
            // If the container is returned, we really want the current object
            return $this;
        }
        return $return;
    }

    require_once 'Zend/View/Exception.php';
    throw new Zend_View_Exception('Method "' . $method . '" does not exist');
}

So with the above __call method, calling set, append and prepend methods, which does not exist in Standalone class, will automatically route to the container inside. This will mean that all methods expose by Zend_View_Helper_Placeholder_Container_Abstract and ArrayObject can be invoked on Standalone class itself.

The solution

If you follow the technical stuff above, you will understand that the ArrayObject methods are exposed on the HeadX classes as well. So to clear/reset HeadX, we just need the following line in .phtml file. It’s just that simple. 🙂

$this->headX()->exchangeArray(array())

You might like to read up on a related post on clearing ArrayObject for more information.

As usual, if you have any comments or if there is any better way of doing this, remember to share them with me.