The mystery of missing Form->getData() values in Zend Framework 2

After taking a short break from Zend Framework 2 I started working on a new project in it. I created a Form class that looks like:

<?php

namespace MyProject\Form;

use ...

class RegistrationForm extends Form
{
    public function __construct($name = null)
    {
        parent::__construct('registration');

        $this->add(
            array(
                'name' => 'name',
                'type' => 'Text',
                'options' => array(
                    'label' => _t('Full name'),
                ),
            )
        );
        $this->add(
            array(
                'name' => 'email',
                'type' => 'Email',
                'options' => array(
                    'label' => _t('Email'),
                )
            )
        );
    }

    public function getInputFilter()
    {
        $inputFilter = new InputFilter();
        
        $fullName = new Input('name');
        $v = new ValidatorChain();
        $v->attach(new StringLength(array('min' => 3)));
        $fullName->setValidatorChain($v);
        $fullName->setRequired(true);
        $inputFilter->add($fullName);

        $email = new Input('email');
        $email->setRequired(true);
        $v = new ValidatorChain();
        $v->attach(new EmailAddress());
        $email->setValidatorChain($v);
        $inputFilter->add($email);

        return $inputFilter;
    }
}

The problem arose when I validated the form and tried to access the data in the controller:

$form->setData($postData);
if ($form->isValid()) {
     $data = $form->getData();
}

However, for some reason, the output of $data was missing values, something of this sort:

[
    'name' => null,
    'email' => null,
]

So, the getData() keys were present, but values were missing!

It took me a while to figure out what’s going on. In order to validate the form, Zend Framework 2 calls getInputFilter on it. However, it does not store the resulting InputFilter, but rather calls this method again whenever it needs it – essentially overwriting any data put into it by $form->setData($postData);

The solution was to store form’s InputFilter as a static variable. The code now looks like this and behaves as expected:

<?php

namespace MyProject\Form;

use ...

class RegistrationForm extends Form
{
    /**
     * @var \Zend\InputFilter\InputFilter
     */
    static private $inputFilter;

    public function __construct($name = null)
    {
        ... same as before ...
    }

    public function getInputFilter()
    {
        // Keep reusing the same instance of InputFilter
        if (isset(self::$inputFilter)) return self::$inputFilter;

        $inputFilter = new InputFilter();
        
        $fullName = new Input('name');
        $v = new ValidatorChain();
        $v->attach(new StringLength(array('min' => 3)));
        $fullName->setValidatorChain($v);
        $fullName->setRequired(true);
        $inputFilter->add($fullName);

        $email = new Input('email');
        $email->setRequired(true);
        $v = new ValidatorChain();
        $v->attach(new EmailAddress());
        $email->setValidatorChain($v);
        $inputFilter->add($email);

        // Make sure we store $inputFilter as a static variable
        return self::$inputFilter = $inputFilter;
    }
}

Output of getData() looks like:

[
    'name' => 'User submitted name',
    'email' => 'their@email.com',
]

Looks like a bug at the first glance, but I suppose this behavior allows for bigger flexibility when validating forms. I still have to find the good use for it, but oh well…