The 装饰器模式是一种设计模式,用于向现有类添加功能而不更改这些现有类。相反,装饰器类将自身包装在另一个类周围,并且通常公开与被装饰类相同的接口。
基本示例:
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'
例如,选项数组。
For Zend_Form_Decorator_PrepareElements
例如,这个放置选项是无用的,因此被忽略,因为它准备了供表单使用的表单元素ViewScript
装饰器,使其成为不接触被装饰元素的渲染内容的装饰器之一。
根据各个装饰器的默认功能,装饰类的内容可以被包装、附加、前置或丢弃or在将内容传递给下一个装饰器之前,会对装饰类执行完全不同的操作,而不直接向内容添加某些内容。考虑这个简单的例子:
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>
现在,当你为 a 设置装饰器时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>
你知道什么?这实际上is所有相应装饰器的默认位置。
但现在最困难的部分来了,我们需要做什么才能得到您想要的结果?为了包裹label
and 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
and 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
and 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 的首席开发人员 Matthew Weier O'Phinney 的文章关于 Zend 表单装饰器的文章