【问题标题】:Proper way to update bidirectional Many to Many relationship symfony2-doctrine更新双向多对多关系symfony2-doctrine的正确方法
【发布时间】:2013-03-26 11:11:43
【问题描述】:

我做了一些研究,在阅读了 thisthis(以及所有相关问题)之后,我仍然无法确定在 Symonfy 2 Doctrine 中更新多对多关系的正确方法。感觉应该有一个很简单的方法我还没有找到。

我有这 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;
}

这很好用,我的问题更多是关于删除关系。在表单中有一个多选,用户可能会选择一些已经相关的学生和一些不相关的学生。感觉应该有一种自动的方式来做到这一点,但我不得不做很多代码。

在控制器中,略高于我之前写的代码,我写的是:

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

关键靠这里?

【问题讨论】:

  • 第一个想法是使用 $student->supportLog->clear();而不是循环。但这不是答案,只是改进。二是表单可以自动管理集合。请显示您的表单类型的代码。
  • 第一个想法带来了非常有价值的改进,它更加优雅!谢谢!对于表格,是Symfony生成的平面,我什至没有碰我想,看看编辑请@DmitryKrasun
  • (只是一个注释,在 supportlog 控制器中: $student->supportLog->clear(); 不起作用,因为它会删除学生拥有的所有关系,我只想删除一个关系,以及我当前正在编辑的支持日志(循环是对已经与我当前正在编辑的此支持日志相关的所有其他学生执行相同操作)
  • 我把它放在这里了,因为我没有特定于 symfony 的解决方案。这应该阐明如何在实体级别处理多对多:gist.github.com/3121916

标签: php symfony doctrine-orm many-to-many bidirectional


【解决方案1】:

到目前为止,(为了避免永远没有答案的问题),似乎没有“我还没有找到的简单方法”来做到这一点。根据 cmets 的说法,这就是我的问题的答案。

但由于最后评论的改进,代码可以改进并使其更加优雅。如果在实体级别我们有: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 解决方案,但到目前为止我能做到的最好(也许将这段代码分组到存储库中的一个函数中,以使其更优雅)。

如果你有更好的想法,我会全力以赴。

【讨论】:

    【解决方案2】:

    我向所有正在寻找解决方案的人展示我的解决方案。

    我使用 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 可以通过在路由中只添加 id 来找到给定的实体(Post):

    /post/edit/{id}

    【讨论】:

      【解决方案3】:

      根据条令文档,它只会检查关联的拥有方是否有更改。

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

      这一切都不需要修改控制器动作。 在关联的反面实现这一点很重要!

      【讨论】:

        【解决方案4】:

        注释上带有 indexBy 属性的ManyToMany 双向为我解决了这个问题

        学生课注解应该是

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

        通常在 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)));
                }           
        

        请注意:我没有测试过这段代码。我重写了这段代码以适应问题中提到的场景。

        【讨论】:

          猜你喜欢
          • 2013-01-08
          • 2012-11-05
          • 1970-01-01
          • 2014-09-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-20
          • 1970-01-01
          相关资源
          最近更新 更多