如何仅在实体不存在的情况下保留关系的一端和另一端?

How to persist only one end of a relationship and the other one only if the entity doesn't exist?

我有一个 complex/nested 对象,它是根据 Zend\Form 数据自动水合创建的。现在我想用 Doctrine 2 保存它。最好的情况是在顶层只有一个 persist(...) 和一个 flush(...) 调用。但它不是这样工作的。所以现在我有以下问题:

有对象 UserOrder。关系是 1:n(所以,1 Usern Order)。 User 已经存在。当 User Joe 试图保存多个 Order (例如它的第二个订单)时,会发生错误:

A new entity was found through the relationship '...\Order#user' that was not configured to cascade persist operations for entity: ...\User@000000003ba4559d000000005be8d831. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement '...\User#__toString()' to get a clue.

好吧,我加cascade={"persist"}(虽然这里没意义,但不管怎样,试试看):

class Order
{
    ...
    /**
     * @var User
     *
     * @ORM\ManyToOne(targetEntity="User", cascade={"persist"})
     */
    protected $user;
    ...
}

现在可以使用了,如果给定的 User 不存在:创建一个 Order 和一个 User

但是如果User存在,就会出现错误:

An exception occurred while executing 'INSERT INTO user (username, role, created, updated) VALUES (?, ?, ?, ?)' with params ["myusername", "member", null, null]:

SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'myusername' for key 'username_UNIQUE'

如何处理保存,如果 User 还不存在,则只保存它?

解决方案是在保存引用实体之前保留 User。如果还不存在,需要先创建(persisted 和 flushed):

$user = $this->entityManager->getRepository(User::class)->findOneBy(
    ['username' => $dataObject->getUser()->getUsername()]
);
if (! $user) {
    $this->entityManager->persist($dataObject->getUser());
    $this->entityManager->flush($dataObject->getUser());
    $user = $this->entityManager->getRepository(User::class)->findOneBy(
        ['username' => $dataObject->getUser()->getUsername()]
    );
}
$dataObject->setUser($user);

$this->entityManager->persist($dataObject);
$this->entityManager->flush();

并且不应使用 cascade={"persist"},因为在这种情况下它实际上没有意义。


编辑

或者更简单:

$user = $this->entityManager->getRepository(User::class)->findOneBy(
    ['username' => $dataObject->getUser()->getUsername()]
);
if ($user) {
    $dataObject->setUser($user);
} else {
    $this->entityManager->persist($dataObject->getUser());
}

$this->entityManager->persist($dataObject);
$this->entityManager->flush();