I am having a problem with the validation system in Symfony2. I have a Account entity which is validated on registration and account update. The entity has username, password, name, email etc. On the Account Update screen I allow to update on both the username and password for the account. I would like the password to be updated on the account only if a value is entered, which is implemented fine but the validation fails because the Validator expects a password there. If I make a separate group for registration with the password and account without the password it will not validate it at all. I want it to validate only if a new password is entered there. Is a conditional validation possible in this scenario?
1 Answers
To add some logic into your validation form process is very easy. You can pass an callable to 'validation_groups' OptionsResolverInterface.
Example (from Symfony2 book):
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$data = $form->getData();
if (Entity\Client::TYPE_PERSON == $data->getType()) {
return array('person');
} else {
return array('company');
}
},
));
}
Or:
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => array(
'Acme\AcmeBundle\Entity\Client',
'determineValidationGroups',
),
));
}
Check out: http://symfony.com/doc/current/book/forms.html#groups-based-on-the-submitted-data
Edit 1:
Try this:
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$account = $form->getData();
$password = $account->getPassword();
if (!empty($password)) {
return array('group_with_password');
} else {
return array('group_without_password');
}
},
));
}
Edit 2:
You could use the form events Symfony2[1]. The event Symfony\Component\Form\FormEvents::PRE_SUBMIT give you the data on an array before being set on the user object.
A good approach is use another attribute (not mapped to database), to store the password typed by the user. Like this:
<?php
/**
* @ORM\Entity
* @ORM\Table(name="user")
*/
class User {
/**
* @var integer
*
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* The password encrypted.
* @var string
*
* @ORM\Column(type="text", nullable=true)
*/
protected $password;
/**
* Password in plain text. This field should not be mapped as Doctrine field.
*
* @var string
*/
protected $plainPasword;
}
The $plainPassword is used in forms that user can set/change their password. This attribute always will be null, except when the users change their passwords.
The $password is used to store the encrypted version of $plainPassword. This attribute will be stored in database.
To know when a new password is given, simply check $plainPassword in your controller/service. Check out the example abode:
/**
*
* @Route("user/{id}", name="user_update")
* @Method("POST")
* @Template()
*/
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('MyBundle:User')->find($id);
if (!$user) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$editForm = $this->createEditForm($user);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$user = $editForm->getData();
$newPassword = $user->getPlainPassword();
if (!empty($newPassword)) {
$encodeFactory = $this->get('security.encoder_factory');
$passwordEncoder = $factory->getEncoder($user);
$encodedPassword = $encoder->encodePassword($newPassword, $user->getSalt());
$user->setPassword($encodedPassword);
}
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('user_show', array('id' => $id)));
}
return array(
'entity' => $user,
'edit_form' => $editForm->createView(),
);
}
To be short and didactic, I put all the logic in controller, but is better move them to a service. This is the way of FOSUserBundle do[2][3].
[1] http://symfony.com/doc/current/components/form/form_events.html
[2] https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Model/User.php
[3] https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Model/User.php

- 484
- 1
- 6
- 13
-
2That would only work if a password is present or not. What I am trying to achieve is maybe a bad practice. I want the password field to be validated if present, and not validated if empty value. In plain PHP it would be something like if (!empty($_POST['password]) validateFunction(); else skipValidation(); – Svetlin Staev Aug 16 '14 at 06:55
-
Check the snippet included above (I edited the Answer). – xthiago Aug 19 '14 at 19:18
-
It didn't work. I tried it with XDebug and for the $password I get the value already in the Entity, when no password is inputted in the password field. The funny thing is that when I get the account update validation error my authentication status from that Entity becomes Not Authenticated... – Svetlin Staev Aug 21 '14 at 06:07
-
It works :) I have only one more question because I do not know whether I have done it the most elegant way. With an event listener on FormEvents::PRE_SUBMIT you can see if the password field is empty or not but is it possible to somehow set the validation group dynamically on FormEvents::PRE_SUBMIT in the closure or is it too late in the process and the only way to do it is in the setDefaultOptions()? – Svetlin Staev Aug 22 '14 at 04:59