Reputation: 11020
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
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
Reputation: 1122
A better and cleaner solution https://symfony.com/doc/3.4/validation/custom_constraint.html is to write
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
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
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
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
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