Skip to content

Instantly share code, notes, and snippets.

@webdevilopers
Last active August 29, 2015 14:13
Show Gist options
  • Save webdevilopers/7dbf2f072ab394e1311b to your computer and use it in GitHub Desktop.
Save webdevilopers/7dbf2f072ab394e1311b to your computer and use it in GitHub Desktop.
How to use Symfony2 Validation Constraint Callback with validation groups to validate a single property
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* PriceQuoteRequest
*
* @ORM\Table(name="pricequote")
* @ORM\Entity
*
* @Assert\Callback(methods={"isGutterValid"}, groups={"flow_dormerRequest_step2"})
*/
class PriceQuoteRequest
{
/**
* @var integer $dormerInnerHeight
*
* @ORM\Column(name="h", type="float")
* @Assert\NotBlank(groups={"flow_dormerRequest_step2"})
* @Assert\Range(min="80", max="1000", groups={"flow_dormerRequest_step2"})
*/
public $dormerInnerHeight;
/**
* @var boolean $dormerGutter
*
* @ORM\Column(name="gutter", type="boolean")
*
* @Assert\Type(type="boolean", groups={"flow_dormerRequest_step2"})
* Assert\Callback(methods={"isGutterValid"}, groups={"flow_dormerRequest_step2"})
*/
public $dormerGutter;
/**
* @ORM\OneToOne(targetEntity="Acme\AppBundle\Entity\GutterMaterial")
*
* Assert\Type(type="Acme\AppBundle\Entity\GutterMaterial", groups={"flow_dormerRequest_step2"})
*/
public $dormerGutterMaterial;
/**
*
* @param ExecutionContextInterface $context
*
* Assert\Callback(groups={"flow_dormerRequest_step2"})
*/
public function isGutterValid(ExecutionContextInterface $context)
{
if ($this->getDormerGutter() === true && $this->getDormerGutterMaterial() === null) {
$context->buildViolation('Gutter checked but no Material selected.')
->atPath('dormerGutter')
->addViolation();
}
}
}
<?php
namespace Acme\AppBundle\Tests\Entity;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Validation;
class RequestFunctionalTest extends KernelTestCase
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* {@inheritDoc}
*/
public function setUp()
{
self::bootKernel();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager()
;
}
public function testDormerGutterValidator()
{
$priceQuoteRequest = new PriceQuoteRequest();
$priceQuoteRequest->setDormerGutter(true);
// Validating on single property will not call "isGutterValid" Callback, exptected only 1 error otherwise
// $violationList = $this->getValidator()->validateProperty($priceQuoteRequest, 'dormerGutter', array('flow_dormerRequest_step2'));
// Validating full object will call "isGutterValid" but return all errors resp. > 1
$violationList = $this->getValidator()->validate($priceQuoteRequest, array('flow_dormerRequest_step2'));
dump($violationList);
$this->assertEquals(1, count($violationList));
}
}
@webdevilopers
Copy link
Author

If DormerGutter is set to true (resp. checked in form) a DormerGutterMaterial has to be selected.

The isGutterValid method is supposed to check this and otherwise point the violation to the DormerGutter field:

 if ($this->getDormerGutter() === true && $this->getDormerGutterMaterial() === null) {
    $context->buildViolation('Gutter checked but no Material selected.')
        ->atPath('dormerGutter')
        ->addViolation();
} 

The Test sets the DormerGutter to true but selects no DormerGutterMaterial.
Validating on the property DormerGutter I expect to get that 1 error returned by the isGutterValid Callback.

Instead it will return 0 errors because calling validateProperty on a single property DOES NOT CALL the "global" Callback for the entire object resp. class.

Instead I can validate the complete object. This will indeed call the isGutterValid method.
It will return at least 2 errors since this also validates all the other properties e.g. dormerInnerHeight
Unfortunately - in my use case - I only want that 1 error returned.

Is there a way to point a Callback to a single property only enabling me to call the validateProperty method and still fire the Callback?

The alternative / workaround will of course be the usage of the Expression Constraint:

    /**
     * @var boolean $dormerGutter
     *
     * @ORM\Column(name="gutter", type="boolean")
     *
     * @Assert\Type(type="boolean", groups={"flow_dormerRequest_step2"})
     * @Assert\Expression(
     *  "this.getDormerGutterMaterial() !== null or this.getDormerGutter() == 0",
     *  message="Gutter checked but no Material selected.",
     *  groups={"flow_dormerRequest_step2"}
     * )
     */
    public $dormerGutter; 

Ps.: All validation groups are successfully linked to flow_dormerRequest_step2.

@webdevilopers
Copy link
Author

A smart workaround if I will HAVE TO use the validate method instead of validateProperty is to reduce the number of expected errors by using a custom validation group:

/**
* PriceQuoteRequest
*
* @ORM\Table(name="pricequote")
* @ORM\Entity
*
* @Assert\Callback(methods={"isGutterValid"}, groups={"dormerGutter"})
*/ 

And change the call in the Test class:

$violationList = $this->getValidator()->validate($priceQuoteRequest, array('dormerGutter'));

Thanks @bjo3rnf !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment