Я провел некоторое исследование, и после прочтения этого и этого (и всех связанных вопросов) я все еще не могу понять, что является правильным способом обновить многие отношения во многих отношениях в Доктрине Symonfy 2. Он чувствует, что должен быть очень простой способ сделать это, которого я до сих пор не нашел.
У меня есть два объекта:
class student_main { /** * @ORM\ManyToMany(targetEntity="support_log", inversedBy="student_main") * @ORM\JoinTable(name="support_log_student") **/ private $support_log;
а также
class support_log { /** * @ORM\ManyToMany(targetEntity="student_main", mappedBy="support_log") **/ private $student;
Я хочу начать с support_log
. В контроллере в действии обновления у меня есть что-то вроде этого:
if ($editForm->isValid()) { //add the relationship the user added foreach($students as $student){ if(!$em->getRepository('mybundle:student_main')->hasSupportLog($entity,$student)){ $entity->addstudent_main($student);//* } } $em->persist($entity); $em->flush(); return $this->redirect($this->generateUrl('support_log_edit', array('id' => $id))); }
Конечно, как говорится в документации к доктрине, я изменил эту функцию (addstudent_main) соответственно:
public function addstudent_main(student_main $student) { $student->addsupport_log($this); // the important addition $this->student[] = $student; }
-public function addstudent_main(student_main $student) { $student->addsupport_log($this); // the important addition $this->student[] = $student; }
Это прекрасно работает, мой вопрос связан скорее с удалением отношений. В форме есть мультиселекция, и пользователь может выбрать некоторых студентов, которые уже связаны, а некоторые – нет. Он чувствует, что должен быть автоматический способ сделать это, но вместо этого мне пришлось делать много кода.
В контроллере, чуть выше кода, который я написал ранее, я сказал, что:
//delete all old relationship foreach($idsldstudents as $idst){ //I take Id's because the doctrine collection is updating always.. $stu=$em->getRepository('MyBundle:student_main')->find($idst); $stu->deletesupport_log($entity);//I had to create that method (in the entity, I do "$this->support_log->removeElement($support_log)") $em->persist($stu); $em->flush(); }
Я удаляю все отношения рассматриваемого объекта (конечно, заботясь о двунаправленных отношениях, поэтому его нужно удалить сначала на другой стороне), а затем будут добавлены те, которые выбран пользователь.
Есть и другие способы сделать это, но я не нашел простой. Во всех них у меня такие же проблемы:
Есть ли способ сделать то, что заботится об этих двух проблемах автоматически? (У меня есть сильное чувство, что должно быть – может быть, с лучшими заявлениями об отношениях? – вот почему я спрашиваю).
заранее спасибо
Изменить: у моей формы нет ничего особенного, я думаю, что даже не прикасался к сгенерированному коду. Он отображает многозадачность, которую я хочу, по умолчанию Symfony2, где вам нужно использовать клавишу ctrl, чтобы выбрать более одного. Вот код:
public function buildForm(FormBuilder $builder, array $options) { $builder ->add('student') ... ; }
Ключ полагается здесь?
До сих пор (и чтобы вовремя не оставлять вопрос без ответа), похоже, что нет «простого способа, которого я еще не нашел» для этого. Это будет ответ на мой вопрос, согласно комментариям.
Но код может быть улучшен и сделать его более элегантным благодаря усовершенствованиям последнего комментария. Если на уровне сущности мы имеем это: gist.github.com/3121916 (из комментария)
Затем код в контроллере можно немного уменьшить:
$editForm->bindRequest($request); if ($editForm->isValid()) { //delete all old relationships, we can go from student: foreach($em->getRepository('mybundle:student_main')->findAll() as $oldstudent) { $oldstudent->removeSupportLog($entity); //if they are related, the relationship will be deleted. //(check the code from the url) } //add the relationship the user added in the widget $students=$entity->getStudent(); foreach($students as $student) { $entity->addstudent_main($student); } $em->persist($entity); $em->flush(); return $this->redirect($this->generateUrl('support_log_edit', array('id' => $id))); }
Это все еще не «магическое» решение Symfony, которое я ожидал, но пока что лучшее, что я могу сделать (возможно, группировать этот код внутри функции в репозитории, чтобы сделать его более элегантным).
Если у вас есть лучшие идеи, я все уши.
Я представляю свое решение всем, кто ищет решение.
Я использую Symfony 2.5.
У моего объекта «Почта» есть много-много двунаправленных.
контроллер:
public function editPostAction(Post $post, Request $request) { $form = $this->createForm(new NewPost(), $post, [ 'action' => $this->generateUrl('admin_edit_post', ['id' => $post->getId()]) ]); $form->handleRequest($request); if( $form->isSubmitted() ) { $this->get('post.repository')->update(); } return $this->render('BlogJakonAdminPanelBundle:Post:post-edit.html.twig', array( 'form' => $form->createView(), 'errors' => $form->getErrors(true) )); }
Я связываю свою сущность, следуя маршрутизации:
admin_edit_post: path: /post/edit/{id} defaults: { _controller: BlogJakonAdminPanelBundle:Post:editPost }
Мой репозиторий:
public function update() { try { $this->getEntityManager()->flush(); } catch (\Exception $e) { $this->getEntityManager()->getConnection()->rollback(); return false; } return true; }
Класс формы:
class NewPost extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder [...] ->add('categories', 'entity', array( 'class' => 'BlogJakon\PostBundle\Entity\Category', 'property' => 'name', 'multiple' => true, 'expanded' => true) ) ->add( 'save', 'submit', [ 'label' => 'Add post' ] ) ->getForm(); } public function setDefaultOption(OptionsResolverInterface $resolver) { $resolver->setDefaults( [ 'data_class' => 'BlogJakon\PostBundle\Entity\Post', 'csrf_protection' => true ] ); } public function getName() { return 'newPost'; } }
Стоит упомянуть, что Symfony может найти данный объект (Post), добавив только идентификатор для маршрутизации:
/ Запись / редактирование / {ID}
Согласно документации доктрины, он будет проверять только принадлежность ассоциации к изменениям.
http://doctrine-orm.readthedocs.org/en/latest/reference/unitofwork-associations.html
Таким образом, лучший способ – обновить ассоциации сторонних сторон.
В этом случае вы должны удалить и добавить support_log в главную студию в методах добавления и удаления
class support_log { /** * @ORM\ManyToMany(targetEntity="student_main", mappedBy="support_log") **/ private $student; public function addStudent($student) { $this->student[] = $student; $student->addSupportLog($this); } public function removeStudent($student) { $student->removeSupportLog($this); $this->student->removeElement($student); }
-class support_log { /** * @ORM\ManyToMany(targetEntity="student_main", mappedBy="support_log") **/ private $student; public function addStudent($student) { $this->student[] = $student; $student->addSupportLog($this); } public function removeStudent($student) { $student->removeSupportLog($this); $this->student->removeElement($student); }
-class support_log { /** * @ORM\ManyToMany(targetEntity="student_main", mappedBy="support_log") **/ private $student; public function addStudent($student) { $this->student[] = $student; $student->addSupportLog($this); } public function removeStudent($student) { $student->removeSupportLog($this); $this->student->removeElement($student); }
-class support_log { /** * @ORM\ManyToMany(targetEntity="student_main", mappedBy="support_log") **/ private $student; public function addStudent($student) { $this->student[] = $student; $student->addSupportLog($this); } public function removeStudent($student) { $student->removeSupportLog($this); $this->student->removeElement($student); }
Нет необходимости изменять действия контроллера. Важно реализовать это на обратной стороне ассоциации!
ManyToMany двунаправленный с атрибутом indexBy в аннотации исправил это для меня
Аннотации класса учащихся должны быть
class student_main { /** * @ORM\ManyToMany(targetEntity="support_log", mappedBy="student_main") **/ private $support_log;
Аннотации поддержки классов должны быть
class support_log { /** * @ORM\ManyToMany(targetEntity="student_main", inversedBy="support_log", indexBy="id") * @ORM\JoinTable(name="support_log_student", * joinColumns={@ORM\JoinColumn(name="support_log_id",referencedColumnName="id")}, * inverseJoinColumns={@ORM\JoinColumn(name="student_id", referecedColumnName="id")} * ) **/ private $student;
Теперь форма symfony 2 должна быть
public function buildForm(FormBuilder $builder, array $options) { $builder ->add('student', 'entity', array( 'class' => '<<ENTER YOUR NAMESPACE PATH TO ENTITY>>\Entity\Student', 'property' => 'Name', //property you want to display on the select box 'label' => 'Belongs to Students', 'multiple' => true, 'constraints' => array( new NotBlank(array('message' => 'Please choose atleast one student')) ) )) .... ; }
При подаче формы обычно внутри действия
if ($editForm->isValid()) { $entity = $editForm->getData(); $em->persist($entity); //this should take care of everything saving the manyToMany records $em->flush(); return $this->redirect($this->generateUrl('support_log_edit', array('id' => $id))); }
Обратите внимание: я не тестировал этот код. Я переписал этот код, чтобы он соответствовал сценарию, упомянутому в вопросе.