2011-09-25 12 views
10

Soy nuevo en esto, zend decoration malarchy, pero tengo dos preguntas importantes que no puedo entender. Primera pregunta es seguida por un cierto ejemploZend_Framework Decorators Wrap Label y ViewHelper dentro de un div

$decorate = array(
    array('ViewHelper'), 
    array('Description'), 
    array('Errors', array('class'=>'error')), 
    array('Label', array('tag'=>'div', 'separator'=>' ')), 
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')), 
); 

... 

$name = new Zend_Form_Element_Text('title'); 
$name->setLabel('Title') 
    ->setDescription("No --- way"); 

$name->setDecorator($decorate); 

que emite

<li class="element"> 
    <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 can't be empty</li> 
    </ul> 
</li> 

La pregunta # 1

¿Cómo me enrolle el label y la input alrededor de una etiqueta div? Por lo que la salida es la siguiente:

<li class="element"> 
    <div> 
     <label for="title" class="required">Title</label> 
     <input type="text" name="title" id="title" value=""> 
    </div> 
    <p class="hint">No --- way</p> 
    <ul class="error"> 
     <li>Value is required and can't be empty</li> 
    </ul> 
</li> 

La Pregunta # 2

¿Qué pasa con el orden del elements en la matriz $decorate? ¡NO TIENEN SENTIDO!

Respuesta

21

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

+0

Guau, esa es una respuesta completa! Buena esa ! –

+2

Oh wow, tuve un momento ahaa en esa respuesta, Gracias y acepté: D –

+0

P.S esa fue la respuesta más sorprendente que he tenido –

2

Pregunta # 1

Cambiar los decoradores y añada un ayudante HtmlTag esta manera:

$decorate = array(
    array('ViewHelper'), 
    array('Label', array('tag'=>'div', 'separator'=>' ')), 
    array('HtmlTag', array('tag' => 'div')), 
    array('Description'), 
    array('Errors', array('class'=>'error')), 
    array('HtmlTag', array('tag' => 'li', 'class'=>'element')) 
); 

Pregunta # 2

decoradores son una cadena, la salida de cada uno pasa a la entrada del siguiente, para ser 'decorado' por él.

De forma predeterminada, añaden contenido (descripción, errores), anteponen el contenido (etiqueta ...) o envuelven algo (HtmlTag). Pero estos son comportamientos predeterminados, y se puede cambiar durante la mayor parte de ellos:

array('HtmlTag', array('tag' => 'span', placement=>'APPEND')); 
//this would append <span></span> to the output of the previous decorator instead of wrapping it inside the <span> 

Vamos a echar un vistazo más cercano a lo que sucede en su cadena:

  1. ViewHelper renders el elemento de formulario utilizando su viewHelper predeterminado, declarado en la clase del elemento del formulario.

  2. Label antepone la etiqueta a la salida anterior

  3. HtmlTag envuelve un <div> alrededor

  4. Description anexa la descripción elementos

  5. Errors anexa mensajes de error, si lo hay

  6. HtmlTag envuelve todo esto en una edición <li>

escribí esta respuesta sin probar nada, así que podría haber algunos pequeños errores aquí y allá. Estimado lector, si ve algo, simplemente deje caer un comentario y lo actualizaré.

+0

Buena respuesta del desarrollador principal de Zend Framework Mateo Weier O'Phinney así, por supuesto, ! La única advertencia es que uno o quizás ambos (no estoy seguro en realidad) de los decoradores 'HtmlTag' deben agregarse con una única clave' array ('someUniqueKey' => 'HtmlTag') ', de lo contrario, el último reemplazará al precedente . –

+0

wow gracias. ....: D –