Symfony2双嵌套动态表单字段

时间:2015-04-02 14:23:40

标签: php forms symfony

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

然后是第三个字段,它取决于第二个字段,它已经是一个动态字段。

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

<?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
}

现在的问题是:

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

我尝试了几种解决方法,但事实证明,一旦构建器准备就绪(即在事件监听器中添加时),表单字段选项无法被覆盖并且形成以后添加的字段不接受新事件监听器。

我真的必须解决这个问题。我错过了什么吗? Symfony2的Form引擎是否无法处理两个动态表单字段依赖关系?

有什么建议吗?

1 个答案:

答案 0 :(得分:10)

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

<?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
}

AJAX-stuff的功能与上面的文章链接

中的建议相同