Шаблон декоратора является шаблоном проектирования для добавления функциональности к существующим классам без изменения этих существующих классов.Вместо этого, класс декоратора оборачивается вокруг другого класса и, как правило, предоставляет тот же интерфейс, что и декорированный класс.
Базовый пример:
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>
Теперь вы можете подумать, чтопоскольку классы Zend_Form_Decorator_*
являются декораторами и имеют метод render
, это автоматически означает, что выходные данные метода декорированного класса 'render
всегда будут упакованы дополнительным содержимым декоратором.Но при рассмотрении нашего основного примера, приведенного выше, мы можем легко увидеть, что это совсем не обязательно должно быть так, как показано на этом дополнительном (хотя и довольно бесполезном) примере:
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>
Этона самом деле именно так работают многие Zend_Form_Decorator_*
декораторы, если для них имеет смысл иметь такую функциональность размещения.
Для декораторов, где это имеет смысл, вы можете управлять размещением с помощью setOption( 'placement', 'append' )
длянапример, или путем передачи опции 'placement' => 'append'
в массив опций, например.
Например, для Zend_Form_Decorator_PrepareElements
эта опция размещения бесполезна и поэтому игнорируется, так как она подготавливает элементы формы для использованияViewScript
декоратор, что делает его одним из декораторов, которые не касаются визуализированного содержимого декорированного элемента.
В зависимости от функциональности по умолчанию отдельных декораторов, либо содержимое декорированного класса переносится, добавляется, добавляется, отбрасывается или что-то совершенно другое делается сecorated класс, без добавления чего-либо непосредственно к контенту, перед передачей контента следующему декоратору.Рассмотрим простой пример:
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>
Теперь, когда вы установите декораторы для элемента Zend_Form_Element_*
, они будут упакованы и, следовательно, выполнены в том порядке, в котором они были добавлены.Итак, в соответствии с вашим примером:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
... в основном происходит следующее (фактические имена классов сокращены для краткости):
$decorator = new HtmlTag( new Label( new Errors( new Description( new ViewHelper() ) ) ) );
echo $decorator->render();
Итак, при проверке выходных данных вашего примерамы должны иметь возможность определить поведение размещения отдельных декораторов по умолчанию:
// 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>
А что вы знаете;на самом деле - это размещение по умолчанию всех соответствующих декораторов.
Но теперь самое сложное, что нам нужно сделать, чтобы получить результат, который вы ищете?Чтобы обернуть label
и input
, мы не можем просто сделать это:
$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')),
);
... так как это обернет весь предыдущий контент (ViewHelper
, Description
, Errors
и Label
) с div, верно?Даже не ... добавленный декоратор будет заменен следующим, так как декораторы заменяются следующим декоратором, если он того же класса.Вместо этого вам нужно было бы дать ему уникальный ключ:
$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')),
);
Теперь мы все еще сталкиваемся с проблемой, что divWrapper
обернет все предыдущее содержимое (ViewHelper
, Description
, Errors
и Label
).Таким образом, мы должны быть креативными здесь.Есть множество способов достичь того, чего мы хотим.Я приведу один пример, который, вероятно, является самым простым:
$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
);
Для более подробного объяснения о Zend_Form
декораторах я бы рекомендовал прочитать статью ведущего разработчика Zend Framework Мэтью Вейера О'Пинни о ZendФорма декораторов