In Part 1, we are able to split the content into parts and apply decorators on them separately. This can be achieved by using the following classes. I guess the render method of each class pretty much sums up what they do so I will not explain further.
<?php /** * @see Zend_Form_Decorator_Abstract */ require_once 'Zend/Form/Decorator/Abstract.php'; /** * This class will store the content into this instance during rendering. You * can retrieve the stored content using the complementing class * App_Form_Decorator_Retrieve. When adding several instances of this decorator, * you will have to alias it. The alias given will be used as the name option of * the App_Form_Decorator_Retrieve decorator. * * Accepts the following option: * - clear: whether to clear the passed in content by returning an empty content. * * Add the following line to the init() function of the Zend_Form-extended class * and use 'Store' to instantiate this decorator class. * * $this->addPrefixPath('App_Form_Decorator', 'App/Form/Decorator', Zend_Form::DECORATOR); * * @category App * @package App_Form_Decorator */ class App_Form_Decorator_Store extends Zend_Form_Decorator_Abstract { /** * @access protected * @var boolean */ protected $_clear = null; /** * @access protected * @var string */ protected $_content = null; /** * Specifies whether to clear the passed in content after it is stored in * this instance. * * @param boolean $clear True if to clear the passed in content. * @return App_Form_Decorator_Store For method chaining. * @throws App_Form_Decorator_Exception If parameter is not valid. */ public function setClear($clear) { if (!is_bool($clear)) { require_once 'App/Form/Decorator/Exception.php'; throw new App_Form_Decorator_Exception('Clear option is not defined'); } $this->_clear = $clear; return $this; } /** * Returns whether to clear the passed in content after it is stored in * this instance. * * @return boolean True if to clear the passed in content. * @throws App_Form_Decorator_Exception If 'clear' option is not valid. */ public function getClear() { if (is_null($this->_clear)) { if (!is_null($clear = $this->getOption('clear'))) { $this->setClear($clear); $this->removeOption('clear'); } else $this->setClear(true); } return $this->_clear; } /** * Clears content stored in this instance. * * @return App_Form_Decorator_Store For method chaining. */ public function clearContent() { $this->_content = null; return $this; } /** * Returns content stored in this instance. * * @param boolean $clear True if to clear stored content in this instance. * @return string Stored content. */ public function getContent($clear = false) { $content = $this->_content; if (is_null($content)) $content = ''; elseif ($clear) $this->clearContent(); return $content; } /** * Store the passed in content for retrieval. * * @param string $content Passed in content to store. * @return string Depending on the clear option, it is either empty content or as is. */ public function render($content) { // Store the passed in content to this instance. $this->_content = $content; // Clear or return content. return ($this->getClear()) ? '' : $content; } }
<?php /** * @see Zend_Form_Decorator_Abstract */ require_once 'Zend/Form/Decorator/Abstract.php'; /** * This class will retrieve the stored content from complementing class * App_Form_Decorator_Store. When adding several instances of this decorator, * you will have to alias it. The alias is purely used to ensure the instances * will not overwrite each other. To retrieve the stored content, you will need * to pass the alias used in instantiating the App_Form_Decorator_Store * decorator to the 'name' option. * * Accepts the following options: * - name: The alias used when creating the App_Form_Decorator_Store instance. * - remove: Boolean value determining whether to remove the stored content * after retrieval. * - separator: string with which to separate passed in content and retrieved * content. * - placement: whether to append, prepend or replace with the retrieved * content. * * Add the following line to the init() function of the Zend_Form-extended class * and use 'Retrieve' to instantiate this decorator class. * * $this->addPrefixPath('App_Form_Decorator', 'App/Form/Decorator', Zend_Form::DECORATOR); * * @category App * @package App_Form_Decorator */ class App_Form_Decorator_Retrieve extends Zend_Form_Decorator_Abstract { /** * @access protected * @var string */ protected $_name = null; /** * @access protected * @var boolean */ protected $_remove = null; /** * Returns stored content from the App_Form_Decorator_Store instance. * * @param boolean $clear True if to clear stored content after retrieval. * @return string Stored content. * @throws App_Form_Decorator_Exception If wrong decorator instance is retrieved. */ protected function _getContent($clear) { $store = $this->getElement()->getDecorator($this->getName()); if (!$store instanceof App_Form_Decorator_Store) { require_once 'App/Form/Decorator/Exception.php'; throw new App_Form_Decorator_Exception('"' . $this->getName() . '" is not a App_Form_Decorator_Store decorator'); } return $store->getContent($clear); } /** * Specifies the name/alias of the element decorator to retrieve. * * @param string $name Name/alias of the element decorator to retrieve. * @return App_Form_Decorator_Retrieve For method chaining. * @throws App_Form_Decorator_Exception If parameter is not valid. */ public function setName($name) { if (!is_string($name)) { require_once 'App/Form/Decorator/Exception.php'; throw new App_Form_Decorator_Exception('Element decorator name is not defined'); } if (is_numeric($name)) { require_once 'App/Form/Decorator/Exception.php'; throw new App_Form_Decorator_Exception('Element decorator name must be alphanumeric'); } $this->_name = $name; return $this; } /** * Returns name/alias of the element decorator to retrieve. * * @return string Name/alias of the element decorator to retrieve. * @throws App_Form_Decorator_Exception If 'name' option is not valid. */ public function getName() { if (is_null($this->_name)) { if (is_null($name = $this->getOption('name'))) { require_once 'App/Form/Decorator/Exception.php'; throw new App_Form_Decorator_Exception('Element decorator name is not defined in the options'); } $this->setName($name); $this->removeOption('name'); } return $this->_name; } /** * Specifies whether to remove the stored content in * App_Form_Decorator_Store instance after retrieval. * * @param boolean $remove True if remove the stored content after retrieval. * @return App_Form_Decorator_Store For method chaining. * @throws App_Form_Decorator_Exception If parameter is not valid. */ public function setRemove($remove) { if (!is_bool($remove)) { require_once 'App/Form/Decorator/Exception.php'; throw new App_Form_Decorator_Exception('Remove option is not defined'); } $this->_remove = $remove; return $this; } /** * Returns whether to remove the stored content in * App_Form_Decorator_Store instance after retrieval. * * @return boolean True if remove the stored content after retrieval. * @throws App_Form_Decorator_Exception If 'remove' option is not valid. */ public function getRemove() { if (is_null($this->_remove)) { if (!is_null($remove = $this->getOption('remove'))) { $this->setRemove($remove); $this->removeOption('remove'); } else $this->setRemove(true); } return $this->_remove; } /** * Retrieves the stored content. * * @param string $content Passed in content. * @return string Rendered content. */ public function render($content) { switch ($this->getPlacement()) { case self::APPEND: return $content . $this->getSeparator() . $this->_getContent($this->getRemove()); case self::PREPEND: return $this->_getContent($this->getRemove()) . $this->getSeparator() . $content; default: return $this->_getContent($this->getRemove()); } } }
And finally, the App_Form_Decorator_Exception class for completeness.
App/Form/Decorator. You need to add this folder to the include_path similar to what you did for Zend library. Remember to enable autoloading using the Zend_Loader class. Other information can be found in the class remarks.
If there is any optimization or bug, please feel free to point them out so that I can update my copy as well.
Oh… One more thing, you can also used the same concept to create decorator that does formatting, such as indentation, of the rendered content if you haven’t thought of that.