76

I have a form that combines two entities (User and Profile).

Validation seems to work on the first part of the form that comes form the User Entity and is the basis of the form.

The ProfileType is included inside the UserType. The form renders correctly and displays the correct information, so it seems it is properly connected to the Profile entity. It's just the validation that is broken on the ProfileType.

Any idea as to why one part would validate and the other wouldn't?

Code below:

Validation.yml

DEMO\DemoBundle\Entity\User\Profile:
    properties:
        address1:
            - NotBlank: { groups: [profile] }
        name:
            - NotBlank: { groups: [profile] }
        companyName:
            - NotBlank: { groups: [profile] }

DEMO\DemoBundle\Entity\User\User:
    properties:
        username:
            - NotBlank:
                groups: profile
                message: Username cannot be left blank.
        email:
            - NotBlank:
                groups: profile
                message: Email cannot be left blank
            - Email:
                groups: profile
                message: The email "{{ value }}" is not a valid email.
                checkMX: true
        password:
            - MaxLength: { limit: 20, message: "Your password must not exceed {{ limit }} characters." }
            - MinLength: { limit: 4, message: "Your password must have at least {{ limit }} characters." }
            - NotBlank: ~

UserType.php

namespace DEMO\DemoBundle\Form\Type\User;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;

use DEMO\DemoBundle\Form\Type\User\ProfileType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('username');
        $builder->add('email');
        $builder->add('profile', new ProfileType());
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'DEMO\DemoBundle\Entity\User\User',
            'validation_groups' => array('profile')
        );
    }

    public function getName()
    {
        return 'user';
    }
}

ProfileType.php

namespace DEMO\DemoBundle\Form\Type\User;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackValidator;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;

class ProfileType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name');
        $builder->add('companyName', null, array('label' => 'Company Name'));
        $builder->add('address1', null, array('label' => 'Address 1'));
        $builder->add('address2', null, array('label' => 'Address 2'));
        $builder->add('city');
        $builder->add('county');
        $builder->add('postcode');
        $builder->add('telephone');
    }

    public function getDefaultOptions(array $options)
    {
        return array(
            'data_class' => 'DEMO\DemoBundle\Entity\User\Profile',
        );
    }

    public function getName()
    {
        return 'profile';
    }
}

Controller

$user = $this->get('security.context')->getToken()->getUser();

        $form = $this->createForm(new UserType(), $user);

        if ($request->getMethod() == 'POST') {
            $form->bindRequest($request);

            if ($form->isValid()) {
                // Get $_POST data and submit to DB
                $em = $this->getDoctrine()->getEntityManager();
                $em->persist($user);
                $em->flush();

                // Set "success" flash notification
                $this->get('session')->setFlash('success', 'Profile saved.');
            }

        }

        return $this->render('DEMODemoBundle:User\Dashboard:profile.html.twig', array('form' => $form->createView()));

8 Answers 8

118

I spent an age searching and found that it was adding 'cascade_validation' => true to the setDefaults() array in my parent type's class that fixed it (as mentioned already in the thread). This causes the entity constraint validation to trigger in the child types shown in the form. e.g.

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(            
        ...
        'cascade_validation' => true,
    ));
}

For collections, also make sure to add 'cascade_validation' => true to the $options array for the collection field on the form. e.g.

$builder->add('children', 'collection', array(
    'type'         => new ChildType(),
    'cascade_validation' => true,
));

This will have the UniqueEntity validation take place as it should in the child entity used in the collection.

Sign up to request clarification or add additional context in comments.

5 Comments

How did you work out that you needed to add the cascade flag to the options array for the collection? This was my problem but I've not come across it in Symfony docs?
Through sifting through a good many other comments relating to similar issues on here :/
More easily, you can specify "valid" constraint in validation.yml to enable validation on objects that are embedded as properties on an object being validated symfony.com/doc/current/reference/constraints/Valid.html
For those using Symfony 3.0 and up by now: cascade_validation has been removed. See my answer below for details.
@sdespont If an entity extend another where the property with Assert\Valid() annotation is defined, the first entity will not cascade validation for this property. So cascade_validation solve the problem.
82

A note to those using Symfony 3.0 and up: the cascade_validation option has been removed. Instead, use the following for embedded forms:

$builder->add('embedded_data', CustomFormType::class, array(
    'constraints' => array(new Valid()),
));

Sorry for adding to this old thread with a slightly off-topic answer (Symfony 3 vs. 2), but finding this information here would have saved me a few hours today.

6 Comments

awesome, thanks. The validated answer should also include yours.
This is the only option that works for me in Symfony 3.1.4. Following the documentation symfony.com/doc/current/form/embedded.html and adding @Assert\Valid() Constrait to Entity like it is written doesn't work. In my case I use FOSUserBundle and Custom User entity which is related to UserProfile (OneToOne). Thank you @cg.
With this, you might want to turn off error bubbling.
Damn, spent some time looking for this.... what's the purpose of the @Assert\Valid then -.- ... these kinds of hacks are a real turn off now we have to worry about validation in two additional places. Thank you very much @cg. !
Additionally I had to add use Symfony\Component\Validator\Constraints\Valid; at top of form definitoin file. Symfony 4 here.
|
36

According to form type documentation you can also use Valid constraint instead of cascade_validation option.

$builder->add('children', 'collection', array(
    'type'        => new ChildType(),
    'constraints' => array(new Valid()),
));

Example from the owner entity:

/**
 * @var Collection
 *
 * @ORM\OneToMany(targetEntity="Child", ...)
 * @Assert\Valid()
 */
private $children

1 Comment

And forced to do so since Symfony 3.0 (as mentioned by @cg)
4

I was looking for the exact same thing and here is what I found

http://symfony.com/doc/master/book/forms.html#forms-embedding-single-object

You need to tell the main entity to validate it's sub entities like so:

/**
 * @Assert\Type(type="AppBundle\Entity\Category")
 * @Assert\Valid()
 */
 private $subentity;

I've tested this on symfony 2.8 and it works.

Comments

3

are you using YML or Annotations?

I tried applying the cascade_validation option on my parent form class, but validation was still not occurring. After reading a little documentation, I went to app/config/config.yml and found that enable_annotations under framework->validation was set to true. Apparently, if this is true, the validation service no loner reads any validation.yml files. So I just changed it to false, and now the form is validating fine.

Comments

3

Belongs to Symfony 2.3

Working with embedded forms and validation groups could be quite painful: Annotation @Assert\Valid() doesen't work for me (without groups it is ok). Insert 'cascade_validation' => true on the DefaultOptions is the key. You do not need to repeat this on the ->add(). Take care: HTML 5 validation do not work together with validation groups.

Example:

A Collection of 2 Addresses. Both 1:1 unidirectional. Each with a different (!) validation group.

  class TestCollection{

//(...)

/**
 * @var string
 * @Assert\NotBlank(groups={"parentValGroup"})
 * @ORM\Column(name="name", type="string", length=255, nullable=true)
 */
protected $name;

/**
 * @var \Demo\Bundle\Entity\TestAddress  
 * @Assert\Type(type="Demo\Bundle\Entity\TestAddress")
 * @ORM\OneToOne(targetEntity="TestAddress",cascade={"persist","remove"},orphanRemoval=true)
 * @ORM\JoinColumn(name="billing_address__id", referencedColumnName="id")
 */
protected $billingAddress;

/**
 * @var \Demo\Bundle\Entity\TestAddress
 * @Assert\Type(type="Demo\Bundle\Entity\TestAddress")
 * @ORM\OneToOne(targetEntity="TestAddress",cascade={"persist","remove"}, orphanRemoval=true)
 * @ORM\JoinColumn(name="shipping_address__id", referencedColumnName="id")
 */ 
protected $shippingAddress;

//(...)
}

Address Entity

class TestAddress {
/**
 * @var string
 * @Assert\NotBlank(groups={"firstname"})
 * @ORM\Column(name="firstname", type="string", length=255, nullable=true)
 */
private $firstname;

/**
 * @var string
 * @Assert\NotBlank(groups={"lastname"})
 * @ORM\Column(name="lastname", type="string", length=255, nullable=true)
 */
private $lastname;

/**
 * @var string
 * @Assert\Email(groups={"firstname","lastname"}) 
 * @ORM\Column(name="email", type="string", length=255, nullable=true)
 */
private $email;

Address type - ability to change validation group

class TestAddressType extends AbstractType {    
protected $validation_group=['lastname'];//switch group

public function __construct($validation_group=null) {
    if($validation_group!=null) $this->validation_group=$validation_group;
}

public function buildForm(FormBuilderInterface $builder, array $options)
{
    //disable html5 validation: it suchs with groups 

    $builder
        ->add('firstname',null,array('required'=>false))
        ->add('lastname',null,array('required'=>false))
        ->add('email',null,array('required'=>false))
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Demo\Bundle\Entity\TestAddress',           
        'validation_groups' => $this->validation_group,
    ));
}
(...)

And last the CollectionType

class TestCollectionType extends AbstractType { 

public function buildForm(FormBuilderInterface $builder, array $options)
{   $builder
        ->add('name')           
        ->add('billingAddress', new TestAddressType(['lastname','firstname']))
        ->add('shippingAddress', new TestAddressType(['firstname']))            
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Demo\Bundle\Entity\TestCollection',
        'validation_groups' => array('parentValGroup'),         
        'cascade_validation' => true
    ));
}

//(...)    

Hope it helps..

1 Comment

This saved my day!! no.. it saved my year. God bless you :D Did you find a solution with @Assert\Valid() because 'cascade_validation' option is deprecated since version 2.8
2

You have to add validation_groups in your ProfiletType also. Validation is done in each form type separately based on their data_class if exists.

3 Comments

I added that in afterwards, it didn't help. But I found the fix. I had to add in 'cascade_validation' to the options array. Damn annoying because this isn't mentioned in the Symfony2 Docs anywhere.
@MrPablo I presume this is the cascade_validation option in 2.1?
@MrPablo Since seems that you have found an answer to your question, please consider to post your own answer with a full example of the use of "cascade_validation". This particular is too important to be relegated in a comment! Thanks
0

From my controller:

$form = $this->get('form.factory')
        ->createNamedBuilder('form_data', 'form', $item, array('cascade_validation' => true))
        ->add('data', new ItemDataType())
        ->add('assets', new ItemAssetsType($this->locale))
        ->add('contact', new ItemContactType())
        ->add('save', 'submit',
            array(
                'label' => 'Save',
                'attr' => array('class' => 'btn')
            )
        )
        ->getForm();

Fourth parametr in ::createNamedBuilder - array('cascade_validation' => true))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.