Zend_Framework Decorators Wrap Label и ViewHelper внутри div

Я новичок в этом, малархия украшений zend, но у меня есть два важных вопроса, которые я не могу опустить. За одним вопросом следует некоторый пример

$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

Как мне обернуть label и input вокруг тега div? Таким образом, выход выглядит следующим образом:

<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? Они НЕ ДАЮТ СЕМЬЮ!

Ответ 1

шаблон декоратора - это шаблон проектирования для добавления функциональности к существующим классам без изменения существующих классов. Вместо этого класс декоратора обертывается вокруг другого класса и обычно предоставляет тот же интерфейс, что и декорированный класс.

Основной пример:

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, делая ее одним из декораторов, которые не касаются визуализированного содержимого украшенный элемент.

В зависимости от функциональности по умолчанию для отдельных декораторов либо содержимое украшенного класса обернуто, добавлено, добавлено, отброшено или, то что-то совершенно другое делается для украшенного класса, без добавления чего-либо непосредственно к контенту, перед тем как перейти по контенту к следующему декоратору. Рассмотрим этот простой пример:

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 ведущего разработчика Matthew Weier O'Phinney статьи о декораторах Zend Form

Ответ 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>

ИЗМЕНИТЬ

Я написал этот ответ, ничего не тестировав, поэтому здесь и там могут быть некоторые неточности. Дорогой читатель, если вы видите, что некоторые просто отбросят комментарий, и я обновлю.