Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ZF2 and Doctrine input filters (Unique, ObjectExists) for new, and existing entities with fieldsets

Im learning doctrine and i have a form. ZF2 and doctrine with field "email". This field need to be unique, so i need validator for it. Im using fieldsets too (its important here). Problem is, when i use:

DoctrineModule\Validator\UniqueObject

it is impossible to create new entity. This validator need primary key to compare. Validator dump error with message:

Expected context to contain itemId

itemId is my primary key.

So its obvious, that i need to use UniqueObject for update, and:

DoctrineModule\Validator\NoObjectExists

for new entity. And question is:

What is the best way, to store different input filter specification for existing and new entity?

Or, better if possible: How to use Unique validator with new, and existing records with zend form fieldsets.

If i put it in the form, I need to modify it inside controller if entity is new or no. Not so good idea.

I think that the best way is to store input filter spec. inside entity repository, but how i can check there if entity is new or not?

---- edit

I saw documentation, i know how to use unique object but i have error like said before: "Expected context to contain itemId". I think problem is with fieldsets (im using it). I dont understand how to do this (text from docs):

If you leave out the use_context option or set it to false you have to pass an array containing the fields- and identifier-values into isValid(). When using Zend\Form this behaviour is needed if you're using fieldsets.

Ok im using fieldsets, so now what i can do? How i can pass correct values to isValid when im using zend forms?

like image 886
Daimos Avatar asked Oct 31 '15 13:10

Daimos


2 Answers

With UniqueObject validator you need to have the identifier field in the context. So it will only work if the email column is the identifier column of your Email entity? You have an additional id column. It would be better to use the NoObjectExists validator in your user case:

'email' => array(
    'validators' => array(
        array(
            'name' => 'DoctrineModule\Validator\NoObjectExists',
            'options' => array(
                'object_repository' => $entityManager->getRepository(
                    'Application\Entity\Email'
                ),
                'fields' => 'email'
            )
        )
    )
)

You can also find this example in the documentation.

EDIT

About separating logic for update and new input filters I would suggest making two folders. It is better to keep them strictly separated like that, otherwise it is very likely that mistakes will happen. You could for example do like this (but it totally depends on you personal preference how this is organized).

Application/InputFilter/Create
    UserInputFilter

And one for updating resources:

Application/InputFilter/Update
    UserInputFilter

And then in your controller you could do like this:

<?php

namespace Application\Controller;

use Application\InputFilter\Create;
use Application\InputFilter\Update;

class UserController{

    public function updateAction()
    {
        $inputFilter = new Update\UserInputFilter();
        //... etc
    }

    public function createAction()
    {
        $inputFilter = new Create\UserInputFilter();
        //... etc
    }
}
like image 177
Wilt Avatar answered Oct 01 '22 02:10

Wilt


I developed it for my application to deal with this issue..hope it will help

<?php

namespace Application\Validator;

use Zend\Validator\AbstractValidator;

class DbUniqueObject extends AbstractValidator {

const INVALID = 'objectAlreadyExists';

protected $messageTemplates = array(
    self::INVALID => "Field value must be unique in the database (id=%id%)",
);
protected $messageVariables = array(
    'id' => array('options' => 'id'),
);
protected $options          = array(
    'em',
    'entity',
    'field',
    'exclude_id'
);

public function __construct($options = null) {
    $this->options = $options;
    parent::__construct($options);
}

public function isValid($value) {
    $qb = $this->em->createQueryBuilder();
    $qb->select('t')
            ->from($this->entity, 't')
            ->where('t.' . $this->field . '= :field')
            ->setParameter('field', $value);

    if (boolval($this->exclude_id)) {
        $qb->andWhere('t.id <> :id');
        $qb->setParameter('id', $this->exclude_id);
    }
    $result = $qb->getQuery()->getResult();
    if (boolval($result)) {
        $this->options['id'] = $result[0]->getID();
        $this->error(self::INVALID);
        return false;
    }
    return true;
}

public function __get($property) {
    return array_key_exists($property, $this->options) ? $this->options[$property] : parent::__get($property);
}

}

Anf then in your input filter attached to your form, just add ,in the 'Validators' array :

                    'validators' => array(
                    array(
                        'name'    => '\Application\Validator\DbUniqueObject',
                        'options' => array(
                            'em'         => $em, //Entity manager
                            'entity'     => 'Application\Entity\Customer', // Entity name
                            'field'      => 'label', // column name
                            'exclude_id' => $this->customer->getID() : null, // id to exclude (useful in case of editing)
                        )
                    )
                ),
like image 41
Seif Avatar answered Oct 01 '22 04:10

Seif