Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
123 views
in Technique[技术] by (71.8m points)

SYMFONY custom CONSTRAINT -> Pass variable to a custom CONSTRAINT / How CONSTRAINT binded to a form field can OVERRIDE CONSTRAINT in ANNOTATION

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{

    /**
    * @MyBundleAssertCheckValueAlreadyInDB(
    *     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: MyBundleFormCustomConstraintsCheckValueAlreadyInDBValidator
    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 * @MyBundleAssertCheckValueAlreadyInDB(..) 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...