我是新手,zend装饰malarchy,但我有两个重要问题,我无法理解。问题一之后是一些例子
$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);
哪个输出
<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>
如何围绕div标签包裹label
和input
?所以输出如下:
<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>
elements
数组中$decorate
的顺序是什么? 他们没有感觉!
答案 0 :(得分:21)
decorator pattern是一种设计模式,用于在不改变现有类的情况下向现有类添加功能。相反,一个装饰器类将自己包装在另一个类周围,并且通常公开与装饰类相同的接口。
基本示例:
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'
传递给options数组。
对于Zend_Form_Decorator_PrepareElements
,例如,这个放置选项是无用的,因此它被忽略,因为它准备了ViewScript
装饰器使用的表单元素,使其成为不接触的装饰器之一装饰元素的渲染内容。
根据各个装饰器的默认功能,装饰类的内容被包装,追加,前置,丢弃或与装饰类完全不同的东西,而不直接添加内容在将内容传递给下一个装饰者之前的内容。考虑这个简单的例子:
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')),
);
...因为这将使用div包装所有前面的内容(ViewHelper
,Description
,Errors
和Label
),对吧?甚至没有......添加的装饰器将被下一个装饰器替换,因为装饰器被以下装饰器替换,如果它是同一个类。相反,你必须给它一个独特的钥匙:
$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的首席开发人员Matthew Weier O'Phinney的article about Zend Form Decorators
答案 1 :(得分:2)
问题#1
更改装饰器顺序并以这种方式添加HtmlTag助手:
$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'))
);
问题#2
装饰器是一个链,每个输出都传递给下一个输入,以便被它“装饰”。
默认情况下,它们附加内容(描述,错误),前置内容(标签..)和/或包装(HtmlTag)。但这些都是默认行为,您可以为大多数行为进行更改:
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>
让我们更仔细地了解您的链中发生的事情:
ViewHelper
使用它在表单元素类中声明的默认viewHelper呈现您的表单元素。
Label
将标签添加到上一个输出
HtmlTag
包裹<div>
Description
附加元素说明
Errors
会附加错误消息(如果有)
HtmlTag
将所有这些内容包含在<li>
修改强>
我在没有测试任何东西的情况下写了这个答案,所以这里和那里可能会有一些不准确之处。亲爱的读者,如果你看到一些只是发表评论,我会更新。