I’ve already written a post on how to create a custom validation constraint here (https://creativcoders.wordpress.com/2014/07/15/symfony2-tutorial-create-custom-constraint-validation-and-use-it-with-fosuserbundle/), but what if you need to validate two fields or more in your entity?
Well, I have an entity called “Card” that stores credit cards informations and i need to check the card validity by checking the expiration month and expiration year.
The proper way to do this is to create a custom validation constraint that we will append in our “card” entity.
This is how the entity looks:
<?php
namespace EdouardKombo\StripePaymentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Card
*
* @ORM\Table(name="headoo_stripe_credit_card")
* @ORM\Entity
*/
class Card
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var integer
*
* @ORM\Column(name="number", type="integer")
* @Assert\NotBlank(message="card.number.blank")
* @Assert\Length(min="16", max="16", minMessage="card.number.length", maxMessage="card.number.length", exactMessage="card.number.length")
* @Assert\Range(min="1000000000000000", max="9999999999999999", minMessage="card.number.range", maxMessage="card.number.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.number.invalid")
*/
private $number;
/**
* @var integer
*
* @ORM\Column(name="expirationMonth", type="integer")
* @Assert\NotBlank(message="card.expiration.month.blank")
* @Assert\Length(min="2", max="2", minMessage="card.expiration.month.length", maxMessage="card.expiration.month.length", exactMessage="card.expiration.month.length")
* @Assert\Range(min="1", max="12", minMessage="card.expiration.month.range", maxMessage="card.expiration.month.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.expiration.month.invalid")
*/
private $expirationMonth;
/**
* @var integer
*
* @ORM\Column(name="expirationYear", type="integer")
* @Assert\NotBlank(message="card.expiration.year.blank")
* @Assert\Length(min="2", max="2", minMessage="card.expiration.year.length", maxMessage="card.expiration.year.length", exactMessage="card.expiration.year.length")
* @Assert\Range(min="0", max="99", minMessage="card.expiration.year.range", maxMessage="card.expiration.month.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.expiration.year.invalid")
*/
private $expirationYear;
/**
* @var integer
*
* @ORM\Column(name="cvc", type="integer")
* @Assert\NotBlank(message="card.cvc.blank")
* @Assert\Length(min="3", max="4", minMessage="card.cvc.short", maxMessage="card.cvc.long", exactMessage="card.cvc.long")
* @Assert\Range(min="1", max="9999", minMessage="card.cvc.range", maxMessage="card.cvc.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.cvc.invalid")
*/
private $cvc;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set number
*
* @param integer $number
* @return Card
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
*
* @return integer
*/
public function getNumber()
{
return $this->number;
}
/**
* Set expirationMonth
*
* @param integer $expirationMonth
* @return Card
*/
public function setExpirationMonth($expirationMonth)
{
$this->expirationMonth = $expirationMonth;
return $this;
}
/**
* Get expirationMonth
*
* @return integer
*/
public function getExpirationMonth()
{
return $this->expirationMonth;
}
/**
* Set expirationYear
*
* @param integer $expirationYear
* @return Card
*/
public function setExpirationYear($expirationYear)
{
$this->expirationYear = $expirationYear;
return $this;
}
/**
* Get expirationYear
*
* @return integer
*/
public function getExpirationYear()
{
return $this->expirationYear;
}
/**
* Set cvc
*
* @param integer $cvc
* @return Card
*/
public function setCvc($cvc)
{
$this->cvc = $cvc;
return $this;
}
/**
* Get cvc
*
* @return integer
*/
public function getCvc()
{
return $this->cvc;
}
}
Ok so to validate the expirationMonth and expirationYear properties with custom validation constraints, we will create one constraint, called “CardHasExpired”.
- Create these files in your bundle main directory: “Validator/Constraints/CardHasExpired.php” and “Validator/Constraints/CardHasExpiredValidator.php”
This is how will look the CardHasExpired.php field:
<?php
namespace EdouardKombo\StripePaymentBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class CardHasExpired extends Constraint
{
/**
*
* @var string
*/
public $message = 'Your card has expired.';
/**
*
* @return string
*/
public function validatedBy()
{
return 'card_has_expired';
}
/**
* Get class constraints and properties
*
* @return array
*/
public function getTargets()
{
return array(self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT);
}
}
Now, we can open the CardHasExpiredValidator.php file and we can call the properties we want from our card entity.
This way we can validate multiple properties.
Here, i just need to check that the expiration month and year are not outdated.
This how our CardHasExpiredValidator.php file will look like.
<?php
namespace EdouardKombo\StripePaymentBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class CardHasExpiredValidator extends ConstraintValidator
{
/**
* Method to validate
*
* @param string $value Property value
* @param \Symfony\Component\Validator\Constraint $constraint All properties
*
* @return boolean
*/
public function validate($value, Constraint $constraint)
{
$date = getdate();
$year = (string) $date['year'];
$month = (string) $date['mon'];
$yearLastDigits = substr($year, 2);
$monthLastDigits = $month;
$otherFieldValue = $this->context->getRoot()->get('expirationMonth')->getData();
if (!empty($otherFieldValue) && ($value <= $yearLastDigits) &&
($otherFieldValue <= $monthLastDigits)) {
$this->context->addViolation(
$constraint->message,
array('%string%' => $value)
);
return false;
}
return true;
}
}
Now, we have to create a service that will load our constraints.
<?php
edouard_kombo_stripe_payment.card.validator.card_has_expired:
class: EdouardKombo\StripePaymentBundle\Validator\Constraints\CardHasExpiredValidator
tags:
- { name: validator.constraint_validator, alias: card_has_expired }
And finally, we can use our constraint directly in our card entity, and assign it to $expirationYear property.
<?php
namespace EdouardKombo\StripePaymentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use EdouardKombo\StripePaymentBundle\Validator\Constraints\CardHasExpired;
/**
* Card
*
* @ORM\Table(name="headoo_stripe_credit_card")
* @ORM\Entity
*/
class Card
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var integer
*
* @ORM\Column(name="number", type="integer")
* @Assert\NotBlank(message="card.number.blank")
* @Assert\Length(min="16", max="16", minMessage="card.number.length", maxMessage="card.number.length", exactMessage="card.number.length")
* @Assert\Range(min="1000000000000000", max="9999999999999999", minMessage="card.number.range", maxMessage="card.number.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.number.invalid")
*/
private $number;
/**
* @var integer
*
* @ORM\Column(name="expirationMonth", type="integer")
* @Assert\NotBlank(message="card.expiration.month.blank")
* @Assert\Length(min="2", max="2", minMessage="card.expiration.month.length", maxMessage="card.expiration.month.length", exactMessage="card.expiration.month.length")
* @Assert\Range(min="1", max="12", minMessage="card.expiration.month.range", maxMessage="card.expiration.month.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.expiration.month.invalid")
*/
private $expirationMonth;
/**
* @var integer
*
* @ORM\Column(name="expirationYear", type="integer")
* @Assert\NotBlank(message="card.expiration.year.blank")
* @Assert\Length(min="2", max="2", minMessage="card.expiration.year.length", maxMessage="card.expiration.year.length", exactMessage="card.expiration.year.length")
* @Assert\Range(min="0", max="99", minMessage="card.expiration.year.range", maxMessage="card.expiration.month.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.expiration.year.invalid")
* @CardHasExpired(message="card.expiration.expired")
*/
private $expirationYear;
/**
* @var integer
*
* @ORM\Column(name="cvc", type="integer")
* @Assert\NotBlank(message="card.cvc.blank")
* @Assert\Length(min="3", max="4", minMessage="card.cvc.short", maxMessage="card.cvc.long", exactMessage="card.cvc.long")
* @Assert\Range(min="1", max="9999", minMessage="card.cvc.range", maxMessage="card.cvc.range")
* @Assert\Regex(pattern="^(([0-9]*)|(([0-9]*).([0-9]*)))$^", match=true, message="card.cvc.invalid")
*/
private $cvc;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set number
*
* @param integer $number
* @return Card
*/
public function setNumber($number)
{
$this->number = $number;
return $this;
}
/**
* Get number
*
* @return integer
*/
public function getNumber()
{
return $this->number;
}
/**
* Set expirationMonth
*
* @param integer $expirationMonth
* @return Card
*/
public function setExpirationMonth($expirationMonth)
{
$this->expirationMonth = $expirationMonth;
return $this;
}
/**
* Get expirationMonth
*
* @return integer
*/
public function getExpirationMonth()
{
return $this->expirationMonth;
}
/**
* Set expirationYear
*
* @param integer $expirationYear
* @return Card
*/
public function setExpirationYear($expirationYear)
{
$this->expirationYear = $expirationYear;
return $this;
}
/**
* Get expirationYear
*
* @return integer
*/
public function getExpirationYear()
{
return $this->expirationYear;
}
/**
* Set cvc
*
* @param integer $cvc
* @return Card
*/
public function setCvc($cvc)
{
$this->cvc = $cvc;
return $this;
}
/**
* Get cvc
*
* @return integer
*/
public function getCvc()
{
return $this->cvc;
}
}
Congratulations, you’ve done it, you’re a champion.