Symfony 3 – данные модели модели теряют значения свойств, которые не представлены полями

У меня есть метод действий контроллера, который должен обрабатывать двухраздельную форму. Каждая форма обрабатывает только несколько свойств моего Entity Workflow . После отправки первой формы я могу создать и отобразить вторую форму без проблем. Теперь проблема:

После отправки второй формы информация обо всех значениях, установленных в первой форме, handleRequest что означает, что при вызове 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' )); } } 

Как я могу удерживать значения свойств $workflow представленные в $firstFormPart при отправке $secondFormPart ?

Поскольку форма снова отправляется с данными только второго типа, вы теряете данные firstForm.

У вас есть 3 способа сохранить их:

1) Установите данные из первой формы в запрос

 // 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) Установите данные из первого формата в следующий запрос PATCH с некоторым скрытым полем

Вы должны изменить secondForm для обработки данных из firstForm с некоторым скрытым типом формы

3) Установите данные в сеансе перед возвратом второй формы

Прежде всего, вашей сущности придется реализовать интерфейс Serializable и объявить метод serialize и unserialize

как это

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

serialize метод будет использоваться для его хранения.

Вы можете установить обратно с помощью метода unserialize

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

Поскольку вы храните весь объект в сеансе, это решение значительно снизит производительность вашего приложения

Вы можете использовать отображаемое свойство. Просмотреть сопоставленное свойство или allow_extra_fields

Как я могу удерживать значения свойств рабочего процесса $, представленные в $ firstFormPart при отправке $ secondFormPart?

Итак, вот мое (неудовлетворительное) решение:

  1. Я $secondFormPart текущий сеанс как вариант для моего класса формы для $secondFormPart который является классом WorkflowTransitionsType передавая его в качестве аргумента при вызове createForm внутри метода действия контроллера:

     $secondFormPart = $this->createForm(WorkflowTransitionsType::class, $workflow, array( 'session' => $this->get('session') )); 
  2. Я сохраняю сессию как частное свойство внутри класса WorkflowTransitionsType , сохраняю пройденный рабочий процесс в текущем сеансе и извлекаю его, когда buildForm получает вызов 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' )); } } 

вам нужно использовать РАЗЛИЧНЫЙ БЛОК PREFIX для каждой формы!

WorkflowStatesType:

 public function getBlockPrefix() { return 'workflow_states'; } 

WorkflowTransitionsType:

 public function getBlockPrefix() { return 'workflow_transition'; }