Symfony 3 - 表单模型数据丢失 属性 字段未表示的值
Symfony 3 - Form model data loses property values which are not represented by fields
我有一个控制器操作方法,它应该处理两个拆分的表单。每个表单只处理我的实体 Workflow
的几个属性。提交第一个表单后,我可以毫无问题地创建和呈现第二个表单。现在的问题:
提交第二个表单后,第一个表单中设置的所有值的信息都没有了,这意味着调用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
的 属性 值?
因为再次提交表单时只有 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);
将 name
和 states
设置回 $workflow
实体,在本例中您可以检查查询变量 secondForm
以了解是否提交了第一个表单与否
2) 将来自 firstForm 的数据设置为带有一些隐藏字段的下一个 PATCH 请求
您必须修改 secondForm 以使用一些 hidden form type
来处理来自 firstForm 的数据
3)在返回第二个表单之前在session中设置数据
首先,您的实体必须实现接口 Serializable
并声明方法 serialize
和 unserialize
那样
$this->get('session')->set('workflow', $workflow);
方法serialize
将用于存储它。
你可以通过unserialize
的方法设置回退
$session = $this->get('session');
$workflow = new Workflow();
$workflow->unserialize($session->get('workflow'));
因为您将整个实体存储到会话中,所以此解决方案会大大降低应用程序的性能
您可以使用 属性 映射。参见 mapped property or allow_extra_fields
How can I hold the property values of the $workflow submitted in the $firstFormPart when submitting the $secondFormPart?
所以这是我的(不满意的)解决方案:
我将当前会话作为 class 表单的一个选项传递给 $secondFormPart
,即 WorkflowTransitionsType
class 并将其作为在 Controller 操作方法中调用 createForm
时的参数:
$secondFormPart = $this->createForm(WorkflowTransitionsType::class, $workflow, array(
'session' => $this->get('session')
));
我在WorkflowTransitionsType
class中将会话保存为私有属性,将传递的工作流保存在当前会话中,并在[=16]时检索=] 被调用 2. 提交表单时的时间:
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'
));
}
}
我有一个控制器操作方法,它应该处理两个拆分的表单。每个表单只处理我的实体 Workflow
的几个属性。提交第一个表单后,我可以毫无问题地创建和呈现第二个表单。现在的问题:
提交第二个表单后,第一个表单中设置的所有值的信息都没有了,这意味着调用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
的 属性 值?
因为再次提交表单时只有 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);
将 name
和 states
设置回 $workflow
实体,在本例中您可以检查查询变量 secondForm
以了解是否提交了第一个表单与否
2) 将来自 firstForm 的数据设置为带有一些隐藏字段的下一个 PATCH 请求
您必须修改 secondForm 以使用一些 hidden form type
来处理来自 firstForm 的数据3)在返回第二个表单之前在session中设置数据
首先,您的实体必须实现接口 Serializable
并声明方法 serialize
和 unserialize
那样
$this->get('session')->set('workflow', $workflow);
方法serialize
将用于存储它。
你可以通过unserialize
$session = $this->get('session');
$workflow = new Workflow();
$workflow->unserialize($session->get('workflow'));
因为您将整个实体存储到会话中,所以此解决方案会大大降低应用程序的性能
您可以使用 属性 映射。参见 mapped property or allow_extra_fields
How can I hold the property values of the $workflow submitted in the $firstFormPart when submitting the $secondFormPart?
所以这是我的(不满意的)解决方案:
我将当前会话作为 class 表单的一个选项传递给
$secondFormPart
,即WorkflowTransitionsType
class 并将其作为在 Controller 操作方法中调用createForm
时的参数:$secondFormPart = $this->createForm(WorkflowTransitionsType::class, $workflow, array( 'session' => $this->get('session') ));
我在
WorkflowTransitionsType
class中将会话保存为私有属性,将传递的工作流保存在当前会话中,并在[=16]时检索=] 被调用 2. 提交表单时的时间: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' )); } }