10

How can I in ZF2 create custom form element with custom validator? I want to create custom category picker that uses jQuery and content of this element should be rendered from phtml script. In ZF1 it was quite easy but in ZF2 I don't know from where to start.

dreamcrash
  • 47,137
  • 25
  • 94
  • 117
Maciej
  • 314
  • 5
  • 17

1 Answers1

26

A form element must implement a Zend\Form\ElementInterface. A default class is the Zend\Form\Element which you can use as a base form:

<?php
namespace MyModule\Form\Element;

use Zend\Form\Element;

class Foo extends Element
{
}

CUSTOM VALIDATOR

You can let the element directly assign a custom validator. Then you must implement the Zend\InputFilter\InputProviderInterface:

<?php
namespace MyModule\Form\Element;

use Zend\Form\Element;
use Zend\InputFilter\InputProviderInterface;
use MyModule\InputFilter\Bar as BarValidator;

class Foo extends Element implements InputProviderInterface
{
    protected $validator;

    public function getValidator()
    {
        if (null === $this->validator) {
            $this->validator = new BarValidator;
        }
        return $this->validator;
    }

    public function getInputSpecification()
    {
        return array(
            'name'       => $this->getName(),
            'required'   => true,
            'validators' => array(
                $this->getValidator(),
            ),
        );
    }
}

CUSTOM RENDERING

At this moment it is a bit complex how Zend Framework handles the rendering of custom form element types. Usually, it just returns plain <input type="text"> elements.

There is one option, then you have to override the Zend\Form\View\Helper\FormElement helper. It is registered as formelement and you must override this view helper in your custom module:

namespace MyModule;

class Module
{
    public function getViewHelperConfig()
    {
        return array(
            'invokables' => array(
                'formelement' => 'MyModule\Form\View\Helper\FormElement',
                'formfoo'     => 'MyModule\Form\View\Helper\FormFoo',
            ),
        );
    }
}

Furthermore, every form element in Zend Framework 2 is rendered by a view helper. So you create a view helper for your own element, which will render the element's content.

Then you have to create your own form element helper (MyModule\Form\View\Helper\FormElement):

namespace MyModule\Form\View\Helper;

use MyModule\Form\Element;
use Zend\Form\View\Helper\FormElement as BaseFormElement;
use Zend\Form\ElementInterface;

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

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

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

As a last step, create your view helper to render this specific form element:

namespace MyModule\Form\View\Helper;

use Zend\Form\ElementInterface;
use Zend\Form\View\Helper\AbstractHelper;

class Foo extends AbstractHelper
{
    public function __invoke(ElementInterface $element)
    {
        // Render your element here
    }
}

If you want to render a .phtml file for example for this form element, load it inside this helper:

namespace MyModule\Form\View\Helper;

use Zend\Form\ElementInterface;
use Zend\Form\View\Helper\AbstractHelper;

class Foo extends AbstractHelper
{
    protected $script = 'my-module/form-element/foo';

    public function render(ElementInterface $element)
    {
        return $this->getView()->render($this->script, array(
            'element' => $element
        ));
    }
}

It will render a my-module/form-element/foo.phtml and in this script you will have a variable $element which contains your specific form element.

Jurian Sluiman
  • 13,498
  • 3
  • 67
  • 99
  • I get Catchable fatal error: Argument 1 passed to MyModule\Form\View\Helper\FormElement::render() must be an instance of MyModule\Form\View\Helper\ElementInterface, instance of Zend\Form\Element given – Maciej Dec 08 '12 at 16:14
  • When i'm adding form element it is stil instance of Zend\Form\Element not Element\Foo, i'm adding something like this: `$this->add(array( 'name' => 'category', 'attributes' => array( 'type' => 'MyModule\Form\Elemen\Foo', ));` – Maciej Dec 08 '12 at 16:57
  • @Maciej you are right, I made two mistakes: in the `FormElement` view helper you have to import the `ElementInterface` too, otherwise you get above warning. A second mistake was made in the view helper rendering your custom form element. Check the diff to see the differences, I think this should fix all problems: http://stackoverflow.com/posts/13776839/revisions – Jurian Sluiman Dec 08 '12 at 17:55
  • Yes it's better but I still can't render element, probably I'm doing smoething wrong in form class: `class MyForm extends Form { public function __construct($name = null) { // we want to ignore the name passed parent::__construct('album'); $this->setAttribute('method', 'post'); $this->add(array( 'name' => 'category', 'attributes' => array( 'type' => 'Element\Foo', ), 'options' => array( 'label' => 'Category', ), )); } }` – Maciej Dec 08 '12 at 18:05
  • For custom form elements it's the easiest way to use the FQCN (`MyModule\Form\Element\Foo`). Otherwise, instantiate them and add them like this: `$element = new \MyModule\Form\Element\Foo; $this->add($element)`. Above way assumes you are under the `Zend\Form` namespace, which will not work. – Jurian Sluiman Dec 09 '12 at 12:18
  • you cannot call the same render method passing a string, I think you meant $this->getView()->render() – Omar May 13 '13 at 09:49
  • Thanks heaps for this - so much harder and complex than ZF1. Can I ask if maybe the `Foo` view helper should have an `__invoke()` method? I was getting a `Function name must be a string` error in `MyModule/Form/View/Helper/FormElement.php` until I added it in? – dKen Jun 20 '14 at 11:30
  • 1
    It used to use the render() method. Now all form view helpers apparently had an overhaul so it doesn't work anymore. I edited the answer as render() should just be renamed to __invoke() – Jurian Sluiman Jun 20 '14 at 14:44
  • @JurianSluiman i need your help. my question http://stackoverflow.com/questions/24949200/page-not-found-on-request-url-in-zend-2-framework-application/24952498 – Anshul Parashar Jul 25 '14 at 10:43