【问题标题】:Updating (from the inverse side) bidirectional many-to-many relationships in Doctrine 2?在 Doctrine 2 中更新(从反面)双向多对多关系?
【发布时间】:2012-11-05 18:32:18
【问题描述】:

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

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

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

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

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

但是客户是反面,所以这是行不通的:

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

带有“级联”选项的事件,此代码失败。 $customer->getKeywords() 将返回 Doctrine\ORM\PersistentCollection,它只包含选定的关键字。

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

【问题讨论】:

标签: symfony orm doctrine doctrine-orm symfony-2.1


【解决方案1】:

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

$customer->getKeywords() = $postData; // $postData is somewhere in form framework

这只是将(选定关键字的)集合分配给客户关键字。没有在 Keyword 实例(拥有方)上调用任何方法。关键选项是by_reference(对我来说这只是一个坏名字,但无论如何......):

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

这样表单框架将调用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;
}

(始终使用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);
    }
}

丑陋,但有效。我会将删除代码移至Customer 类,但这根本不可能。如果您能找到更好的解决方案,请告诉我!

【讨论】:

    【解决方案2】:

    我使用的解决方案与@gredmo 略有不同。来自doctrine documentation:当你满足这个假设时,你可以使用Orphan Removal:

    当使用orphanRemoval=true 选项时,Doctrine 假设实体是私有的,并且不会被其他实体重用。如果您忽略此假设,即使您将孤立实体分配给另一个实体,您的实体也会被 Doctrine 删除。

    我有这个实体类:

    class Contract {
    /**
     * @ORM\OneToMany(targetEntity="ContractParticipant", mappedBy="contract", cascade={"all"}, orphanRemoval=true)
     **/
    }
    protected $participants;
    

    表单处理(伪代码):

        // $_POST carry the Contract entity values
    
        $received = [];
    
        foreach ($_POST['participants'] as $key => $participant) {
    
            if ((!$relation = $collection->get($key))) {
                // new entity
                $collection[$key] = $relation = $relationMeta->newInstance();
    
            } else {
                // editing existing entity
            }
    
            $received[] = $key;
            $this->mapper->save($relation, $participant);   // map POST data to entity
        }
    
        foreach ($collection as $key => $relation) {
            if ($this->isAllowedRemove() && !in_array($key, $received)) {
                // entity has been deleted
                unset($collection[$key]);
            }
        }
    

    不要忘记持久化实体结束刷新。 Flush 还会删除已移除的实体。

        $this->em->persist($entity);
        $this->em->flush();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-10
      • 2017-11-11
      • 1970-01-01
      相关资源
      最近更新 更多