Johannes Klauß
Johannes Klauß

Reputation: 11020

How to validate a property dependent on another property in Symfony 2

Is it possible to validate a property of a model class dependent on another property of the same class?

For example, I have this class:

class Conference
{
    /** $startDate datetime */
    protected $startDate;

    /** $endDate datetime */
    protected $endDate;
}

and I want that Symfony 2.0 validates, that $startDate has to be after $endDate.

Is this possible by annotations or do I have to do this manually?

Upvotes: 35

Views: 30182

Answers (6)

Kisz Na
Kisz Na

Reputation: 482

For Date validations, we can simply use GreaterThan and GreaterThanOrEqual comparison constraints.

class Conference
{
     /**
     * @var \DateTime
     * @Assert\GreaterThanOrEqual("today")
     */
    protected $startDate;

     /**
     * @var \DateTime
     * @Assert\GreaterThan(propertyPath="startDate")
     */
    protected $endDate;
}

For more information, see validation constraints

Upvotes: 3

nicolallias
nicolallias

Reputation: 1122

A better and cleaner solution https://symfony.com/doc/3.4/validation/custom_constraint.html is to write

  • a custom constraint (which is basically the error message)
  • and its validator (which is like a controller function that does the control

To check that the entity is fine, add to the custom contraint (not the validator)

public function getTargets()
{
    return self::CLASS_CONSTRAINT;
}

Which allows you to use an instance of that entity instead of just a property value. That make possible to write in the validator:

public function validate($object, Constraint $constraint)
{
    #Your logic, for example:
    if($value1 = $object->getValue1())
    {
        if($value2 = $object->getValue2())
        {
            if($value1 === $value2)
            {
                # validation passed
                return True;
            }
            else
            {
                # validation failed
                $this->context->buildViolation($constraint->message)
                    ->setParameter('{{ string }}', $value1.' !== '.$value2)
                    ->addViolation();
            }

The best part is what you need to write in the entity class:

use YourBundle\Validator\Constraints as YourAssert;

/**
 * Yourentity
 *
 * @ORM\Table(name="yourentity")
 * @ORM\Entity(repositoryClass="YourBundle\Repository\YourentityRepository")
 *
 * @YourAssert\YourConstraintClassName # <-- as simple as this

Hope that helps

Upvotes: 5

TazGPL
TazGPL

Reputation: 3748

It's even more simple since version 2.4. All you have to do is add this method to your class:

use Symfony\Component\Validator\Context\ExecutionContextInterface;

/**
 * @Assert\Callback
 */
public function isStartBeforeEnd(ExecutionContextInterface $context)
{
    if ($this->getStartDate() <= $this->getEndDate()) {
        $context->buildViolation('The start date must be prior to the end date.')
                ->atPath('startDate')
                ->addViolation();
    }
}

The buildViolation method returns a builder that has a couple of other methods to help you configure the constraint (like parameters and translation).

Upvotes: 9

Damaged Organic
Damaged Organic

Reputation: 8467

Another way (at least as of Symfony 2.3) is to use simple @Assert\IsTrue:

class Conference
{
    //...

    /**
     * @Assert\IsTrue(message = "Startime should be lesser than EndTime")
     */
    public function isStartBeforeEnd()
    {
        return $this->getStartDate() <= $this->getEndDate;
    }

    //...
}

As reference, documentation.

Upvotes: 10

Slava Fomin II
Slava Fomin II

Reputation: 28611

Starting from Symfony 2.4 you can also use Expression validation constraint to achieve what you need. I do believe, that this is the most simple way to do this. It's more convenient than Callback constraint for sure.

Here's example of how you can update your model class with validation constraints annotations:

use Symfony\Component\Validator\Constraints as Assert;


class Conference
{
    /**
     * @var \DateTime
     *
     * @Assert\Expression(
     *     "this.startDate <= this.endDate",
     *     message="Start date should be less or equal to end date!"
     * )
     */
    protected $startDate;

    /**
     * @var \DateTime
     *
     * @Assert\Expression(
     *     "this.endDate >= this.startDate",
     *     message="End date should be greater or equal to start date!"
     * )
     */
    protected $endDate;
}

Don't forget to enable annotations in your project configuration.

You can always do even more complex validations by using expression syntax.

Upvotes: 44

Sybio
Sybio

Reputation: 8645

Yes with the callback validator: http://symfony.com/doc/current/reference/constraints/Callback.html

On symfony 2.0:

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ExecutionContext;

/**
 * @Assert\Callback(methods={"isDateValid"})
 */
class Conference
{

    // Properties, getter, setter ...

    public function isDateValid(ExecutionContext $context)
    {
        if ($this->startDate->getTimestamp() > $this->endDate->getTimestamp()) {
                $propertyPath = $context->getPropertyPath() . '.startDate';
                $context->setPropertyPath($propertyPath);
                $context->addViolation('The starting date must be anterior than the ending date !', array(), null);
        }
    }
}

On symfony master version:

    public function isDateValid(ExecutionContext $context)
    {
        if ($this->startDate->getTimestamp() > $this->endDate->getTimestamp()) {
            $context->addViolationAtSubPath('startDate', 'The starting date must be anterior than the ending date !', array(), null);
        }
    }

Here I choose to show the error message on the startDate field.

Upvotes: 22

Related Questions