表单订阅者和“此表单不应包含额外字段”错误

时间:2016-09-05 09:55:56

标签: forms symfony dynamic form-fields

我正在使用symfony 2.3,显然,我不能使用here讨论的'allow_extra_fields'选项。

我有一个主要的表单类型,RegistrationStep1UserType:

/**
 * Class RegistrationStep1UserType
 * @package Evo\DeclarationBundle\Form\Type
 */
class RegistrationStep1UserType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('customer', new RegistrationStep1CustomerType(), [
                'label' => false,
                'data_class' => 'Evo\UserBundle\Entity\Customer',
                'cascade_validation' => true,
            ])
            ->add('declaration', 'evo_declaration_bundle_registration_step1_declaration_type', [
                'label' => false,
                'cascade_validation' => true,
            ])
        ;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Evo\UserBundle\Entity\User',
            'validation_groups' => false,
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'evo_declaration_bundle_registration_step1_user_type';
    }
}

此表单类型包括嵌入式表单类型(在“声明”字段中),RegistrationStep1DeclarationType,注册为服务:

/**
 * Class RegistrationStep1DeclarationType
 * @package Evo\DeclarationBundle\Form\Type
 */
class RegistrationStep1DeclarationType extends AbstractType
{
    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * @var EventSubscriberInterface
     */
    private $addBirthCountyFieldSubscriber;

    /**
     * RegistrationStep1DeclarationType constructor.
     * @param EntityManagerInterface $em
     * @param EventSubscriberInterface $addBirthCountyFieldSubscriber
     */
    public function __construct(EntityManagerInterface $em, EventSubscriberInterface $addBirthCountyFieldSubscriber)
    {
        $this->em = $em;
        $this->addBirthCountyFieldSubscriber = $addBirthCountyFieldSubscriber;
    }

    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('birthLastname', null, [
                'label' => 'Nom de naissance',
                'attr' => [
                    'required' => true,
                ],
            ])
            ->add('nationality', 'entity', [
                'label' => 'Nationalité',
                'class' => 'Evo\GeoBundle\Entity\Country',
                'property' => 'nationalityFr',
                'attr' => [
                    'required' => true,
                    'class' => 'selectpicker',
                ],
                'preferred_choices' => $this->fillPreferredNationalities(),
            ])
            ->add('birthCountry', 'entity', [
                'label' => 'Pays de naissance',
                'class' => 'Evo\GeoBundle\Entity\Country',
                'property' => 'nameFr',
                'empty_value' => '',
                'empty_data' => null,
                'attr' => [
                    'required' => true,
                    'class' => 'trigger-form-modification selectpicker',
                ],
                'preferred_choices' => $this->fillPreferredBirthCountries(),
            ])
            ->add('birthCity', null, [
                'label' => 'Ville de naissance',
                'attr' => [
                    'required' => true,
                ],
            ])
        ;

        $builder->get("birthCountry")->addEventSubscriber($this->addBirthCountyFieldSubscriber);
    }

    /**
     * @return array
     */
    private function fillPreferredNationalities()
    {
        $nationalities = $this->em->getRepository("EvoGeoBundle:Country")->findBy(["isDefault" => true]);

        return $nationalities;
    }

    /**
     * @return array|\Evo\GeoBundle\Entity\Country[]
     */
    private function fillPreferredBirthCountries()
    {
        $countries = $this->em->getRepository("EvoGeoBundle:Country")->findBy(["isDefault" => true]);

        return $countries;
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'required' => false,
            'data_class' => 'Evo\DeclarationBundle\Entity\Declaration',
            'validation_groups' => false,
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'evo_declaration_bundle_registration_step1_declaration_type';
    }
}

此嵌入式表单类型在“birthCountry”字段上添加了订阅服务器(也注册为服务,因为它需要注入EntityManager)。

目标是根据birthCountry选择列表的值动态添加或删除一个额外的字段(称为“birthCounty”)(注意这里的2个字段不同,“birthCountry”和“birthCounty”)。

以下是订阅者:

/**
 * Class AddBirthCountyFieldSubscriber
 * @package Evo\CalculatorBundle\Form\EventListener
 */
class AddBirthCountyFieldSubscriber  implements EventSubscriberInterface
{
    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * AddBirthCountyFieldSubscriber constructor.
     * @param EntityManagerInterface $em
     */
    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        return array(
            FormEvents::POST_SET_DATA => 'postSetData',
            FormEvents::POST_SUBMIT => 'postSubmit',
        );
    }

    /**
     * @param FormEvent $event
     */
    public function postSetData(FormEvent $event)
    {
        $birthCountry = $event->getData();
        $form = $event->getForm()->getParent();

        $this->removeConditionalFields($form);

        if ($birthCountry instanceof Country && true === $birthCountry->getIsDefault()) {
            $this->addBirthCountyField($form);
        }
    }

    /**
     * @param FormEvent $event
     */
    public function postSubmit(FormEvent $event)
    {
        $birthCountry = $event->getData();
        $form = $event->getForm()->getParent();

        if (!empty($birthCountry)) {
            $country = $this->em->getRepository("EvoGeoBundle:Country")->find($birthCountry);

            $this->removeConditionalFields($form);

            if ($country instanceof Country && true === $country->getIsDefault()) {
                $this->addBirthCountyField($form);
            }
        }
    }

    /**
     * @param FormInterface $form
     */
    private function addBirthCountyField(FormInterface $form)
    {
        $form
            ->add('birthCounty', 'evo_geo_bundle_autocomplete_county_type', [
                'label' => 'Département de naissance',
                'attr' => [
                    'required' => true,
                ],
            ])
        ;
    }

    /**
     * @param FormInterface $form
     */
    private function removeConditionalFields(FormInterface $form)
    {
        $form->remove('birthCounty');
    }
}

在视图中,当“birthCountry”选项列表发生更改时,它会触发对控制器的AJAX请求,控制器会处理请求并再次呈现视图(如documentation about dynamic form submission中所述):

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

if ($request->isXmlHttpRequest()) {
    $form->handleRequest($request);
} elseif ("POST" == $request->getMethod()) {
    [...]
}

问题如下:

当我对birthCountry选项列表进行更改并选择一个应该隐藏“birthCounty”字段的国家/地区时,表单会在没有该字段的情况下正确呈现,但会显示错误消息:

Ce formulaire ne doit pas contenir des champs supplémentaires.

this form should not contain extra fields (in english)

我尝试了很多不同的解决方案:

  • 向RegistrationStep1UserType和RegistrationStep1DeclarationType
  • 添加'validation_groups'选项
  • 向AddBirthCountyFieldSubscriber添加preSubmit事件,复制preSetData和postSubmit方法的逻辑
  • 甚至将'mapped' => false,添加到birthCounty字段会触发错误。非常令人惊讶

如果我在$form->getExtraData()

之后转储它,则$form->handleRequest($request);为空

但是在vendor \ symfony \ symfony \ src \ Symfony \ Component \ Form \ Extension \ Validator \ Constraints \ FormValidator中,我可以看到一个额外的字段

array(1) {
  ["birthCounty"]=>
  string(0) ""
}

这里:

// Mark the form with an error if it contains extra fields
    if (count($form->getExtraData()) > 0) {
        echo '<pre>';
        \Doctrine\Common\Util\Debug::dump($form->getExtraData());
        echo '</pre>';
        die();

        $this->context->addViolation(
            $config->getOption('extra_fields_message'),
            array('{{ extra_fields }}' => implode('", "', array_keys($form->getExtraData()))),
            $form->getExtraData()
        );
    }

我是否想念表单动态额外字段?

1 个答案:

答案 0 :(得分:0)

我没有分析所有问题但是,我想,你可以反转逻辑:总是添加该字段并在不满足条件时将其删除。

这样您就不需要在postSubmit执行操作(问题出在哪里)