Zend框架修饰器在div中包装Label和ViewHelper

时间:2011-09-25 14:28:30

标签: zend-framework zend-form zend-decorators

我是新手,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>

问题#1

如何围绕div标签包裹labelinput?所以输出如下:

<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>

问题#2

elements数组中$decorate的顺序是什么? 他们没有感觉!

2 个答案:

答案 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>

你知道什么;实际上所有相应装饰器的默认位置。

但现在遇到困难的部分,我们需要做些什么才能获得您想要的结果?为了包装labelinput,我们不能简单地执行此操作:

$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包装所有前面的内容(ViewHelperDescriptionErrorsLabel),对吧?甚至没有......添加的装饰器将被下一个装饰器替换,因为装饰器被以下装饰器替换,如果它是同一个类。相反,你必须给它一个独特的钥匙:

$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将包含所有先前内容(ViewHelperDescriptionErrorsLabel)的问题。所以我们需要在这里发挥创意。有很多方法可以达到我们想要的效果。我举一个例子,这可能是最简单的:

$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>

让我们更仔细地了解您的链中发生的事情:

  1. ViewHelper使用它在表单元素类中声明的默认viewHelper呈现您的表单元素。

  2. Label将标签添加到上一个输出

  3. HtmlTag包裹<div>

  4. Description附加元素说明

  5. Errors会附加错误消息(如果有)

  6. HtmlTag将所有这些内容包含在<li>

  7. 修改

    我在没有测试任何东西的情况下写了这个答案,所以这里和那里可能会有一些不准确之处。亲爱的读者,如果你看到一些只是发表评论,我会更新。