Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony2 embedded forms using same property path - error bubbling problems

I have split up the user's account area into different forms, then for the registration form I am pulling these bits together like so:

class RegistrationFormType extends AbstractType
{

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('email', 'email', array(
            'label' => '* Email address:'
        ))
        ->add('account_personal', 'my_personalinfo_form', array(
            'property_path' => 'account'
        ))
        ->add('account_contact', 'my_contactinfo_form', array(
            'property_path' => 'account'
        ))
    ;
}

The problem is that error messages for account_personal are bubbling to the top of the form. For example "Please enter your first name" if first name is left blank in the personal info form. The 'personal' and 'contact' forms work fine in their own forms on their own pages.

The error messages for account_contact are fine and appear next to the correct fields.

HOWEVER, if I swap the two ->add bits about above (so have account_contact first) then the problem reverses; the error messages for account_personal work now appear fine next to their corresponding fields, but now the errors for account_contact get bubbled to the top!

Any suggestions much appreciated!

----- EDIT -----

Personal info form:

class PersonalInfoType extends AbstractType
{

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('title', 'text')
        ->add('first_name', 'text', array(
            'required' => true,
            'label' => '* First name:'
        ))
        ->add('last_name', 'text', array(
            'required' => true,
            'label' => '* Surname:'
        ))
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'My\UserBundle\Entity\Account',
        'validation_groups' => array('personalInfo')
    ));
}

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

Contact info form:

class ContactInfoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('postcode', 'text', array(
            'required' => true
        ))
        ->add('address_1', 'text', array(
            'required' => true
        ))
        ->add('address_2', 'text', array(
            'required' => false
        ))
        ->add('address_3', 'text', array(
            'required' => false
        ))
        ->add('town', 'text')
        ->add('phone_daytime', 'text', array(
            'required' => true
        ))
        ->add('phone_mobile', 'text', array(
            'required' => true
        ))
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'My\UserBundle\Entity\Account',
        'validation_groups' => array('contactInfo')
    ));
}

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

and the Account entity for completion:

/**
 * My\UserBundle\Entity\Account
 *
 * @ORM\Entity
 * @ORM\Table(
 *      name="accounts"
 * )
 */
class Account
{
/**
 * @ORM\Id
 * @ORM\Column(type="integer")
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @ORM\Column(type="string", length=25, nullable=true)
 * @Assert\Choice(choices = {"Mr", "Mrs", "Miss", "Ms", "Dr", "Prof"}, message = "Choose a valid title", groups={"personalInfo"})
 */
protected $title;

/**
 * @ORM\Column(type="string", length=150, nullable=true)
 * @Assert\NotBlank(message="Please enter your first name", groups={"personalInfo"})
 * @Assert\Length(max=150, maxMessage="null|Your first name must have less than {{ limit }} characters", groups={"personalInfo"})
 * @Assert\Regex(
 *     pattern="/\d/",
 *     match=false,
 *     message="Your name cannot contain a number"
 * )
 */
protected $first_name;

/**
 * @ORM\Column(type="string", length=150, nullable=true)
 * @Assert\NotBlank(message="Please enter your last name", groups={"personalInfo"})
 * @Assert\Length(max=150, maxMessage="null|Your last name must have less than {{ limit }} characters", groups={"personalInfo"})
 * @Assert\Regex(
 *     pattern="/\d/",
 *     match=false,
 *     message="Your name cannot contain a number"
 * )
 */
protected $last_name;

/**
 * @ORM\Column(type="string", length=255, nullable=true)
 * @MyAssert\Phone(message="Your daytime phone number is not valid", groups={"contactInfo"})
 */
protected $phone_daytime;

/**
 * @ORM\Column(type="string", length=255, nullable=true)
 * @MyAssert\MobilePhone(message="Your mobile phone number is not valid", groups={"contactInfo"})
 */
protected $phone_mobile;

/**
 * @ORM\Column(type="string", length=255)
 * @Assert\NotBlank(message="Please enter the first line of your address", groups={"contactInfo"}
 * @Assert\Length(max=250, maxMessage="null|The first line of your address must have less than {{ limit }} characters", groups={"contactInfo"})
 */
protected $address_1;

/**
 * @ORM\Column(type="string", length=255, nullable=true)
 * @Assert\Length(max=250, maxMessage="null|The second line of your address must have less than {{ limit }} characters", groups={"contactInfo"})
 */
protected $address_2;

/**
 * @ORM\Column(type="string", length=255, nullable=true)
 * @Assert\Length(max=250, maxMessage="null|The third line of your address must have less than {{ limit }} characters", groups={"contactInfo"})
 */
protected $address_3;

/**
 * @ORM\Column(type="string", length=45, nullable=true)
 * @Assert\NotBlank(message="Please enter your town", groups={"contactInfo"})
 * @Assert\Length(max=45, maxMessage="null|Your town name must have less than {{ limit }} characters", groups={"contactInfo"})
 */
protected $town;

/**
 * @ORM\Column(type="string", length=45, nullable=true)
 * @Assert\NotBlank(message="Please enter your postcode", groups={"contactInfo"})
 * @MyAssert\Postcode(message="Invalid postcode entered", groups={"contactInfo"})
 */
protected $postcode;

 ... etc
like image 744
lopsided Avatar asked Oct 07 '22 14:10

lopsided


1 Answers

The issue is down to using the same property path twice in one form. I raised an issue here:

https://github.com/symfony/symfony/issues/5578

On which I posted this solution:

I came up with a different tact of creating a new FormType to contain the 2 embedded forms using virtual fields:

class RegistrationAccountFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('account_personal', 'my_personalinfo_form', array(
            'data_class' => 'My\UserBundle\Entity\Account',
            'virtual' => true
        ))
        ->add('account_contact', 'my_contactinfo_form', array(
            'data_class' => 'My\UserBundle\Entity\Account',
            'virtual' => true
        ))
    ;
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'My\UserBundle\Entity\Account'
    ));
}

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

and then replaced these fields in the main RegistrationFormType with

->add('account', new RegistrationAccountFormType)

This seems like the 'proper' way of doing this, it keeps the property_paths unique within the form and no property_path changes required in the sub-forms either.

like image 56
lopsided Avatar answered Oct 10 '22 01:10

lopsided