decorator pattern è uno schema di progettazione per l'aggiunta di funzionalità a classi esistenti senza alterare quelle classi esistenti. Invece, una classe decoratrice si avvolge attorno ad un'altra classe e generalmente espone la stessa interfaccia della classe decorata.
Esempio di base:
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>
Ora, si potrebbe essere tentati di pensare che, poiché i Zend_Form_Decorator_*
classi sono decoratori, e avere un metodo render
, questo significa automaticamente l'uscita della classe decorato render
metodo sarà sempre essere avvolto con contenuti aggiuntivi dal decoratore. Ma su ispezione del nostro esempio di base di cui sopra, si può facilmente vedere questo non deve necessariamente essere il caso a tutti naturalmente, come illustrato da questo ulteriore (ancorché alquanto inutile) Esempio:
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>
Questo è in Infatti, in pratica, funzionano un sacco di decoratori Zend_Form_Decorator_*
, se ha senso che abbiano questa funzionalità di posizionamento.
Per i decoratori in cui ha senso, è possibile controllare il posizionamento con setOption('placement', 'append')
, ad esempio, oppure passare l'opzione 'placement' => 'append'
all'array delle opzioni, ad esempio.
Per Zend_Form_Decorator_PrepareElements
, per esempio, questa opzione di posizionamento è inutile e per questo ignorato, che si prepara elementi del modulo per essere utilizzati da un ViewScript
decoratore, che lo rende uno dei decoratori che non tocca il contenuto reso dell'elemento decorato .
seconda della funzionalità predefinita dei singoli decoratori, né il contenuto della classe decorato è avvolto, aggiunto, anteporre, scartati o qualcosa di completamente diverso viene fatto alla classe decorato, senza aggiungere qualcosa direttamente al contenuto, prima di passare il contenuto al prossimo decoratore. Considerate questo semplice esempio:
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>
Ora, quando si impostano i decoratori per un elemento Zend_Form_Element_*
, saranno avvolti, e di conseguenza eseguiti, nell'ordine in cui vengono aggiunti.Così, andando con il vostro esempio:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
... in fondo ciò che accade è la seguente (nomi di classe attuale troncati per brevità):
$decorator = new HtmlTag(new Label(new Errors(new Description(new ViewHelper()))));
echo $decorator->render();
Così, per l'esame di uscita ad esempio, dovremmo essere in grado di distillare il comportamento di posizionamento predefinito dei singoli decoratori:
// 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>
E che ne sai; questo in realtà è il posizionamento predefinito di tutti i rispettivi decoratori.
Ma ora arriva la parte difficile, cosa dobbiamo fare per ottenere il risultato che stai cercando? Al fine di avvolgere il label
e input
non possiamo semplicemente fare questo:
$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')),
);
... come questo avvolgere tutto il contenuto precedente (ViewHelper
, Description
, Errors
e Label
) con un div, giusto? Nemmeno ... l'addetto alla decorazione aggiunto sarà sostituito dal successivo, poiché i decoratori sono sostituiti da un decoratore successivo se è della stessa classe. In vece si dovrà dare una chiave univoca:
$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')),
);
Ora, siamo ancora di fronte al problema che divWrapper
lo avvolgerà tutti precedente contenuto (ViewHelper
, Description
, Errors
e Label
). Quindi dobbiamo essere creativi qui. Esistono numerosi modi per ottenere ciò che vogliamo. Ti do un esempio, che probabilmente è il più facile:
$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
);
Per ulteriori spiegazioni su Zend_Form
decoratori Mi raccomando di leggere article about Zend Form Decorators
Wow, questa è una risposta completa! Ben fatto ! –
Oh wow, ho avuto un momento ahaa in quella risposta, Grazie e accettato: D –
P.S è stata la risposta più sorprendente che abbia mai avuto –