Symfony - get an entity object in a field constraint class

Viewed 5048

I created my custom constraint validator:

class CustomConstraint extends Constraint
{
    public $message = '';
}

class CustomConstraintValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {
         exit($this->context->getObject()); // returns null
    }
}

In docs it is stated that:

Returns the currently validated object.

, but for me it returns NULL instead.

P.S. I do not want to assign this constraint to Entity, only to certain forms or fields.

My form property which is validated:

->add('rejectReasons', null, array(
        'property' => 'name',
        'multiple' => true,
        'constraints' => array(
            new CustomConstraint(array(
                'message' => 'Application can not be refused.'
            )),
        )
    ));

Property in entity:

/**
 * @ORM\ManyToMany(targetEntity="RejectReason")
 * @ORM\JoinTable(name="relationship_application_reject_reasons",
 *      joinColumns={@ORM\JoinColumn(name="application_id", referencedColumnName="id", onDelete="CASCADE")},
 *      inverseJoinColumns={@ORM\JoinColumn(name="reject_reason_id", referencedColumnName="id")}
 *      )
 */
private $rejectReasons;

UPDATE

I tried putting constraint on other string property, I still get NULL.

4 Answers

For those making form validation using dependencies itself this can help. I assume that the Symfony version is 3.4 or 4.1 and you have symfony/form on your project.

Build your CustomConstraintValidator

The best way to deal with Symfony Form Validators with some kind of dependency are using CustomValidators

Above is a example that I use to work with them.

Supposed that we have an Entity like

// src/Entity/myEntity.php
namespace App\Entity;
...

class myEntity
{
    private $id;
    private $name; // string, required
    private $canDrive; // bool, not required (default=false)
    private $driveLicense; // string, not required (default = null)

    public function __construct()
    {
        $this->canDrive = false;
    }
    // getters and setters
}

We don't need to populate $driveLicense (cause the attribute its not mandatory), but if $canDrivechange from false to true, now $driveLicense must have a value.

$driveLicense is $canDrive dependent.

To build a form for that and validate $driveLicense correctly on the FormType (the best practice) we need to build a CustomConstraintValidator.

Building CanDriveValidator

// src/Validator/Constraints/CanDrive.php
namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

class CanDrive extends Constraint
{
    public $message = 'invalid_candrive_args'; // I like translators :D
}

Translator file - optional

//src/translators/validators.en.yaml // 
invalid_candrive_args: When "{{ candrivelabel }} " field is checked you must fill "{{ drivelicenselabel }}"

The validator

// src/Validator/Constraints/CanDriveValidator.php
namespace App\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class CanDriveValidator extends ConstraintValidator
{

    /**
     * Checks if the passed value is valid.
     *
     * @param mixed $value The value that should be validated
     * @param Constraint $constraint The constraint for the validation
     */
    public function validate($value, Constraint $constraint)
    {
        $canDriveField = $this->context->getObject(); // the Field using this validator
        $form = $canDriveField->getParent(); // the formType where the Field reside
        $myEntity = $form->getData(); // The Entity mapped by formType                
            
        if ($myEntity->getCanDrive() == true && $myEntity->getDriveLicense() == null) {
            $this->context->buildViolation($constraint->message)
                ->setParameter('{{ candrivelabel }}', 'Can Drive')
                ->setParameter('{{ drivelicenselabel }}', 'Drive License')
                ->addViolation();
        }            
    } 
}

The form myEntityType

//src/Form/myEntityType.php
namespace App\Form;
use App\Entity\myEntity;
use App\Validator\Constraints\CanDrive;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;


class myEntityType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name') 
            ->add('canDrive', CheckBoxType::class, [
                    'required' => false,
                    'constraints' => array(new canDrive()),                    
                    ]                    
                )
            ->add('driveLicense', TextType::class, ['required' => false])
        ;

    }


    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(['data_class' => myEntity::class]);
    }        
}

Now, when use call isValid() method of myEntityType form and the canDrive field was checked and driveLicense is blank, a Violation will be fired on canDrive field. If canDrive is set to false (not checked, not submitted), nothing happens and form will be valid even when driveLicense is blank.

The answer is simple. Write :

this->context->getRoot()->getData()

and u have the object.

Related