DocIntrine在@OneToMany上插入/更新/修改子实体

时间:2015-06-19 17:50:50

标签: php symfony doctrine-orm

我有一个基于Symfony 2.6的项目。它具有以下结构:

Customer->@OneToMany->Orders->@OneToMany->Domains->@OneToMany->SubDomains

以相反的顺序相同:

SubDomains->@ManyToOne->Domains->@ManyToOne->Orders->@ManyToOne->Customer

Doctrine创造了一些非常酷的魔法"类似于customer_id(在域中),order_id(在域中)和domain_id(在subdomains中)的列。

如果所有这些*_id列都填充了父表的ID,它认为这将是完美的。但在我的情况下,这不会一直匹配。 仅对表customerorder*_id列正确填充。

我的PHP代码创建了新的域实体。如果记录存在于DB中,它还会设置ID,并将所有subdomains添加为ArrayCollection

这是我的storeOrder()方法:

protected function storeOrder(Order $order, array $domains) {
    if ($order->getDomains()->count() === 0) {
        // insert
        foreach ($domains as $domain) {
            $order->getDomains()->add($this->createDomainFromArray($domain));
        }
    } else {
        // remove/add
        $order->getDomains()->clear();
        foreach ($domains as $domain) {
            $order->getDomains()->add($this->createDomainFromArray($domain));
        }
    }
    //$updatedOrder = $this->entityManager->merge($order);
    // starts the commit process incl. transactions and prepared queries
    $this->entityManager->persist($order);
    $this->entityManager->flush();
    // clear all cached entities in em. That speeds up processing enormous
    $this->entityManager->clear();
    $this->amountOfDomains += count($domains);
}

/**
 * create a domain object from delivered data array
 *
 * @param array $data
 * @return Domain
 */
protected function createDomainFromArray($data) {
    $domain = new Domain();
    $domain->setOrderNumber($this->orderNumber);
    $domain->fromArray(ArrayUtility::removeSubEntriesFromArray($data));
    $this->addIdToDomainIfFoundInDatabase($domain);
    $this->addSubDomainsToDomain($domain, $data['subdomain']);

    return $domain;
}

/**
 * To prevent duplicate domains in database we have to set the id,
 * if we have found such domain already in DB
 * @param Domain $domain
 */
protected function addIdToDomainIfFoundInDatabase(Domain $domain) {
    /** @var Domain|NULL $domainFromDatabase */
    $domainFromDatabase = $this->findDomainBySeid($domain->getSeid());
    if ($domainFromDatabase instanceof Domain) {
        $domain->setId($domainFromDatabase->getId());
    }
}

/**
 * find domain by seid
 *
 * @param int $seid
 * @return Domain|NULL
 */
protected function findDomainBySeid($seid) {
    // do not use empty, because findOneBy can also return NULL as result
    if (!isset($this->cache['domainFromDatabase']) || (is_object($this->cache['domainFromDatabase']) && $this->cache['domainFromDatabase']->getSeid() !== $seid)) {
        $this->cache['domainFromDatabase'] = $this->domainRepository->findOneBy(array(
            'seid' => $seid,
            'orderNumber' => $this->orderNumber
        ));
    }
    return $this->cache['domainFromDatabase'];
}

/**
 * add/override subdomains to domain object
 *
 * @param Domain $domain
 * @param array $subDomains These are the subDomains from request
 */
protected function addSubDomainsToDomain(Domain $domain, array $subDomains) {
    $first = TRUE;
    $this->amountOfSubDomains += count($subDomains);
    $domainFromDatabase = $this->findDomainBySeid($domain->getSeid());
    if ($domainFromDatabase instanceof Domain) {
        $subDomainsFromDatabase = $domainFromDatabase->getSubDomains();
    } else {
        $subDomainsFromDatabase = new ArrayCollection();
    }

    foreach ($subDomains as $subDomain) {
        // create new SubDomain
        $subDomainObject = new SubDomain();
        $subDomainObject->setOrderNumber($this->orderNumber);
        $subDomainObject->fromArray($subDomain);

        // add ID to subDomain, if we have one already in DB
        // It's not perfect, but as long as we can have fully equal records in subdomain table
        // I don't see any chance to change that
        if ($first) {
            $subDomainFromDatabase = $subDomainsFromDatabase->first();
            $first = FALSE;
        } else {
            $subDomainFromDatabase = $subDomainsFromDatabase->next();
        }
        if ($subDomainFromDatabase instanceof SubDomain) {
            $subDomainObject->setId($subDomainFromDatabase->getId());
        }

        // add Order to Customer
        $domain->addSubDomain($subDomainObject);
    }
}

以下是订单实体的注释:

/**
 * Order -> domains
 *
 * @var ArrayCollection
 * @ORM\OneToMany(targetEntity="FQCN\Domain", mappedBy="order", cascade={"all"})
 */
protected $domains;

以下是子域实体的注释:

/**
 * relation from domain to order
 *
 * @ORM\ManyToOne(targetEntity="FQCN\Order", inversedBy="domains")
 */
protected $order = NULL;

/**
 * domain -> subDomains
 *
 * @var ArrayCollection
 * @ORM\OneToMany(targetEntity="FQCN\SubDomain", mappedBy="domain", cascade={"all"})
 */
protected $subDomains;

如果我仅使用flush(),则不会填写*_id字段。如果我调用detach($order),字段将被正确填充,但现在我的数据库中有第二个(重复)$order。如果我使用merge(),则所有内容都适用于 INSERT ,但如果我想更新所有ArrayCollections,则无效。

我做错了什么?如何更新 ArrayCollections正确的方式,而不用删除并再次创建它们?如何填写*_id字段?

的Stefan

2 个答案:

答案 0 :(得分:1)

谢谢大家。在这里我的解决方案:

我已经为我的域模型添加了一个新函数,以从数据库中获取原始的子域对象:

public function getSubDomain($seid) {
    foreach ($this->subDomains as $subDomain) {
        if ($subDomain->getSeid() === $seid) {
            return $subDomain;
        }
    }
}

在我的DomainSynchronization-Object中,我决定更新或新记录:

$domainFromDatabase = $order->getDomain((int)$domain['seid']);
if ($domainFromDatabase instanceof Domain) {
    // update
    $this->updateDomainFromArray($domainFromDatabase, $domain);
} else {
    // insert
    $order->getDomains()->add($this->createDomainFromArray($domain, $order));

}

而且,正如@redbirdo已经提到的,我现在手动将域对象添加到子域:

protected function addSubDomainsToDomain(Domain $domain, array $subDomains) {
    $this->amountOfSubDomains += count($subDomains);
    foreach ($subDomains as $subDomain) {
        $subDomainObject = $domain->getSubDomain((int)$subDomain['seid']);
        if ($subDomainObject instanceof SubDomain) {
            // update
            $subDomainObject->fromArray($subDomain);
        } else {
            // insert
            $subDomainObject = new SubDomain();
            $subDomainObject->setDomain($domain);
            $subDomainObject->setOrderNumber($this->orderNumber);
            $subDomainObject->fromArray($subDomain);
            $domain->addSubDomain($subDomainObject);
        }
    }
}

现在我不再需要 - > persist()或 - > merge()了。这足以称呼:

$this->entityManager->flush();

我现在也可以在子域表中看到父UID。

谢谢大家

答案 1 :(得分:0)

首先,我注意到当您向正在调用的订单添加域名时:

$order->getDomains()->add($this->createDomainFromArray($domain));

您的订单类应该有这些方法,然后您可以使用addDomain():

public function addDomain(Domain $domain)
{
    $this->domains[] = $domain;
    return $this;
}

public function removeDomain(Domain $domain)
{
    $this->domains->removeElement($domain);
}

添加这些方法可能足以解决您的问题。但是,我通常发现有必要明确设置关系的两端,在这种情况下,在Order和Domain之间。我认为你不是这样做的,因为当你创建一个域时,你没有传递订单:

$order->getDomains()->add($this->createDomainFromArray($domain));

请改为尝试:

$domain = $this->createDomainFromArray($domain);
$domain->setOrder($order);
$order->addDomain($domain);

您需要对子域进行类似的操作。

我无法理解为什么你需要使用$ em-> detach()或$ em-> merge()虽然从问题中的代码我不知道订单的位置传入storeOrder()来自。