Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging input filters in Zend Framework 2

I have a number of fieldsets, and I would like to create an input filter class for each of them. The idea is then that for each of my forms, I can create an input filter class that is composed of other input filters. For instance, when creating an account via a registration form, I would like to take the base Account input filter I have for my Account entity and use it in a new input filter class that can modify the inputs or add additional ones. Something like the below.

class Register extends InputFilter
{
    public function __construct(ObjectRepository $accountRepository, Account $accountFilter)
    {
        /***** Add inputs from input filters *****/
        $this->inputs = $accountFilter->getInputs();

        /***** Add additional validation rules *****/
        // Username
        $usernameAvailability = new NoObjectExists(array(
            'object_repository' => $accountRepository,
            'fields' => array('username'),
        ));

        $username = $this->get('username');
        $username->getValidatorChain()
            ->attach($usernameAvailability, true);

        // E-mail
        $emailAvailability = new NoObjectExists(array(
            'object_repository' => $accountRepository,
            'fields' => array('email'),
        ));

        $email = $this->get('email');
        $email->getValidatorChain()
            ->attach($emailAvailability, true);
    }
}

I pass in an input filter to the constructor, and I want to add the inputs of this filter to my Register filter and modify the inputs.

The problem I am having is that only some of my inputs seem to validate as intended, and I cannot seem to figure out why. When I submit my form, only some inputs are validated as expected:

Postback result

Interestingly, the e-mail input does not behave as expected when filling out an e-mail that already exists in my database. The result should be a validation error that it already exists, but this does not happen. If I debug and look at my form, I found the following:

Debugging POST request

The form's filter has the right inputs with the right validators, and as shown on the above image, the username input does seem to validate correctly. But for some reason, this is not visually reflected in my form.

Below is my code.

Fieldsets

class Profile extends Fieldset
{
    public function __construct(ObjectManager $objectManager)
    {
        parent::__construct('profile');

        $this->setHydrator(new DoctrineHydrator($objectManager))
            ->setObject(new ProfileEntity());

        // Elements go here

        $this->add(new AccountFieldset($objectManager));
    }
}



class Account extends Fieldset
{
    public function __construct()
    {
        parent::__construct('account');

        $username = new Element\Text('username');
        $username->setLabel('Username');

        $password = new Element\Password('password');
        $password->setLabel('Password');

        $repeatPassword = new Element\Password('repeatPassword');
        $repeatPassword->setLabel('Repeat password');

        $email = new Element\Email('email');
        $email->setLabel('E-mail address');

        $birthdate = new Element\DateSelect('birthdate');
        $birthdate->setLabel('Birth date');

        $gender = new Element\Select('gender');
        $gender->setLabel('Gender')
            ->setEmptyOption('Please choose')
            ->setValueOptions(array(
                1 => 'Male',
                2 => 'Female',
            ));

        $this->add($username);
        $this->add($password);
        $this->add($repeatPassword);
        $this->add($email);
        $this->add($birthdate);
        $this->add($gender);
        $this->add(new CityFieldset());
    }
}

Form

class Register extends Form
{
    public function __construct()
    {
        parent::__construct('register');

        // Terms and Conditions
        $terms = new Element\Checkbox('terms');
        $terms->setLabel('I accept the Terms and Conditions');
        $terms->setCheckedValue('yes');
        $terms->setUncheckedValue('');
        $terms->setAttribute('id', $terms->getName());

        // Submit button
        $submit = new Element\Submit('btnRegister');
        $submit->setValue('Register');

        $profileFieldset = new ProfileFieldset($objectManager);
        $profileFieldset->setUseAsBaseFieldset(true);

        // Add elements to form
        $this->add($terms);
        $this->add($profileFieldset);
        $this->add($submit);
    }
}

View

$form->prepare();
echo $this->form()->openTag($form);
$profile = $form->get('profile');

$account = $profile->get('account');
echo $this->formRow($account->get('username'));
echo $this->formRow($account->get('password'));
echo $this->formRow($account->get('repeatPassword'));
echo $this->formRow($account->get('email'));
echo $this->formRow($account->get('birthdate'));
echo $this->formRow($account->get('gender'));

$city = $account->get('city');
echo $this->formRow($city->get('postalCode'));
echo $this->formRow($form->get('terms'));
echo $this->formSubmit($form->get('btnRegister'));
echo $this->form()->closeTag();

Controller

$form = new Form\Register();
$profile = new Profile();

if ($this->request->isPost()) {
    $form->bind($profile);

    $form->setData($this->request->getPost());
    $form->setInputFilter($this->serviceLocator->get('Profile\Form\Filter\Register'));

    if ($form->isValid()) {
        // Do stuff
    }
}

return new ViewModel(array('form' => $form));

Am I misunderstanding something here? Is there a better way to do this while still having multiple input filter classes? I would really prefer to keep my code maintainable like this rather than copying validation rules around for different forms. Sorry for the long post - it was really difficult to explain this problem!

like image 330
ba0708 Avatar asked May 26 '14 18:05

ba0708


1 Answers

Okay, it seems like I figured this out. Apparently my first approach was quite wrong. I found a way to have an input filter class for each of my fieldsets and then reuse these input filters for my form while adding additional validation rules for certain form elements (from my fieldsets). This way, I can have my generic validation rules defined in standard input filter classes per fieldset and modify them for different contexts (i.e. forms). Below is the code. The classes differ a bit from the question because that was slightly simplified.

Main input filter

// This input filter aggregates the "fieldset input filters" and adds additional validation rules
class Register extends InputFilter
{
    public function __construct(ObjectRepository $accountRepository, InputFilter $profileFilter)
    {
        /***** ADD ADDITIONAL VALIDATION RULES *****/
        // Username
        $usernameAvailability = new NoObjectExists(array(
            'object_repository' => $accountRepository,
            'fields' => array('username'),
        ));

        $emailInput = $profileFilter->get('account')->get('username');
        $emailInput->getValidatorChain()->attach($usernameAvailability, true);

        // E-mail
        $emailAvailability = new NoObjectExists(array(
            'object_repository' => $accountRepository,
            'fields' => array('email'),
        ));

        $emailInput = $profileFilter->get('account')->get('email');
        $emailInput->getValidatorChain()->attach($emailAvailability, true);


        /***** ADD FIELDSET INPUT FILTERS *****/
        $this->add($profileFilter, 'profile');
    }
}

Profile input filter

class Profile extends InputFilter
{
    public function __construct(InputFilter $accountFilter)
    {
        $this->add($accountFilter, 'account');

        // Add generic validation rules (inputs) for the profile fieldset here
    }
}

The Account input filter referred to in the code above is a completely normal input filter class that extends Zend\InputFilter\InputFilter and adds inputs. Nothing special about it.

My fieldsets remain untouched and are identical to the ones in the question, as are the form class and the controller. The Register input filter is added to the form with the setInputFilter method, and that's it!

With this approach, each input filter instance is added to a fieldset - something that my first approach did not do. I hope this helps someone with similar problems!

like image 136
ba0708 Avatar answered Oct 12 '22 14:10

ba0708