更新(从反面)Doctrine 2中的双向多对多关系?

gre*_*emo 7 orm doctrine symfony doctrine-orm symfony-2.1

Customer与"关键字/客户"关系的反面是Keyword:

/**
 * @ORM\ManyToMany(targetEntity="Keyword", mappedBy="customers",
 *     cascade={"persist", "remove"}
 * )
 */
protected $keywords;
Run Code Online (Sandbox Code Playgroud)

创建新客户时,应选择一个或多个关键字.实体表单字段是:

$form->add($this->factory->createNamed('entity', 'keywords', null, array(
    'class'    => 'Acme\HelloBundle\Entity\Keyword',
    'property' => 'select_label',
    'multiple' => true,
    'expanded' => true,
)));
Run Code Online (Sandbox Code Playgroud)

在我的控制器代码中,在绑定请求并检查表单是否有效之后,我需要同时保留客户和所有客户/关键字关联,即连接表.

但客户是反面的,所以这不起作用:

if($request->isPost()) {
    $form->bindRequest($request);

    if(!$form->isValid()) {
        return array('form' => $form->createView());
    }

    // Valid form here   
    $em = $this->getEntityManager();

    $em->persist($customer);    
    $em->flush();
}
Run Code Online (Sandbox Code Playgroud)

具有"级联"选项的事件,此代码失败.$customer->getKeywords()将返回Doctrine\ORM\PersistentCollection,仅包含选定的关键字.

我应该手动检查删除/添加了哪个关键字,然后从拥有方进行更新?

gre*_*emo 10

好的,找到了方法,即使我不完全满意.关键是这个示例表单集合字段类型.基本上我之前的表单定义发生了什么:

$customer->getKeywords() = $postData; // $postData is somewhere in form framework
Run Code Online (Sandbox Code Playgroud)

这只是将一组(所选关键字)分配给客户关键字.没有在Keyword实例(拥有方)上调用方法.关键选项是by_reference(对我而言,它只是一个坏名字,但无论如何......):

$form
    ->add($this->factory->createNamed('entity', 'keywords', null, array(
        // ...
        'by_reference' => false
    ))
);
Run Code Online (Sandbox Code Playgroud)

这样,表单框架将调用setter,即$customer->setKeywords(Collection $keywords).在该方法中,您可以"告诉"拥有方存储您的关联:

public function setKeywords(Collection $keywords)
{
    foreach($keywords as $keyword) {
        $keyword->addCustomer($this); // Owning side call!
    }

    $this->keywords = $keywords;

    return $this;
}
Run Code Online (Sandbox Code Playgroud)

(始终使用contains方法检查拥有方的重复实例).

此时,仅添加已检查的关键字($keyword参数).需要管理删除未经检查的关键字(控制器端):

$originalKeywords = $customer->getKeywords()->toArray(); // When GET or POST

// When POST and form valid
$checkedKeywords = $customer->getKeywords()->toArray(); // Thanks to setKeywords

// Loop over all keywords
foreach($originalKeywords as $keyword) {
    if(!in_array($keyword, $checkedKeywords)) { // Keyword has been unchecked
        $keyword->removeCustomer($customer);
        $manager->persist($keyword);
    }
}
Run Code Online (Sandbox Code Playgroud)

丑,但有效.我会将移除代码移到Customer课堂上,但根本不可能.如果您能找到更好的解决方案,请告诉我们!