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