正确的方法来更新双向多对多关系symfony2-doctrine

时间:2013-03-26 11:11:44

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

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

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

钥匙在这里依赖?

4 个答案:

答案 0 :(得分:2)

到目前为止,(并且为了避免永远无法回答问题),看起来没有“我仍然没有找到的简单方法”来做到这一点。根据评论,这将是我的问题的答案。

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

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

答案 1 :(得分: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):

  

/后/编辑/ {ID}

答案 2 :(得分:2)

根据学说文档,它只会检查关联的拥有方是否有变化。

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

因此,最好的方法是不断更新边实体协议。

在这种情况下,您必须删除并在student main中添加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);
}

这一切都不需要修改控制器动作。 重要的是在关联的反面执行此操作!

答案 3 :(得分:1)

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

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

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