Symfony2 双嵌套动态表单字段 [英] Symfony2 double nested dynamic form fields

查看:27
本文介绍了Symfony2 双嵌套动态表单字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有两个分层动态字段的 Symfony2 表单.第一层用表单事件实现记录的方式没有问题:http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms

然后是第三个字段,它依赖于已经是动态字段的第二个字段.

为了演示这个问题,这里是我的剥离代码:

'AppBundle:公园','财产' =>'识别名称','标签' =>'公园','必需' =>真的,'invalid_message' =>'选择一个公园','占位符' =>'请选择',))//只是 $builder->get('facility')->addEventListener 的占位符,用来绑定一些东西//我知道,这只是我问题的一个症状->add('设施', '选择', 数组('选择' =>大批(),'扩展' =>真的,'多个' =>错误的,'标签' =>'设施','必需' =>错误的,'invalid_message' =>'先选公园','占位符' =>'请先选择一个公园',))//其他字段;$formModifierPark = 函数(FormInterface $form,Park $park = null){//用所需的实体类型覆盖设施字段$form->add('设施', '实体', 数组('类' =>'AppBundle:设施','财产' =>'识别名称','选择' =>null === $park ?数组() : $park->getFacilities(),'标签' =>'设施','必需' =>真的,'invalid_message' =>'选择设施','占位符' =>null === $park ?'请先选择一个公园' : '请选择',));};$formModifierFacility = function (FormInterface $form, Facility $facility = null) {$form->add('facilityStatuscode', 'entity', array('类' =>'AppBundle:FacilityStatuscode','财产' =>'识别名称','选择' =>空 === $设施?array() : $facility->getFacilityStatuscodeType()->getFacilityStatuscodes(),'标签' =>'状态码','必需' =>空 === $设施?假:真,'invalid_message' =>'选择一个状态码','占位符' =>空 === $设施?'请先选择一个设施' : '请选择',));};$builder->addEventListener(FormEvents::PRE_SET_DATA,函数 (FormEvent $event) 使用 ($formModifierPark) {$formModifierPark($event->getForm(), $event->getData()->getPark());});$builder->get('park')->addEventListener(FormEvents::POST_SUBMIT,函数 (FormEvent $event) 使用 ($formModifierPark) {$formModifierPark($event->getForm()->getParent(), $event->getForm()->getData());});$builder->addEventListener(FormEvents::PRE_SET_DATA,函数 (FormEvent $event) 使用 ($formModifierFacility) {$formModifierFacility($event->getForm(), $event->getData()->getFacility());});$builder->get('facility')->addEventListener(FormEvents::POST_SUBMIT,函数 (FormEvent $event) 使用 ($formModifierFacility) {$formModifierFacility($event->getForm()->getParent(), $event->getForm()->getData());});}//更多代码}

现在的问题是:

使用 $builder->get('facility')->addEventListener(FormEvents::POST_SUBMIT,... 设置的事件监听器此时丢失,设施字段被覆盖由另一个事件监听器.

我尝试了几种解决方法,但结果证明,一旦构建器准备就绪(即添加到事件侦听器中时),表单字段选项不能被覆盖,并且表单后来添加的字段不接受新的事件侦听器.

我真的必须解决这个问题.我错过了什么吗?Symfony2 的表单引擎是不是不能处理两层动态表单域依赖?

有什么建议吗?

解决方案

感谢 dmnptr 从他的第一条评论中提供的链接 (http://showmethecode.es/php/symfony/symfony2-4-dependent-forms/),我可以解决我的问题.诀窍是,将事件绑定到整个表单而不是某些字段(并且绑定到 PRE_SUBMIT 而不是 POST_SUBMIT).所以我的表单类现在看起来像这样:

'AppBundle:公园','财产' =>'识别名称','标签' =>'公园','必需' =>真的,'invalid_message' =>'选择一个公园','占位符' =>'请选择',))//其他字段;$addFacilityForm = function (FormInterface $form, $park_id) {//在这里使用 Park 实体会更容易,//但在 PRE_SUBMIT 事件中获取它并非易事$form->add('设施', '实体', 数组('类' =>'AppBundle:设施','财产' =>'识别名称','标签' =>'设施','必需' =>真的,'invalid_message' =>'选择设施','占位符' =>null === $park_id ?'请先选择一个公园' : '请选择','query_builder' =>功能(FacilityRepository $repository)使用($park_id){//这是获得正确选项的诀窍返回 $repository->createQueryBuilder('f')->innerJoin('f.park', 'p')->where('p.id = :park')->setParameter('park', $park_id);}));};$builder->addEventListener(FormEvents::PRE_SET_DATA,函数 (FormEvent $event) 使用 ($addFacilityForm) {$park = $event->getData()->getPark();$park_id = $park ?$park->getId() : null;$addFacilityForm($event->getForm(), $park_id);});$builder->addEventListener(FormEvents::PRE_SUBMIT,函数 (FormEvent $event) 使用 ($addFacilityForm) {$data = $event->getData();$park_id = array_key_exists('park', $data) ?$data['park'] : null;$addFacilityForm($event->getForm(), $park_id);});$addFacilityStatuscodeForm = function (FormInterface $form, $facility_id) {$form->add('facilityStatuscode', 'entity', array('类' =>'AppBundle:FacilityStatuscode','财产' =>'识别名称','标签' =>'状态码','必需' =>真的,'invalid_message' =>'选择一个状态码','占位符' =>null === $facility_id ?'请先选择一个设施' : '请选择','query_builder' =>功能 (FacilityStatuscodeRepository $repository) 使用 ($facility_id) {//有点复杂,这就是这个模型的工作原理返回 $repository->createQueryBuilder('fs')->innerJoin('fs.facilityStatuscodeType', 'fst')->innerJoin('AppBundle:Facility', 'f', 'WITH', 'f.facilityStatuscodeType = fst.id')->where('f.id = :facility_id')->setParameter('facility_id', $facility_id);}));};$builder->addEventListener(FormEvents::PRE_SET_DATA,函数 (FormEvent $event) 使用 ($addFacilityStatuscodeForm) {$facility = $event->getData()->getFacility();$facility_id = $facility ?$facility->getId() : null;$addFacilityStatuscodeForm($event->getForm(), $facility_id);});$builder->addEventListener(FormEvents::PRE_SUBMIT,函数 (FormEvent $event) 使用 ($addFacilityStatuscodeForm) {$data = $event->getData();$facility_id = array_key_exists('facility', $data) ?$数据['设施']:空;$addFacilityStatuscodeForm($event->getForm(), $facility_id);});}//更多代码}

AJAX 的东西然后像上面文章链接中建议的那样工作

I have a Symfony2 Form with two layered dynamic fields. The first Layer is no problem implementing the documented way with form events: http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#dynamic-generation-for-submitted-forms

But then comes a third field, which depends on the second field, which is already a dynamic field.

To demonstrate the problem, here is my stripped code:

<?php
class ServiceeventType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('park', 'entity', array(
                'class' => 'AppBundle:Park',
                'property' => 'identifyingName',
                'label' => 'Park',
                'required' => true,
                'invalid_message' => 'Choose a Park',
                'placeholder' => 'Please choose',
            ))
            // just a placeholder for the $builder->get('facility')->addEventListener to have something to bind to
            // I'm aware, that this is just a symptom of my problem
            ->add('facility', 'choice', array(
                'choices' => array(),
                'expanded' => true,
                'multiple' => false,
                'label' => 'Facility',
                'required' => false,
                'invalid_message' => 'Choose a Park first',
                'placeholder' => 'Please choose a Park first',
            ))
            // other fields
        ;

        $formModifierPark = function (FormInterface $form, Park $park = null) {
            // overwrite the facility field with the desired entity type
            $form->add('facility', 'entity', array(
                'class' => 'AppBundle:Facility',
                'property' => 'identifyingName',
                'choices' => null === $park ? array() : $park->getFacilities(),
                'label' => 'Facility',
                'required' => true,
                'invalid_message' => 'Choose a Facility',
                'placeholder' => null === $park ? 'Please choose a Park first' : 'Please choose',
            ));
        };

        $formModifierFacility = function (FormInterface $form, Facility $facility = null) {
            $form->add('facilityStatuscode', 'entity', array(
                'class' => 'AppBundle:FacilityStatuscode',
                'property' => 'identifyingName',
                'choices' => null === $facility ? array() : $facility->getFacilityStatuscodeType()->getFacilityStatuscodes(),
                'label' => 'Statuscode',
                'required' => null === $facility ? false : true,
                'invalid_message' => 'Choose a Statuscode',
                'placeholder' => null === $facility ? 'Please choose a Facility first' : 'Please choose',
            ));
        };

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifierPark) {
                $formModifierPark($event->getForm(), $event->getData()->getPark());
            }
        );
        $builder->get('park')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifierPark) {
                $formModifierPark($event->getForm()->getParent(), $event->getForm()->getData());
            }
        );

        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($formModifierFacility) {
                $formModifierFacility($event->getForm(), $event->getData()->getFacility());
            }
        );
        $builder->get('facility')->addEventListener(
            FormEvents::POST_SUBMIT,
            function (FormEvent $event) use ($formModifierFacility) {
                $formModifierFacility($event->getForm()->getParent(), $event->getForm()->getData());
            }
        );
    }

    // more code
}

The problem is now:

The event-listener set with $builder->get('facility')->addEventListener(FormEvents::POST_SUBMIT,… gets lost at the moment, the facility-field is overwritten by the other event-listener.

I tried several workarounds, but it turns out, that form field options cannot be overridden and form later added fields don't accept new event listeners, once the builder is ready (i.e. when added inside an event listener).

I really have to solve this. Am I missing something? Is Symfony2's Form engine not able to handle two layered dynamic form field dependencies?

Any suggestions?

解决方案

Thanks to dmnptr's link from his first comment (http://showmethecode.es/php/symfony/symfony2-4-dependent-forms/), I could solve the problem for my case. The trick is, to bind the events to the whole form and not to certain fields (and to PRE_SUBMIT instead of POST_SUBMIT). So my form class now looks like this:

<?php
class ServiceeventType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('park', 'entity', array(
                'class' => 'AppBundle:Park',
                'property' => 'identifyingName',
                'label' => 'Park',
                'required' => true,
                'invalid_message' => 'Choose a Park',
                'placeholder' => 'Please choose',
            ))
            // other fields
        ;

        $addFacilityForm = function (FormInterface $form, $park_id) {
            // it would be easier to use a Park entity here,
            // but it's not trivial to get it in the PRE_SUBMIT events
            $form->add('facility', 'entity', array(
                'class' => 'AppBundle:Facility',
                'property' => 'identifyingName',
                'label' => 'Facility',
                'required' => true,
                'invalid_message' => 'Choose a Facility',
                'placeholder' => null === $park_id ? 'Please choose a Park first' : 'Please Choose',
                'query_builder' => function (FacilityRepository $repository) use ($park_id) {
                    // this does the trick to get the right options
                    return $repository->createQueryBuilder('f')
                        ->innerJoin('f.park', 'p')
                        ->where('p.id = :park')
                        ->setParameter('park', $park_id)
                    ;
                }
            ));
        };
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addFacilityForm) {
                $park = $event->getData()->getPark();
                $park_id = $park ? $park->getId() : null;
                $addFacilityForm($event->getForm(), $park_id);
            }
        );
        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addFacilityForm) {
                $data = $event->getData();
                $park_id = array_key_exists('park', $data) ? $data['park'] : null;
                $addFacilityForm($event->getForm(), $park_id);
            }
        );

        $addFacilityStatuscodeForm = function (FormInterface $form, $facility_id) {
            $form->add('facilityStatuscode', 'entity', array(
                'class' => 'AppBundle:FacilityStatuscode',
                'property' => 'identifyingName',
                'label' => 'Statuscode',
                'required' => true,
                'invalid_message' => 'Choose a Statuscode',
                'placeholder' => null === $facility_id ? 'Please choose a Facility first' : 'Please Chosse',
                'query_builder' => function (FacilityStatuscodeRepository $repository) use ($facility_id) {
                    // a bit more complicated, that's how this model works
                    return $repository->createQueryBuilder('fs')
                        ->innerJoin('fs.facilityStatuscodeType', 'fst')
                        ->innerJoin('AppBundle:Facility', 'f', 'WITH', 'f.facilityStatuscodeType = fst.id')
                        ->where('f.id = :facility_id')
                        ->setParameter('facility_id', $facility_id)
                    ;
                }
            ));
        };
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            function (FormEvent $event) use ($addFacilityStatuscodeForm) {
                $facility = $event->getData()->getFacility();
                $facility_id = $facility ? $facility->getId() : null;
                $addFacilityStatuscodeForm($event->getForm(), $facility_id);
            }
        );
        $builder->addEventListener(
            FormEvents::PRE_SUBMIT,
            function (FormEvent $event) use ($addFacilityStatuscodeForm) {
                $data = $event->getData();
                $facility_id = array_key_exists('facility', $data) ? $data['facility'] : null;
                $addFacilityStatuscodeForm($event->getForm(), $facility_id);
            }
        );


    }

    // more code
}

The AJAX-stuff then works like suggested in the article link above

这篇关于Symfony2 双嵌套动态表单字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆