【问题标题】:Symfony 3 - Form model data loses property values which are not represented by fieldsSymfony 3 - 表单模型数据丢失未由字段表示的属性值
【发布时间】:2018-03-05 00:50:32
【问题描述】:

我有一个控制器操作方法,它应该处理一个两拆分的表单。每个表单只处理我的实体Workflow 的几个属性。提交第一个表单后,我可以毫无问题地创建和渲染第二个表单。现在的问题:

提交第二个表单后,第一个表单中设置的所有值的信息都没有了,这意味着当调用submit(或handleRequest在这里没有任何区别)时,实体对象只保存属性的数据以第一种形式设置,它甚至无法正确解析某些值。

这是控制器(带有一些 cmets):

public function createWorkflowAction(Request $request, Project $project, Workflow $workflow = null) {   

    if(!$workflow) {
        $workflow = new Workflow($project);
    }

    $firstFormPart = $this->createForm(WorkflowStatesType::class, $workflow);

    // $firstFormPart->handleRequest($request);
    $firstFormPart->submit($request->get($firstFormPart->getName()), false);

    $secondFormPart = $this->createForm(WorkflowTransitionsType::class, $workflow);
    // secondFormPart is created correct with all values after submitting $firstFormPart and calling submit

    if($firstFormPart->isSubmitted() && $firstFormPart->isValid()) {
        return $this->render('@MyBundle/Workflow/workflow_edit_create_second_part.html.twig', array(
            'form'   => $secondFormPart->createView(),
        ));
        // This will render correctly with all values submitted in the $firstFormPart
    }

    $secondFormPart->submit($request->get($secondFormPart->getName()), false);
    // $secondFormPart->handleRequest($request);
    // HERE IS THE PROBLEM -> After submitting the $secondFormPart all property values set in the $firstFormPart are gone

    if($secondFormPart->isSubmitted() && $secondFormPart->isValid()) {
        dump($workflow);
        die();
    }

    return $this->render('@MyBundle/Workflow/workflow_edit_create_first_part.html.twig', array(
        'form' => $firstFormPart->createView(),
    ));
}

WorkflowStatesType:

class WorkflowStatesType extends AbstractType {

        /**
         * @var \Doctrine\ORM\Mapping\ClassMetadata
         */
        private $classMetadata;

        /**
         * WorkflowType constructor.
         * @param EntityManager $em
         */

        public function __construct(EntityManager $em) {
            $this->classMetadata = $em->getClassMetadata(Workflow::class);
        }

        public function buildForm(FormBuilderInterface $builder, array $options) {
            $builder
                ->setMethod('PATCH')
                ->add('name', TextType::class, array(
                    'label' => 'nameTrans',
                    'attr'  => array('maxLength' => $this->classMetadata->getFieldMapping('name')['length']),
                ))
                ->add('states',  CollectionType::class, array(
                    'entry_type'        => StateType::class,
                    'allow_add'         => true,
                    'error_bubbling'    => false,
                    'by_reference'      => false,
                    'label'             => 'workflowStatesTrans',
                ))
                ->add('next', SubmitType::class, array(
                    'label' => 'nextFormPartTrans',
                ));
        }

        public function configureOptions(OptionsResolver $resolver) {
            $resolver->setDefaults(array(
                'data_class'            => Workflow::class,
                'translation_domain'    => 'My_Bundle',
            ));
        }

    }

WorkflowTransitionsType:

class WorkflowTransitionsType extends AbstractType {

        /**
         * @var Workflow
         */
        private $workflow;

        /**
         * @var Session
         */
        private $session;

        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options) {

            /** @var Workflow $workflow */
            $this->workflow = $options['data'];

                $builder
                    ->setMethod('PATCH')
                    ->add('initialState', ChoiceType::class, array(
                        'choices'           => $this->workflow->getStates(),
                        'choice_label'      => function($state) {
                            return ($state && $state instanceof State) ? $state->getStatekey() : 'noVal';
                        },
                        'choice_value'      => function($state) {
                            return ($state && $state instanceof State) ? $state->getStatekey() : 'noVal';
                        },

                        // This combination of 'expanded' and 'multiple' implements a select box
                        'expanded'          => false,
                        'multiple'          => false,
                    ))
                    ->add('transitions', CollectionType::class, array(
                        'entry_type'        => TransitionType::class,
                        'allow_add'         => true,
                        'allow_delete'      => true,
                        'error_bubbling'    => false,
                        'by_reference'      => false,
                        'label'             => 'transitionsTrans',
                        'entry_options'     => array(
                            'states'    => $this->workflow->getStates(),
                        ),
                    ))
                    ->add('save', SubmitType::class, array(
                        'label'             => 'submitTrans',
                    ));
        }

        public function configureOptions(OptionsResolver $resolver) {
            $resolver->setDefaults(array(
                'data_class'            => Workflow::class,
                'translation_domain'    => 'My_Bundle',
            ));
            $resolver->setRequired(array(
                'session'
            ));
        }
    }

提交$secondFormPart时,如何保存$firstFormPart中提交的$workflow的属性值?

【问题讨论】:

  • 你能同时发布PraWorkflowStatesTypePraWorkflowTransitionsType吗?我的猜测是PraWorkflowTransitionsType不包含PraWorkflowStatesType的字段,所以第二次提交后永远不会有第一个表单的数据。
  • @JoryGeerts 你是个天才!嘿 Mcfly,每个表单只处理我的实体 Workflow 的几个属性还有什么意思?当然PraWorkflowTransitionsType 不包含PraWorkflowStatesType 的字段!它甚至是问题标题的一部分表单模型数据丢失了 字段不表示的属性值。
  • @JoryGeerts 现在你说在第二次提交之后它永远不会有第一个表单的数据。。当然可以,因为我传递了一个包含所有必要值的对象,但 Symfony 在渲染表单后不会保存这些值,顺便说一句,这是主要问题!
  • @JoryGeerts 我还是添加了表单类...

标签: php forms symfony symfony-forms


【解决方案1】:

提交$secondFormPart时,如何保存$firstFormPart中提交的$workflow的属性值?

所以这是我的(不满意的)解决方案:

  1. 我将当前会话作为$secondFormPart 的表单类的选项传递给WorkflowTransitionsType 类,并在Controller 操作方法中调用createForm 时将其作为参数传递:

    $secondFormPart = $this->createForm(WorkflowTransitionsType::class, $workflow, array(
        'session'   => $this->get('session')
    ));
    
  2. 我将会话保存为 WorkflowTransitionsType 类中的私有属性,将传递的工作流保存在当前会话中,并在提交表单时调用 buildForm 时检索它:

    class WorkflowTransitionsType extends AbstractType {
    
        /**
         * @var Workflow
         */
        private $workflow;
    
        /**
         * @var Session
         */
        private $session;
    
        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options) {
    
            /** @var Workflow $workflow */
            $this->workflow = $options['data'];
    
            /** @var Session $session */
            $this->session = $options['session'];
    
            // If the workflow is stored in the session we know that this method is called a 2. time!
            if($this->session->has($this->getBlockPrefix() . '_workflow')) $this->workflow = $this->session->get($this->getBlockPrefix() . '_workflow');
    
                $builder
                    ->setMethod('PATCH')
                    ->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
                        dump($event);
                        // This always gets called AFTER storing the workflow if it is present in the current session
                        $this->session->set($this->getBlockPrefix() . '_workflow', $this->workflow);
                    })
                    ->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
                        // Here we manipulating the passed workflow data by setting all previous values! 
                        $eventForm = $event->getForm();
    
                        /** @var Workflow $submitWorkflow */
                        $submitWorkflow = $eventForm->getData();
    
                        $submitWorkflow->setName($this->workflow->getName());
                        foreach($this->workflow->getStates() as $state) $submitWorkflow->addState($state);
    
                        $eventForm->setData($submitWorkflow);
                    })
                    ->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {
                        // After submitting the workflow object is no longer required!
                        $this->session->remove($this->getBlockPrefix() . '_workflow');
                    })
                    ->add('initialState', ChoiceType::class, array(
                        ...
                        // Didn´t change (look at my question)
                    ))
                    ->add('transitions', CollectionType::class, array(
                        ...
                        // Didn´t change (look at my question)
                    ))
                    ->add('save', SubmitType::class, array(
                        ...
                        // Didn´t change (look at my question)
                    ));
        }
    
        /**
         * {@inheritdoc}
         */
        public function configureOptions(OptionsResolver $resolver) {
            $resolver->setDefaults(array(
                'data_class'            => Workflow::class,
                'translation_domain'    => 'MyBundle',
            ));
            $resolver->setRequired(array(
                // This is necessary to prevent an error about an unknown option!
                'session'
            ));
        }
    }
    

【讨论】:

    【解决方案2】:

    您可以使用映射的属性。见mapped propertyallow_extra_fields

    【讨论】:

    • 你好,McFly?,你建议的链接是default: true,这意味着在我的情况下,所有字段都已映射,因为我没有设置'mapped' => false!当然allow_extra_fields 也不会改变任何东西,直到我添加一些额外的字段来保存我不喜欢的属性值,然后我新添加的unsatisfying solution...
    • 不必粗鲁,这只是一个远射。我只是想给你一个方向,对不起。问题:为什么不能嵌套第二种形式?如果第二个值不为空,它应该是令人满意的。您可以有两种使用相同实体的表单类型。 symfony.com/doc/current/form/form_collections.html
    【解决方案3】:

    由于再次提交表单时仅包含 secondForm 数据,因此您将丢失 firstForm 数据。

    您有 3 种方法来保留它们:

    1) 将 firstForm 中的数据设置到查询中

    // Insert that instead of the `return $this->render` for the second form
    $url = $this->generateUrl(
        $request->attributes->get('_route'),
        array_merge(
            $request->query->all(),
            array('secondForm' => true, 'name' => $workflow->getName(), 'states' => $workflow->getStates()) // change the param
        )
    );
    return $this->redirect($url);
    

    $secondFormPart = $this->createForm(WorkflowTransitionsType::class, $workflow);之前

    namestates 设置回$workflow 实体,在本例中,您可以检查查询变量secondForm 以了解第一个表单是否已提交

    2) 将 firstForm 中的数据设置到下一个带有隐藏字段的 PATCH 请求中

    您必须修改 secondForm 以使用 hidden form type 处理来自 firstForm 的数据

    3)在返回第二个表单之前设置会话中的数据

    首先,您的实体必须实现接口Serializable并声明方法serializeunserialize

    这样

    $this->get('session')->set('workflow', $workflow);
    

    方法serialize将用于存储它。

    你可以用unserialize的方法返回

    $session = $this->get('session');
    $workflow = new Workflow();
    $workflow->unserialize($session->get('workflow'));
    

    因为您将整个实体存储到会话中,此解决方案将大大降低您的应用程序的性能

    【讨论】:

    • 1.因此,symfony 无法在响应/请求周期中处理 HOLDING 对象,例如通过添加隐藏字段本身?这就是问题的目的。 2.现在没有关于请求查询的解决方案。 3.添加hiddin字段不是很动态,因为实体和第一个表单可以改变。 4. 出于某种原因,在会话中存储对象时,我什至不需要使用接口Serializable
    • 1.不,因为您要提交 2 个不同的请求,所以第二个不知道第一个请求,如果您想这样做,您还必须提交第一个数据 2. 我会添加它 3. 在第二种形式中,您可以扩展第一个表单并将类型动态更改为隐藏(没有测试它,但理论上它应该可以工作) 4. 显然,symfony 现在有一个序列化程序可以为您完成工作:)
    • 但是 3. 点看起来很脏,你也可以做的是在你的实体中声明一个 getter,它正在获取 firstForm 数据的数组。然后你可以在buildForm 方法 $builder->add('firstForm', FirstFormType::class); 中使用类似 // 在你的 secondFormType 中的子表单带有disablehidden 选项
    • 我现在添加了一个我已经拥有的解决方案(在前几天发布它时撒谎 =D),这正是我一直在寻找的东西,因为我不必更改控制器内部的任何东西。关于表单的一切都在表单内部。但我接受你的回答,因为你想出了多种可能的解决方案。 (我不知道的是我开始赏金时失去了所有50分并且没有给任何人,然后我会开始另一个赏金给你50分。下次我会更快......:| )
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多