SYMFONY custom CONSTRAINT -> Pass variable to a cu

2019-09-19 15:53发布

问题:

My goal: I built a custom constraint in SYMFONY, I needed to pass a variable to that constraint.

The context: The constraint do a check if a value is unique in the DB, if it is not, it raises a CONSTRAINT alert. That works alright when the FORM is used to create a new tuple in the DB but if it is an edit it raises an exception which should be bypass by checking that the value already existing, exists for the tuple Id being edited.

Hence I needed to pass the Id of the tuple being edited to my constraint check.

At first I implemented my custom constraint in my entity:

class MyEntity{

    /**
    * @MyBundleAssert\CheckValueAlreadyInDB(
    *     message = "already_exists_in_db",
    *     fieldToSearch = "my_value",
    *     tableToSearch = "my_table"
    *)
    */
    private myValue;

} 

As one can see, I did not find a way to implement a way to pass a VARIABLE using the constraint with ANNOTATION. By searching, I understood I could do that by using the __construct() of my custom constraint class:

/**
 * @Annotation
 */
class CheckValueAlreadyInDB extends Constraint{
public $message;
public $fieldToSearch;
public $tableToSearch;
public $idToCheck;
public $idToCheckFieldName;

public function __construct($options){
    if(count($options)>0){
        $this->idToCheck = $options['idToCheck'];
        $this->idToCheckFieldName = $options['idToCheckFieldName'];
        $this->fieldToSearch = $options['fieldToSearch'];
        $this->tableToSearch = $options['tableToSearch'];
        $this->message = $options['message'];
    }
}

public function validatedBy()
{
    return 'validator_check_value_already_in_db';
}
}

And, the ConstraintValidator extended class linked to it:

class CheckValueAlreadyInDBValidator extends ConstraintValidator
{
    private $con;

    public function __construct($con){
        $this->con = $con;
    }

    public function validate($value, Constraint $constraint)
    {
        ////My stuff to get a record from the DB////
        $sel = new PdoSelect($this->con);
        $search = $sel->returnRecordsInTableForSpecificKey([$constraint->fieldToSearch],[$value],  $constraint->tableToSearch,false);
       //////////////////////////////////////////////

        $sameId = false;
        if($constraint->idToCheck!==null){
            $idToCheckInRetrieveRecord = $search->{$constraint->idToCheckFieldName};            
            $sameId = ($idToCheckInRetrieveRecord==$constraint->idToCheck)?true:false;
        }

        if($search!=null&&!$sameId){
            $this->context->buildViolation($constraint->message)
                ->setParameter('%string%', $value)
                ->addViolation();
        }
    }
}

With service:

validator.unique.check_value_already_in_db:
    class: MyBundle\Form\CustomConstraints\CheckValueAlreadyInDBValidator
    arguments: ['@doctrine.dbal.default_connection']
    tags:
        - { name: validator.constraint_validator, alias: validator_check_value_already_in_db }

I my FORM (AbstractType extended class) for the field regarding myValue, I did edit the constraints attribute.

class MyEntityType extends AbstractType {     
public function buildForm(FormBuilderInterface $builder, array $options)
      {

    ....
        $builder->add('myValue',****Type::class,array(
          'constraints' => array(
              new CheckValueAlreadyInDB(array(
                'idToCheck'=>$options['data']->getId(),
                'idToCheckFieldName'=>'id',
                'fieldToSearch'=>'my_value',
                'tableToSearch'=>'my_table',
                'message' => "value_already_exists_in_db"))
            )
          ));

    ...
    }
}

I thought that the CONSTRAINT defined in the buildForm() would override the one defined in the * @MyBundleAssert\CheckValueAlreadyInDB(..) of MyEntity class (which should be the default behaviour). But It did not! I had to delete the ANNOTATION above MyEntity to make the constraint work as defined in the buildForm().

Does anyone know if there is a setting that could permit to have a constraint in a buildForm() overriding one existing as an ANNOTATION in MyEntity, but still let the ANNOTATION above a field in MyEntity be the default behavior? Or is there is a way to pass VARIABLE to ANNOTATIONS?

回答1:

I found the solution.

My mistake was to try to use constraints in class MyEntityType extends AbstractType:

 $builder->add('myValue',****Type::class,array(
  'constraints' => array(
      new CheckValueAlreadyInDB(array(
        'idToCheck'=>$options['data']->getId(),
        'idToCheckFieldName'=>'id',
        'fieldToSearch'=>'my_value',
        'tableToSearch'=>'my_table',
        'message' => "value_already_exists_in_db"))
    )
  ));

Update:

DON'T USE IT HERE

Have a look at class-constraint-validator section in the doc.

Implement the ConstraintValidator extended class above the class of the Entity where the validator has to execute its check and not above one attribute of the Entity class. That way one can have access to other attributes of the entity and use it as conditionals in the ConstraintValidator extended class.