Проблема
У меня есть две сущности, одна из которых называется Question
, которая может быть самореферентной, она связана с QuestionSubQuestions
(необходимо было добавить несколько дополнительных такие поля, как filter
), поэтому у него может быть много вопросов, но во многих Questions
могут использоваться те же вопросы, что и у детей. Намерение этого состоит в том, чтобы иметь единую сущность, которая может иметь много Children
(Вопросы), и повторно использовать существующие.
Проблема, с которой я сталкиваюсь, заключается в том, что, когда я добавляю существующий Вопрос в качестве дочернего элемента, он создает новую запись Question
в базе данных вместо использования существующей записи для ассоциации.
У меня есть веб-интерфейс, где пользователь может выбрать список существующих вопросов и добавить его в качестве ребенка к основному. Форма POST передает всю информацию (включая идентификатор сущности) и doctrine обрабатывает ее самостоятельно.
Все сохраняется и удаляется правильно при добавлении несуществующих вопросов (новых), но при выборе существующего допускает указанную ошибку. Но этого не происходит, когда Вопрос обновляется, doctrine правильно сохраняет существующие отношения и не создаются дублированные записи.
Кроме того, контроллер не содержит ничего особенного, но при выгрузке По данным формы я вижу, что добавленный вопрос не имеет свойства __isInitialized__
, поэтому я могу предположить, что doctrine действительно не знает, что эта сущность уже существует. Вы можете видеть в дампе (см. Раздел кода), что у дочернего элемента с индексом 0 есть параметр, а у дочернего с индексом 1.
Вопрос
Итак Как я могу это исправить? Может быть, есть способ проверить, существует ли сущность при обработке данных формы, и снова присоединить сущность к EntityManager? Я знаю, что могу сделать Слушателя для этого, но я не знаю, хорошая ли это практика.
Любая помощь будет оценена.
Фактический код
Дамп данных формы:
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());
}