ZF-11517: unwanted Label decorator gets added when creating Form from Config object

Description

Here is the scenario :

You create an array describing the form, listing the decorators you want to associate to your element. You create a Zend Config object from this array. You create a Zend Form from the Zend Config object.

At this point, your form element holds the decorators you assigned it, plus a Label decorator.

here's some testcase code :


$options = array('1' => 'answer1',
                 '2' => 'answer2',
                );

$elements['element_3'] = array('type'    => 'radio',
                               'options' => array(
                                   'required'     => true,
                                   'multiOptions' => $options,
                                   'decorators'   => array(
    'ViewHelper',
    'Errors',
    array(array('wrapper' => 'HtmlTag'), array('tag'   => 'div',
                                               'class' => 'content',
                                               )),
    array(array('Fieldset' => 'HtmlTag'), array('tag'      => 'fieldset',
                                                'data-role' => 'controlgroup',
                                                )),
    )
                                                       ),
                                                   );

$elements['formSubmit'] = array('type'    => 'submit',
                                'options' => array('label'      => 'valider',
                                                   'decorators' => array(
                                        'ViewHelper',
                                        'Errors'
                                    )
                                                )
                                );

$configArray = array('elements'          => $elements,'decorators'        => array('FormElements',
                                                  array('HtmlTag', array('tag' => 'div')),
                                                  'Form')
                     );

$config = new \Zend_Config($configArray);
$form   = new \Zend_Form($config);
var_dump($form);die();

I noticed this on the Radio element, it might be the case with other elements too.

You can prevent this behaviour by explicitely setting disableLoadDefaultDecorators to true on your element in the config array/config object.

Comments

Zend_Config removed as affected components

This is the normal behavior:


/**
 * Load default decorators
 *
 * Disables "for" attribute of label if label decorator enabled.
 *
 * @return Zend_Form_Element_Radio
 */
public function loadDefaultDecorators()
{
    if ($this->loadDefaultDecoratorsIsDisabled()) {
        return $this;
    }
    parent::loadDefaultDecorators();
    $this->addDecorator('Label', array('tag' => 'dt',
                                       'disableFor' => true));
    return $this;
}

I see no problem and the solution have you already given.


$elements['element_3'] = array(
    'type'    => 'radio',
    'options' => array(
        'required'     => true,
        'multiOptions' => $options,
        'decorators' => array(
            'ViewHelper',
            'Errors',
            array(
                array(
                    'wrapper' => 'HtmlTag',
                ),
                array(
                    'tag'   => 'div',
                    'class' => 'content',
                ),
            ),
            array(
                array(
                    'Fieldset' => 'HtmlTag',
                ),
                array(
                    'tag'       => 'fieldset',
                    'data-role' => 'controlgroup',
                ),
            ),
        ),
        'disableLoadDefaultDecorators' => true,
    ),
);

This is not the exptected behaviour. When you manually declare a set of decorators on your form element, the usual behaviour is to automatically disable default decorators. For example when you use $element->setDecorators(array(...)), you don't get any default decorators added.

Hi Matthieu, I know what you mean. Here is an example for a fix:


public function loadDefaultDecorators()
{
    if ($this->loadDefaultDecoratorsIsDisabled()) {
        return $this;
    }
    parent::loadDefaultDecorators();
            
    $labelDecorator = $this->getDecorator('Label');        
    if (false !== $labelDecorator) {
        $labelDecorator->setOption('disableFor', true);
    }
    
    return $this;
}

Kai, Thanks for you proposal, I guess it would do the trick. On the other hand, wouldn't it be more appropriate just not to go through the loadDefaultDecorators method when decorators have been defined ?

See the parent method:


/**
 * Load default decorators
 *
 * @return Zend_Form_Element
 */
public function loadDefaultDecorators()
{
    if ($this->loadDefaultDecoratorsIsDisabled()) {
        return $this;
    }

    $decorators = $this->getDecorators();
    if (empty($decorators)) {
        $getId = create_function('$decorator',
                                 'return $decorator->getElement()->getId()
                                         . "-element";');
        $this->addDecorator('ViewHelper')
             ->addDecorator('Errors')
             ->addDecorator('Description', array('tag' => 'p', 'class' => 'description'))
             ->addDecorator('HtmlTag', array('tag' => 'dd',
                                             'id'  => array('callback' => $getId)))
             ->addDecorator('Label', array('tag' => 'dt'));
    }
    return $this;
}

My solution does not work because it breaks a test:


/**
 * @group ZF-9682
 */
public function testCustomLabelDecorator()
{
    $form = new Zend_Form();
    $form->addElementPrefixPath('My_Decorator', dirname(__FILE__) . '/../_files/decorators/', 'decorator');

    $form->addElement($this->element);

    $element = $form->getElement('foo');

    $this->assertType('My_Decorator_Label', $element->getDecorator('Label'));
}

ZF-9682

An unit test:


/**
 * @group ZF-11517
 */
public function testRenderingWithIndividualDecoratorsAsConstructorOptionsWithoutLabel()
{
    $element = new Zend_Form_Element_Radio(array(
        'name'         => 'foo',
        'multiOptions' => array(
            'bar'  => 'Bar',
            'baz'  => 'Baz',
        ),
        'decorators' => array(
            'ViewHelper',
            'Errors',
            array(
                array(
                    'wrapper' => 'HtmlTag',
                ),
                array(
                    'tag' => 'div',
                ),
            ),
            array(
                array(
                    'fieldset' => 'HtmlTag',
                ),
                array(
                    'tag' => 'fieldset',
                ),
            ),
        ),
    ));

    $this->assertFalse($element->getDecorator('Label'));

    $html = $element->render($this->getView());
    $this->assertNotContains(' ', $html);
}

Proposal 1:


/**
 * Load default decorators
 *
 * Disables "for" attribute of label if label decorator enabled.
 *
 * @return Zend_Form_Element_Radio
 */
public function loadDefaultDecorators()
{
    if ($this->loadDefaultDecoratorsIsDisabled()) {
        return $this;
    }

    $decorators = $this->getDecorators();
    if (empty($decorators)) {
        $getId = create_function('$decorator',
                                 'return $decorator->getElement()->getId()
                                         . "-element";');
        $this->addDecorator('ViewHelper')
             ->addDecorator('Errors')
             ->addDecorator('Description', array('tag' => 'p', 'class' => 'description'))
             ->addDecorator('HtmlTag', array('tag' => 'dd',
                                             'id'  => array('callback' => $getId)))
             ->addDecorator('Label', array('tag' => 'dt', 'disableFor' => true));
    }
    return $this;
}

Proposal 2:


/**
 * Load default decorators
 *
 * Disables "for" attribute of label if label decorator enabled.
 *
 * @return Zend_Form_Element_Radio
 */
public function loadDefaultDecorators()
{
    if ($this->loadDefaultDecoratorsIsDisabled()) {
        return $this;
    }

    parent::loadDefaultDecorators();

    if (isset($this->_decorators['Label'])
        && !isset($this->_decorators['Label']['options']['disableFor']))
    {
         $this->_decorators['Label']['options']['disableFor'] = true;
    }

    return $this;
}

Patch and unit tests added.

Patch applied to trunk (25108) and release-1.12 (25109)