简体   繁体   中英

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:

回发结果

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:

调试POST请求

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!

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 (ie 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!

I have a User entity with some validators specified in it.

But I want to extend that list of validators so as to add one based on a service call to check if a user email is not already used.

The problem is that I need an entity manager injected in the validator to call the service. And I can't (or rather don't know how to and don't want to :-) inject the entity manager into the entity.

I also want to leave in place the existing validation on the entity, as it is closer to the repository and might be used outside of my controller. So I cannot move all the validators out of this entity, into my new custom filter.

My new custom filter thus shall reuse the existing validators of the entity, as is.

The new custom filter is called UserFormFilter and it receives in its controller the existing entity filter, loops on it, adding to itself, each of the passed in validators:

class UserFormFilter extends InputFilter
{

    public function __construct(EntityManager $em, $identity, InputFilter $userDefaultInputFilter)
    {
        // Add the validators specified in the user entity
        foreach ($userDefaultInputFilter->inputs as $inputFilter) {
            $this->add($inputFilter);
        }

        $this->add(array(
            'name' => 'email',
            'required' => true,
            'filters' => array(),
            'validators' => array(
                array('name' => 'EmailAddress'),
                array(
                    'name' => 'Application\Validator\User',
                    'options' => array('manager' => $em, 'identity' => $identity)
                )
            ),
        ));
    }

}

I can now instantiate a custom UserFormFilter in my UserController:

$formFilter = new \Application\Form\UserFormFilter($em, $user->getInputFilter());
$form->setInputFilter($formFilter);

You can see in the above code that the custom UserFormFilter takes the default filter specified in my User entity getInputFilter method. As a side note, it also passes the entity manager enabling the custom filter to carry out the service call.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM