'Doctrine 2 - (sometimes) duplicate related entities on flush

I have php worker which is executed every 60 sec. The worker go thru articles and publish them.

I have repository:

public function findAllUnpublished()
{
    $query = $this->entityManager->createQuery('SELECT i FROM Extra\Item\Item i
            WHERE i.status = :status')
            ->setParameter('status', ItemStatusEnum::UNPUBLISHED);
    return $query->getResult();
}

and facade

public function publishUnpublished()
{
    $items = $this->cliRepository->findAllUnpublished();
    foreach ($items as $item) {
        $item->setPublish(TRUE);
        $this->entityManager->persist($item);
    }
    $this->entityManager->flush();
    $this->itemChangedObserver->notifyBatchPublished($items); // this clear cache on web server
    return $items;
}

and the worker where is called the facade

public function execute()
{
    $this->logger->logMessage(ILogger::DEBUG, 'Start publising');

    $items = $this->itemFacade->publishUnpublished();
    $itemsIds = array_map(function ($item) {
        return $item->getId();
    }, $items);
    $this->logger->logMessage(
        ILogger::DEBUG,
        'Published %d items (%s)',
        count($items),
        implode(', ', $itemsIds)
    );

    $this->logger->logMessage(ILogger::DEBUG, 'End publising');

    return IJob::OK;
}

Problem is that sometimes(not every turn) the worker duplicate related images in gallery.

Article:

/**
 * @var \Doctrine\Common\Collections\Collection
 *
 * @OneToMany(targetEntity="gallery", mappedBy="article", cascade={"persist", "remove"})
 * @OrderBy({"position" = "ASC"})
 *
 */
private $gallery;

Gallery:

/**
 * @var \Article
 *
 * @ManyToOne(targetEntity="Article", inversedBy="gallery")
 */
private $article;

Has it ever happened to someone?



Solution 1:[1]

Calling EntityManager::persist() on an existing entry can cause side effects on associations.

So, just remove this line and keep only your flush:

$items = $this->cliRepository->findAllUnpublished();

foreach ($items as $item) {
    $item->setPublish(TRUE);
}

$this->entityManager->flush();

// ...

For more, look here.

EDIT

You should use EntityManager::merge($object) rather than EntityManager::persist($object), e.g:

foreach ($items as $item) {
    $item->setPublish(TRUE);
    $this->entityManager->merge($item);
}

This will create a new entry if no reference can be found, otherwise this will update an existing entry.

More about merging entities.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1