El decorator pattern es un patrón de diseño para agregar funcionalidad a clases existentes sin alterar esas clases existentes. En lugar, una clase de decorador se envuelve alrededor de otra clase, y generalmente expone la misma interfaz que la clase decorada.
Ejemplo básico:
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->_decoratee = $decoratee;
}
public function render()
{
return '<b>' . $this->_decoratee->render() . '</b>';
}
}
// wrapping (decorating) HelloWorld in a BoldDecorator
$decorator = new BoldDecorator(new HelloWorld());
echo $decorator->render();
// will output
<b>Hello world!</b>
Ahora, usted podría estar tentado a pensar que debido a las Zend_Form_Decorator_*
clases son decoradores, y tener un método render
, esto significa automáticamente la salida de la clase decorada render
método será siempre ser envuelto con contenido adicional por el decorador. Pero en la inspección de nuestro ejemplo básico anterior, podemos ver fácilmente que esto no tiene por qué ser el caso en absoluto, por supuesto, como lo ilustra esto (aunque bastante inútil) ejemplo adicional:
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->_decoratee = $decoratee;
$this->_placement = $placement;
}
public function render()
{
$content = $this->_decoratee->render();
switch($this->_placement)
{
case self::PREPEND:
$content = '<div></div>' . $content;
break;
case self::APPEND:
$content = $content . '<div></div>';
break;
case self::WRAP:
default:
$content = '<div>' . $content . '</div>';
}
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->render();
// will output
<b>Hello world!</b><div></div>
Esto está en De hecho, básicamente, cómo funcionan muchos decoradores Zend_Form_Decorator_*
, si tiene sentido que tengan esta funcionalidad de ubicación.
Para decoradores donde tiene sentido, puede controlar la ubicación con el setOption('placement', 'append')
por ejemplo, o pasando la opción 'placement' => 'append'
a la matriz de opciones, por ejemplo.
Para Zend_Form_Decorator_PrepareElements
, por ejemplo, esta opción de colocación es inútil y por lo tanto ignorado, ya que se prepara elementos de formulario para ser utilizados por un decorador ViewScript
, por lo que es uno de los decoradores que no toca el contenido representado del elemento de decoración .
Dependiendo de la funcionalidad predeterminada de los decoradores individuales, ya sea el contenido de la clase decorado se envuelve, anexa, antepuesto, descartados o algo completamente diferente que se hace a la clase decorado, sin añadir algo directamente al contenido, antes de pasar el contenido al siguiente decorador. Considere este ejemplo sencillo:
class ErrorClassDecorator
implements Renderable
{
protected $_decoratee;
public function __construct(Renderable $decoratee)
{
$this->_decoratee = $decoratee;
}
public function render()
{
// imagine the following two fictional methods
if($this->_decoratee->hasErrors())
{
$this->_decoratee->setAttribute('class', 'errors');
}
// we didn't touch the rendered content, we just set the css class to 'errors' above
return $this->_decoratee->render();
}
}
// wrapping (decorating) HelloWorld in a BoldDecorator and an ErrorClassDecorator
$decorator = new ErrorClassDecorator(new BoldDecorator(new HelloWorld()));
echo $decorator->render();
// might output something like
<b class="errors">Hello world!</b>
Ahora, al configurar los decoradores para un elemento Zend_Form_Element_*
, van a ser envueltos y, en consecuencia ejecutados, en el orden en el que se añaden.Por lo tanto, ir por tu ejemplo:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
... básicamente lo que sucede es lo siguiente (los nombres de las clases reales truncados por razones de brevedad):
$decorator = new HtmlTag(new Label(new Errors(new Description(new ViewHelper()))));
echo $decorator->render();
Así, en el examen de su salida de ejemplo, deberíamos capaz de destilar el comportamiento de colocación predeterminado de los decoradores individuales:
// ViewHelper->render()
<input type="text" name="title" id="title" value="">
// Description->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p> // placement: append
// Errors->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error"> // placement: append
<li>Value is required and cant be empty</li>
</ul>
// Label->render()
<label for="title" class="required">Title</label> // placement: prepend
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error">
<li>Value is required and cant be empty</li>
</ul>
// HtmlTag->render()
<li class="element"> // placement: wrap
<label for="title" class="required">Title</label>
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error">
<li>Value is required and cant be empty</li>
</ul>
</li>
Y qué sabe usted; esto realmente es la ubicación predeterminada de todos los decoradores respectivos.
Pero ahora viene la parte difícil, ¿qué tenemos que hacer para obtener el resultado que está buscando? Con el fin de envolver el label
y input
que simplemente no puede hacer esto:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array('HtmlTag', array('tag' => 'div')), // default placement: wrap
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
... ya que esto envuelva todo el contenido anterior (ViewHelper
, Description
, Errors
y Label
) con un div, ¿verdad? Ni siquiera ... el decorador agregado será reemplazado por el siguiente, ya que los decoradores son reemplazados por el siguiente decorador si es de la misma clase. En lugar tendría que darle una clave única:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // we'll call it divWrapper
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
Ahora, todavía estamos enfrentamos al problema de que divWrapper
envolverá todo el contenido anterior (ViewHelper
, Description
, Errors
y Label
). Entonces tenemos que ser creativos aquí. Hay numerosas formas de lograr lo que queremos. Voy a dar un ejemplo, que probablemente es el más fácil:
$decorate = array(
array('ViewHelper'),
array('Label', array('tag'=>'div', 'separator'=>' ')), // default placement: prepend
array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // default placement: wrap
array('Description'), // default placement: append
array('Errors', array('class'=>'error')), // default placement: append
array('HtmlTag', array('tag' => 'li', 'class'=>'element')), // default placement: wrap
);
Para más explicación sobre Zend_Form
decoradores me gustaría recomendar la lectura de article about Zend Form Decorators
Guau, esa es una respuesta completa! Buena esa ! –
Oh wow, tuve un momento ahaa en esa respuesta, Gracias y acepté: D –
P.S esa fue la respuesta más sorprendente que he tenido –