Proper way to update bidirectional Many to Many relationship symfony2-doctrine

坚强是说给别人听的谎言 提交于 2019-11-28 11:34:34

So far, (and to avoid having a question unanswered forever), it looks like there is no "simple way that I still haven't found" to do that. That would be the answer to my question, according to the comments.

But the code can be improved and make it more elegant thanks to the improvements of last comment. If at entity level we have that: gist.github.com/3121916 (from the comment)

Then, the code in the controller can be reduced a little bit:

$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)));
}

It is still not the "magical" symfony solution I expected, but so far the best I can do (maybe group this code inside a function in the repository, to make it more elegant).

If you have better ideas, I'm all ears.

I present my solution to everybody who are searching a solution.

I use Symfony 2.5.

My 'Post' entity has many-to-many bidirectional.

Controller:

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)
        ));
}

I bind my entity by following routing:

admin_edit_post:
    path:     /post/edit/{id}
    defaults: { _controller: BlogJakonAdminPanelBundle:Post:editPost }

My repository:

public function update()
{
    try {
        $this->getEntityManager()->flush();
    } catch (\Exception $e) {
        $this->getEntityManager()->getConnection()->rollback();
        return false;
    }

    return true;
}

Form class:

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';
    }
} 

It is worth to mention that Symfony can find given entity (Post) by adding only id to routing:

/post/edit/{id}

According to doctrine documentation it will only check the owning side of an association for changes.

http://doctrine-orm.readthedocs.org/en/latest/reference/unitofwork-associations.html

So the best way is keep owning side entity assotiations updated.

In this case you must remove and add support_log in student main in add and remove methods

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);
}

Thats all no need to modify controller actions. Important implement this at the inverse side of association!

ManyToMany Bidirectional with indexBy attribute on the annotation fixed this for me

Student class annotation should be

class student_main
{
    /**
     * @ORM\ManyToMany(targetEntity="support_log", mappedBy="student_main")
     **/
    private $support_log;

Support class annotation should be

    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;

Now the symfony 2 Form should be

    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'))
                )
            ))
        ....
        ;
    }

On submit of form usually inside the Action

        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)));
        }           

Please note: I haven't tested this code. I rewrote this code to fit the scenario mentioned in the question.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!