문제

I've created a new form element class for a special, complex purpose (text input field with an add-on button to open a "search wizard" popup).

To render this element properly, I've also created a form view helper. Everything works and is fine so far.

However, if I try to render the form using the FormCollection view helper, the element is rendered as a basic input element. That's because the FormElement view helper, which the FormCollection helper relies on, uses a hard-coded series of if clauses to map the element's type to a specific form view helper. It can't map my element's class and thus falls back to FormInput.

I.e. (taken from Zend/Form/View/Helper/FormElement.php, line 41-49):

    if ($element instanceof Element\Button) {
        $helper = $renderer->plugin('form_button');
        return $helper($element);
    }

    if ($element instanceof Element\Captcha) {
        $helper = $renderer->plugin('form_captcha');
        return $helper($element);
    }

    ...

    $helper = $renderer->plugin('form_input');
    return $helper($element);

and so on.

I've got a little stuck here because this architecture doesn't really promote extensibility.

The only solution that came to my mind (except rendering the form by hand) is to extend the FormElement view helper class and thus create my own CustomFormElement view helper. However, because of its complexity, I've put the custom element into an own module. So I'd have to write this CustomFormElement helper dynamically to add custom elements from any module. I don't think this is a recommended procedure.

Is there another solution or is maybe even my complete approach unrecommended? Thanks in advance!

도움이 되었습니까?

해결책

Seems like we're both running into Form issues with Zend. I think that it could be better integrated with the whole MVC structure.

I think that your approach is sound. What I might think of doing is the following

  1. Give your elements a variable named helper like in ZF1.
  2. Create the custom form element renderer that will ALSO check the renderer attribute of a form element to decide on how to render it.

You could re-use the ViewHelperProviderInterface or create your own interface:

class CustomElement implements ViewHelperProviderInterface
{
     public function getViewHelperConfig()
     {
          return array('type' => '\My\View\Helper');
     }
}

or

class CustomElement implements FormHelperProviderInterface
{
     public function getFormHelperConfig()
     {
          return '\My\View\Helper';
          // or
          return new My\View\Helper();
     }
}

Then in your FormElement class you can do the following:

    if ('week' == $type) {
        $helper = $renderer->plugin('form_week');
        return $helper($element);
    }

    if ($element instanceof THEINTERFACE) {
          return $renderer->plugin($element->getFormHelperConfig());
    }

    $helper = $renderer->plugin('form_input');
    return $helper($element);

This is probably what you had in mind anyway.

You'd probably be better off creating your own interface since the first one already has some sort of meaning behind it and it might confuse someone.

Aside from that, each module would then ONLY have to provide a helper_map key in the module configuration to have it's view helpers available during rendering with the MVC components.

다른 팁

I think the simplest way is to extend Zend\Form\View\Helper\FormElement, handle your field types in your render() method and register your FormElement as default FormElement for your application/module. Assuming that you have your custom TestField that you would like to render:

namespace Application\Form\View\Helper; 

use \Zend\Form\ElementInterface;
use \Zend\Form\View\Helper\FormElement
use \Application\Form\Element\TestField;

class MyFormElement extends FormElement
{
    public function render(ElementInterface $element)
    {
        $renderer = $this->getView();
        if (!method_exists($renderer, 'plugin')) {
            // Bail early if renderer is not pluggable
            return '';
        }

        //your custom fields go here...
        if ($element instanceof TestField) {
            $helper = $renderer->plugin('\Application\Form\View\Helper\FormTestField');
            return $helper($element);
        }

        return parent::render($element);
    }
}

And in Application/config/module.config.php:

'view_helpers' => array(
    'invokables' => array(
         'form_element' => 'Application\Form\View\Helper\MyFormElement',
    )
)

Get your hands on the FormElement view helper any way you can and addType to overwrite the view helper used. i.e. in view, just before you render your form:

<?php $this->plugin('FormElement')->addType('text', 'formcustom'); ?> 

This will overwrite the view helper used in the FormRow,FormCollection helpers using your view helper by the key name:

in your config

'view_helpers' => array(
    'invokables' => array(
        'formcustom' => 'Application\Form\View\Helper\FormCustom',
    )
),

When this question was asked the method may not have been there. But it is now.

The following is what I've done and feels like the right level of keeping things separate and neat.

Given:

  • A new element: MyModule\Form\MyElement which extends Zend\Form\Element
  • A new view helper class for MyElement: MyModule\Form\View\Helper\FormMyElement which extends Zend\Form\View\Helper\AbstractHelper

Here's how you register your view helper to be used to render your element by adding the following to module.config.php:

'view_helpers' => array(
    'invokables'=> array(
        'formMyElement' => 'MyModule\Form\View\Helper\FormMyElement',
    ),
    'factories' => array(
        'formElement' => function($sm) {
            $helper = new \Zend\Form\View\Helper\FormElement();
            $helper->addClass('MyModule\Form\MyElement', 'formMyElement');
            return $helper;
        }
    ),
),

The key is that you are providing a new factory method for FormElement that still creates the same, standard class (no need to override it), but also calls the addClass method to register your custom helper as the proper helper for your custom element. If you don't want to make the short-name for your helper, you can drop the invokables section and put the FQCN in the addClass call, but I like having the short name available.

This is the best method I've found so far. Ideally, you wouldn't have to take over the construction of the FormElement and could just modify a config that gets passed to it. The downside of this approach is that if you have multiple modules that define custom form elements they are going to clash if they all try to re-define the FormElement factory. You can't specify additions in multiple modules this way. So, if someone finds a better config that can be set that simply gets passed to the FormElement::addClass() method, please let me know.

BTW, I found this page which doesn't address the view helper side of the equation, but talks about registering new form element classes and how to over-ride the built in classes: http://framework.zend.com/manual/current/en/modules/zend.form.advanced-use-of-forms.html

----custom form element-----

namespace App\Form\View\Helper;

use Zend\Form\View\Helper\FormElement as ZendFormElement;

/**
 * Description of FormElement
 */
class FormElement
        extends ZendFormElement
{

    public function addTypes(array $types)
    {
        foreach ($types as $type => $plugin) {
            $this->addType($type, $plugin);
        }
    }

}

---- application module.config.php--------------

//..........
    'view_helpers' => array(
        'invokables' => array(
            'formRTE' => 'App\Form\View\Helper\FormRTE',
        ),
        'factories' => array(
            'formElement' => function($sm) {
                $helper = new App\Form\View\Helper\FormElement();
                $helper->addTypes(array(
                    'rte' => 'formRTE',
                    ));
                return $helper;
            }
        ),
    ),
//.........
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top