2017-09-23 57 views
1

我有一個控制器操作方法,它應該處理一個兩分裂的形式。每個表格只處理我的實體Workflow的一些屬性。提交第一個表單後,我可以創建並呈現沒有問題的第二個表單。現在的問題:Symfony 3 - 表單模型數據丟失屬性值,而不是字段

提交第二表單後,所有的值在第一種形式中設置的信息都不見了,這意味着當調用submit(或handleRequest不會在這裏做任何區別)實體對象僅持有的數據屬性設置爲第一種形式,甚至無法正確解析某些值。

這裏是控制器(帶有一些註釋):

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的屬性值?

+0

您可以發佈兩個'PraWorkflowStatesType'和'PraWorkflowTransitionsType':類,提交表單時保存通過工作流在當前會話和檢索它時,buildForm被稱爲2.時間?我的猜測是'PraWorkflowTransitionsType'不包含'PraWorkflowStatesType'的字段,所以在第二次提交後它永遠不會擁有第一個表單的數據。 –

+0

@JoryGeerts你是個天才!嘿Mcfly,還有什麼可以*每個表單只處理我的Entity'Workflow' *的一些屬性?意思是?當然'PraWorkflowTransitionsType'不包含'PraWorkflowStatesType'的字段!它甚至是問題標題的一部分*表格模型數據丟失屬性值,*** *不由字段***表示。 – goulashsoup

+0

@JoryGeerts現在你在第二次提交後說*它永遠不會有第一個表單的數據。*。當然,這可能是因爲我傳遞了一個包含所有必需值的對象,但Symfony在渲染表單時不會保存這些值,這是主要問題! – goulashsoup

回答

1

因爲表單再次僅以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數據集中到一些隱藏字段的下一個補丁請求

您必須修改secondForm一些hidden form type

3處理來自firstForm數據)在返回第二個表格之前設置會話中的數據

首先,您的實體將必須實現接口Serializable並聲明方法serialize d unserialize

那樣

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

方法serialize將被用於存儲它。

您可以在回來的方法unserialize

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

因爲你存儲整個實體到會話,這種解決方案會降低很多應用程序的性能

+0

1.所以symfony沒有辦法在響應/請求循環中保留對象。通過添加隱藏的領域本身?這就是問題的目的。 2.現在沒有關於請求查詢解決方案。 3.添加hiddin字段不是很動態,因爲實體和第一個表單可以更改。 4.出於某種原因,當將對象存儲在會話中時,我甚至不需要使用接口'Serializable'。 – goulashsoup

+0

1.不,因爲您提交了2個不同的請求,第二個不知道第一個請求,如果你想這樣做,你還必須提交第一個數據 2.我會添加它 3.在第二種形式中,您可以擴展第一種形式並動態地將其類型更改爲隱藏(並未對其進行測試,但理論上它應起作用) 4.很明顯,symfony現在有一個序列化程序可爲您完成工作:) – oktapodia

+0

但是3.點看起來很髒,你也可以做的是也許在你的實體中聲明一個getter,它獲得firstForm數據的數組。然後你可以使用一個子表單,如 //在'buildForm'方法內的secondFormType中 $ builder-> add('firstForm',FirstFormType :: class); 使用「禁用」或「隱藏」選項 – oktapodia

0

可以使用屬性映射。見mapped propertyallow_extra_fields

+0

你好,McFly?,你提示的鏈接說[[default:true']](https://symfony.com/doc/current/reference/forms/types/form .html#mapped)這意味着在我的例子中所有的字段都映射了,因爲我設置了''mapped'=> false'無處!當然'allow_extra_fields'也不會改變任何東西,直到我添加一些exra字段來保存我不喜歡的屬性值,然後我新添加[_unsatisfying solution_](https://stackoverflow.com/a/46601434/5233188).. – goulashsoup

+0

不需要粗魯,這只是一個長鏡頭。我只是試圖給你一個方向,對不起。問題:爲什麼你不能嵌套第二種形式?如果第二個值不爲零,它應該是令人滿意的。你可以有兩個使用相同實體的formtypes。 https://symfony.com/doc/current/form/form_collections.html – Marco

0

如何我可以抱提交$ secondFormPart當$ firstFormPart提交的$工作流的屬性值?

因此,這裏是我的(不滿意)解決方案:

  1. 我通過本屆會議作爲一個選項,以我的表單類爲$secondFormPart這是WorkflowTransitionsType類將它作爲一個參數時調用createForm的控制器的操作方法中:

    $secondFormPart = $this->createForm(WorkflowTransitionsType::class, $workflow, array(
        'session' => $this->get('session') 
    )); 
    
  2. 我會話保存爲012內部的私有財產

    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' 
         )); 
        } 
    }