Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>The <a href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a> is a design pattern for adding functionality to existing classes without altering those existing classes. In stead, a decorator class wraps itself around another class, and generally exposes the same interface as the decorated class.</p> <p>Basic example:</p> <pre><code>interface Renderable { public function render(); } class HelloWorld implements Renderable { public function render() { return 'Hello world!'; } } class BoldDecorator implements Renderable { protected $_decoratee; public function __construct( Renderable $decoratee ) { $this-&gt;_decoratee = $decoratee; } public function render() { return '&lt;b&gt;' . $this-&gt;_decoratee-&gt;render() . '&lt;/b&gt;'; } } // wrapping (decorating) HelloWorld in a BoldDecorator $decorator = new BoldDecorator( new HelloWorld() ); echo $decorator-&gt;render(); // will output &lt;b&gt;Hello world!&lt;/b&gt; </code></pre> <p>Now, you might be tempted to think that because the <code>Zend_Form_Decorator_*</code> classes are decorators, and have a <code>render</code> method, this automatically means the output of the decorated class' <code>render</code> method will always be wrapped with additional content by the decorator. But on inspection of our basic example above, we can easily see this doesn't necessarily have to be the case at all of course, as illustrated by this additional (albeit fairly useless) example:</p> <pre><code>class DivDecorator implements Renderable { const PREPEND = 'prepend'; const APPEND = 'append'; const WRAP = 'wrap'; protected $_placement; protected $_decoratee; public function __construct( Renderable $decoratee, $placement = self::WRAP ) { $this-&gt;_decoratee = $decoratee; $this-&gt;_placement = $placement; } public function render() { $content = $this-&gt;_decoratee-&gt;render(); switch( $this-&gt;_placement ) { case self::PREPEND: $content = '&lt;div&gt;&lt;/div&gt;' . $content; break; case self::APPEND: $content = $content . '&lt;div&gt;&lt;/div&gt;'; break; case self::WRAP: default: $content = '&lt;div&gt;' . $content . '&lt;/div&gt;'; } return $content; } } // wrapping (decorating) HelloWorld in a BoldDecorator and a DivDecorator (with DivDecorator::APPEND) $decorator = new DivDecorator( new BoldDecorator( new HelloWorld() ), DivDecorator::APPEND ); echo $decorator-&gt;render(); // will output &lt;b&gt;Hello world!&lt;/b&gt;&lt;div&gt;&lt;/div&gt; </code></pre> <p>This is in fact basically how a lot of <code>Zend_Form_Decorator_*</code> decorators work, if it makes sense for them to have this placement functionality.</p> <p>For decorators where it makes sense, you can control the placement with the <code>setOption( 'placement', 'append' )</code> for instance, or by passing the option <code>'placement' =&gt; 'append'</code> to the options array, for instance.</p> <p>For <code>Zend_Form_Decorator_PrepareElements</code>, for instance, this placement option is useless and therefor ignored, as it prepares form elements to be used by a <code>ViewScript</code> decorator, making it one of the decorators that doesn't touch the rendered content of the decorated element.</p> <p>Depending on the default functionality of the individual decorators, either the content of the decorated class is wrapped, appended, prepended, discarded <strong>or</strong> something completely different is done to the decorated class, without adding something directly to the content, before passing along the content to the next decorator. Consider this simple example:</p> <pre><code>class ErrorClassDecorator implements Renderable { protected $_decoratee; public function __construct( Renderable $decoratee ) { $this-&gt;_decoratee = $decoratee; } public function render() { // imagine the following two fictional methods if( $this-&gt;_decoratee-&gt;hasErrors() ) { $this-&gt;_decoratee-&gt;setAttribute( 'class', 'errors' ); } // we didn't touch the rendered content, we just set the css class to 'errors' above return $this-&gt;_decoratee-&gt;render(); } } // wrapping (decorating) HelloWorld in a BoldDecorator and an ErrorClassDecorator $decorator = new ErrorClassDecorator( new BoldDecorator( new HelloWorld() ) ); echo $decorator-&gt;render(); // might output something like &lt;b class="errors"&gt;Hello world!&lt;/b&gt; </code></pre> <p>Now, when you set the decorators for a <code>Zend_Form_Element_*</code> element, they will be wrapped, and consequently executed, in the order in which they are added. So, going by your example:</p> <pre><code>$decorate = array( array('ViewHelper'), array('Description'), array('Errors', array('class'=&gt;'error')), array('Label', array('tag'=&gt;'div', 'separator'=&gt;' ')), array('HtmlTag', array('tag' =&gt; 'li', 'class'=&gt;'element')), ); </code></pre> <p>... basically what happens is the following (actual class names truncated for brevity):</p> <pre><code>$decorator = new HtmlTag( new Label( new Errors( new Description( new ViewHelper() ) ) ) ); echo $decorator-&gt;render(); </code></pre> <p>So, on examining your example output, we should be able to distill the default placement behaviour of the individual decorators:</p> <pre><code>// ViewHelper-&gt;render() &lt;input type="text" name="title" id="title" value=""&gt; // Description-&gt;render() &lt;input type="text" name="title" id="title" value=""&gt; &lt;p class="hint"&gt;No --- way&lt;/p&gt; // placement: append // Errors-&gt;render() &lt;input type="text" name="title" id="title" value=""&gt; &lt;p class="hint"&gt;No --- way&lt;/p&gt; &lt;ul class="error"&gt; // placement: append &lt;li&gt;Value is required and cant be empty&lt;/li&gt; &lt;/ul&gt; // Label-&gt;render() &lt;label for="title" class="required"&gt;Title&lt;/label&gt; // placement: prepend &lt;input type="text" name="title" id="title" value=""&gt; &lt;p class="hint"&gt;No --- way&lt;/p&gt; &lt;ul class="error"&gt; &lt;li&gt;Value is required and cant be empty&lt;/li&gt; &lt;/ul&gt; // HtmlTag-&gt;render() &lt;li class="element"&gt; // placement: wrap &lt;label for="title" class="required"&gt;Title&lt;/label&gt; &lt;input type="text" name="title" id="title" value=""&gt; &lt;p class="hint"&gt;No --- way&lt;/p&gt; &lt;ul class="error"&gt; &lt;li&gt;Value is required and cant be empty&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; </code></pre> <p>And what do you know; this actually <em>is</em> the default placement of all respective decorators.</p> <p>But now comes the hard part, what do we need to do to get the result you are looking for? In order to wrap the <code>label</code> and <code>input</code> we can't simply do this:</p> <pre><code>$decorate = array( array('ViewHelper'), array('Description'), array('Errors', array('class'=&gt;'error')), array('Label', array('tag'=&gt;'div', 'separator'=&gt;' ')), array('HtmlTag', array('tag' =&gt; 'div')), // default placement: wrap array('HtmlTag', array('tag' =&gt; 'li', 'class'=&gt;'element')), ); </code></pre> <p>... as this will wrap all preceding content (<code>ViewHelper</code>, <code>Description</code>, <code>Errors</code> and <code>Label</code>) with a div, right? Not even... the added decorator will be replaced by the next one, as decorators are replaced by a following decorator if it is of the same class. In stead you would have to give it a unique key:</p> <pre><code>$decorate = array( array('ViewHelper'), array('Description'), array('Errors', array('class'=&gt;'error')), array('Label', array('tag'=&gt;'div', 'separator'=&gt;' ')), array(array('divWrapper' =&gt; 'HtmlTag'), array('tag' =&gt; 'div')), // we'll call it divWrapper array('HtmlTag', array('tag' =&gt; 'li', 'class'=&gt;'element')), ); </code></pre> <p>Now, we're still faced with the problem that <code>divWrapper</code> will wrap all preceding content (<code>ViewHelper</code>, <code>Description</code>, <code>Errors</code> and <code>Label</code>). So we need to be creative here. There's numerous ways to achieve what we want. I'll give one example, that probably is the easiest:</p> <pre><code>$decorate = array( array('ViewHelper'), array('Label', array('tag'=&gt;'div', 'separator'=&gt;' ')), // default placement: prepend array(array('divWrapper' =&gt; 'HtmlTag'), array('tag' =&gt; 'div')), // default placement: wrap array('Description'), // default placement: append array('Errors', array('class'=&gt;'error')), // default placement: append array('HtmlTag', array('tag' =&gt; 'li', 'class'=&gt;'element')), // default placement: wrap ); </code></pre> <p>For more explanation about <code>Zend_Form</code> decorators I'd recommend reading Zend Framework's lead developer Matthew Weier O'Phinney's <a href="http://devzone.zend.com/article/3450-Decorators-with-Zend_Form">article about Zend Form Decorators</a></p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload