In Symfony2, I use custom constraints to validate some data on my form, but I wonder if I can bring in one value from the form to use in validating another value?
Here's my constraint...
<?php
// src\BizTV\ContainerManagementBundle\Validator\Constraints\ContainerExists.php
namespace BizTV\ContainerManagementBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class ContainerExists extends Constraint
{
public $message = 'Namnet är upptaget, vänligen välj ett annat.';
public function validatedBy()
{
return 'containerExists';
}
}
And my validator...
<?php
// src\BizTV\ContainerManagementBundle\Validator\Constraints\ContainerExistsValidator.php
namespace BizTV\ContainerManagementBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
use Doctrine\ORM\EntityManager as EntityManager;
class ContainerExistsValidator extends ConstraintValidator
{
private $container;
private $em;
public function __construct(Container $container, EntityManager $em) {
$this->container = $container;
$this->em = $em;
}
public function isValid($value, Constraint $constraint)
{
$em = $this->em;
$container = $this->container;
$company = $this->container->get('security.context')->getToken()->getUser()->getCompany();
//Fetch entities with same name
$repository = $em->getRepository('BizTVContainerManagementBundle:Container');
$query = $repository->createQueryBuilder('c')
->where('c.company = :company')
->setParameter('company', $company)
->orderBy('c.name', 'ASC')
->getQuery();
$containers = $query->getResult();
foreach ($containers as $g) {
if ($g->getName() == $value) {
$this->setMessage('Namnet '.$value.' är upptaget, vänligen välj ett annat', array('%string%' => $value));
return false;
}
}
return true;
}
}
Used through a service...
services:
biztv.validator.containerExists:
class: BizTV\ContainerManagementBundle\Validator\Constraints\ContainerExistsValidator
arguments: ['@service_container', '@doctrine.orm.entity_manager']
tags:
- { name: validator.constraint_validator, alias: containerExists }
Here's how I apply it like this to my entity...
<?php
namespace BizTV\ContainerManagementBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use BizTV\ContainerManagementBundle\Validator\Constraints as BizTVAssert;
use BizTV\UserBundle\Entity\User as user;
use BizTV\ContainerManagementBundle\Entity\Container as Container;
/**
* BizTV\ContainerManagementBundle\Entity\Container
*
* @ORM\Table(name="container")
* @ORM\Entity
*/
class Container
{
/**
* @var integer $id
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string $name
* @Assert\NotBlank(message = "Du måste ange ett namn")
* @BizTVAssert\ContainerExists
* @ORM\Column(name="name", type="string", length=255, nullable=true)
*/
private $name;
In my validator I would like to be able to accomplish something like this instead
public function isValid($FORM->OTHERVALUE, $value, Constraint $constraint)
{
$em = $this->em;
$container = $this->container;
$company = $this->container->get('security.context')->getToken()->getUser()->getCompany();
//Fetch entities with same name
$repository = $em->getRepository('BizTVContainerManagementBundle:Container');
$query = $repository->createQueryBuilder('c')
->where('c.company = :company')
->setParameter('company', $company)
->orderBy('c.name', 'ASC')
->getQuery();
$containers = $query->getResult();
if ($g->getName() == $FORM->OTHERVALUE) {
$this->setMessage('Namnet '.$value.' är upptaget, vänligen välj ett annat', array('%string%' => $value));
return false;
}
return true;
}
You can create a Class Constraint Validator (http://symfony.com/doc/master/cookbook/validation/custom_constraint.html#class-constraint-validator) and with this you get the object itself.
First you need to create a Constraint class:
class ContainerExists extends Constraint
{
public $message = 'Namnet är upptaget, vänligen välj ett annat.';
public function validatedBy()
{
return 'containerExists';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
after that create the validator itself where you have access to the object and not only a single property.
class ContainerExistsValidator extends ConstraintValidator {
private $em;
private $security_context;
public function __construct(EntityManager $entityManager, $security_context) {
$this->security_context = $security_context;
$this->em = $entityManager;
}
public function validate($object, Constraint $constraint) {
// do whatever you want with the object, and if something is wrong add a violation
$this->context->addViolation($constraint->message, array('%string%' => 'something'));
}
}
}
then create the service to access entity manager and stuff:
validator.container_exists:
class: YourBundleName\Validator\Constraints\ContainerExistsValidator
arguments: ["@doctrine.orm.entity_manager", "@security.context"]
tags:
- { name: validator.constraint_validator, alias: containerExists }
In this case as the class constraint validator is applied to the class itself, and not to the property you need to add the following annotation to your class.
use YourBundleName\Validator\Constraints as BackendAssert;
/**
* @BackendAssert\ContainerExists
*/
This might not be the answer you are looking for, but one possible work around would be to use form events for validation:
$builder->addEventListener(FormEvents::POST_BIND, function (DataEvent $event) {
$form = $event->getForm();
// You can access all form values with $form['value']
if ($form['name'] == $form['OTHERVALUE']) {
$form['name']->addError(new FormError('Your Message'));
}
});
This can be done with an Assert\Callback. In the callback you have access to the form values and can perform checks with other values. Here is a general solution:
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
public static function authenticate($val1, ExecutionContextInterface $context, $payload)
{
$testVal = null;
$root = $context->getRoot();
if ($root instanceof \Symfony\Component\Form\Form) {
$testVal = $root->getViewData()['testVal'];
if ($testVal < 5){
$context->buildViolation('Please enter a larger value')
->atPath('val1')
->addViolation()
;
}
}
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('val1', TextType::class, [
'constraints' => [
new Assert\Callback([$this, 'authenticate']) //this calls the method above
]
])
->add('testVal', TextType::class)
}
This will associate the error with the specific input field.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With