Проблема
У меня есть две сущности, одна из которых называется Question
, которая может ссылаться на себя, она связана с QuestionSubQuestions
(необходимо было добавить некоторые дополнительные поля, такие как filter
), поэтому у нее может быть много вопросов, но те же вопросы могут использоваться как дочерние в много Questions
. Цель этого состоит в том, чтобы иметь единую сущность, которая может иметь много Children
(Вопросов) и повторно использовать существующие.
Проблема, с которой я сталкиваюсь, заключается в том, что когда я добавляю существующий вопрос в качестве дочернего, он создает новую запись Question
в базе данных вместо использования существующей записи для ассоциации.
У меня есть веб-интерфейс, в котором пользователь может выбрать список существующих вопросов и добавить его в качестве дочернего к основному. Форма POST всю информацию (включая идентификатор объекта) и доктрина обрабатывают ее сами по себе.
При добавлении несуществующих вопросов (новых) все сохраняется и удаляется корректно, но при выборе существующего выдает указанную ошибку. Но этого не происходит, когда вопрос обновляется, доктрина корректно сохраняет существующие отношения, а создание дублирующихся записей не появляется.
Кроме того, контроллер не содержит ничего особенного, но при выводе данных формы я вижу, что добавленный вопрос не имеет свойства __isInitialized__
, поэтому я могу предположить, что доктрина на самом деле не знает, что эта сущность уже существует. Вы можете видеть в дампе (см. раздел кода), что дочерний элемент с индексом 0 имеет параметр, а дочерний элемент с индексом 1 — нет.
Вопрос
Итак, как я могу это исправить? Может быть, есть способ проверить, существует ли объект при обработке данных формы, и снова прикрепить объект к EntityManager? Я знаю, что могу сделать для этого Listener, но я не знаю, является ли это хорошей практикой.
Любая помощь будет оценена по достоинству.
Действительный код
Дамп данных формы:
Question^ {#1535 ▼
-id: 56
-question: "TestB1"
-children: PersistentCollection^ {#1562 ▼
-owner: Question^ {#1535}
-association: array:15 [ …15]
-em: EntityManager^ {#238 …11}
-isDirty: true
#collection: ArrayCollection^ {#1563 ▼
-elements: array:3 [▼
0 => QuestionSubQuestion^ {#1559 ▼
-question: Question^ {#1535}
-subQuestion: Question^ {#1592 ▼
+__isInitialized__: true
-id: "57"
-question: "P-1"
}
-filter: "affirmative"
}
1 => QuestionSubQuestion^ {#2858 ▼
-question: Question^ {#1535}
-subQuestion: Question^ {#2863 ▼
-id: "57"
-question: "P-1"
}
-filter: "negative"
}
]
}
#initialized: true
}
}
Question.php
class Question
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
...
/**
* @var ArrayCollection
* @ORM\OneToMany(targetEntity="QuestionSubQuestion", mappedBy="question", fetch="EAGER" ,cascade={"persist"}, orphanRemoval=true)
*/
private $children;
...
/**
* @param QuestionSubQuestion $children
*/
public function addChild(QuestionSubQuestion $children): void
{
if ($this->children->contains($children)) {
return;
}
$children->setQuestion($this);
$this->children->add($children);
}
/**
* @param mixed $children
*/
public function removeChild(QuestionSubQuestion $children): void
{
if (!$this->children->contains($children)) {
return;
}
$this->children->removeElement($children);
// needed to update the owning side of the relationship!
$children->setSubQuestion(null);
}
}
QuestionSubQuestion.php
class QuestionSubQuestion
{
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Question", inversedBy="children", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $question;
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Question", cascade={"persist"})
* @ORM\JoinColumn(nullable=false)
*/
private $subQuestion;
/**
* @ORM\Id
* @ORM\Column(type="string")
* @ORM\JoinColumn(nullable=false)
*/
private $filter;
}
Форма QuestionType.php
class QuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('question')
->add('children', CollectionType::class, [
'entry_type' => SubQuestionEmbeddedForm::class,
'allow_add' => true,
'allow_delete' => true,
'label' => false,
'by_reference' => false,
'prototype_name' => '__subQuestion__',
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Question::class,
));
}
}
Встроенная форма SubQuestionEmbeddedForm.php
class SubQuestionEmbeddedForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('subQuestion', SubQuestionType::class)
->add('filter', HiddenType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => QuestionSubQuestion::class,
));
}
}
SubQuestionType.php
class SubQuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', HiddenType::class, [
'required' => false,
])->add('question', TextType::class, [
'label' => false,
])
->add('country', HiddenType::class)
->add('category', HiddenType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Question::class,
));
}
}
Редактировать контроллер
$question = $questionRepository->find($questionId);
$form = $this->createForm(QuestionType::class, $question);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$question = $form->getData();
$questionRepository->save($question);
return $this->redirect($request->getUri());
}
children
, поэтому я могу добавить новый дочерний элемент с новыми уникальными данными. Но у меня также есть список выбора, созданный с помощью twig, где я выбираю идентификатор нужного вопроса и вручную с помощью Javascript добавляю вопрос в коллекцию, используя прототип с другой кнопки, а затем передаю данные на входы, даже идентификатор . Затем я блокирую поля, чтобы избежать манипулирования данными, поскольку они должны содержать те же данные, что и исходный вопрос. - person Williams A.   schedule 21.04.2020