如何在 onFlush 事件侦听器中更新实体的 manyToMany 集合


How to update a manyToMany collection of an entity in onFlush event listener?

我有这个实体:

<?php
namespace Comakai'MyBundle'Entity;
use Doctrine'ORM'Mapping as ORM,
    Symfony'Component'Validator'Constraints as Assert;
/**
 * @ORM'Entity
 */
class Stuff {
    /**
     * @ORM'Id
     * @ORM'Column(type="integer")
     * @ORM'GeneratedValue(strategy="IDENTITY")
     */
    private $id;
    /**
     * @ORM'Column(type="text")
     * @Assert'NotBlank()
     */
    private $content;
    /**
     * @ORM'ManyToMany(targetEntity="Apple", cascade={"persist"})
     */
    private $apples;
    /**
     * @ORM'ManyToMany(targetEntity="Pig")
     */
    private $pigs;
    public function __construct() {
        $this->apples = new 'Doctrine'Common'Collections'ArrayCollection();
        $this->pigs = new 'Doctrine'Common'Collections'ArrayCollection();
    }
    public function setApples($apples) {
        $this->getApples()->clear();
        foreach ($apples as $apple) {
            $this->addApple($apple);
        }
    }
    public function setPigs($pigs) {
        $this->getPigs()->clear();
        foreach ($pigs as $pig) {
            $this->addPig($pig);
        }
    }
    /**
     * Get id
     *
     * @return integer 
     */
    public function getId() {
        return $this->id;
    }
    /**
     * Set content
     *
     * @param text $content
     */
    public function setContent($content) {
        $this->content = $content;
    }
    /**
     * Get content
     *
     * @return text 
     */
    public function getContent() {
        return $this->content;
    }
    /**
     * Add apples
     *
     * @param Comakai'MyBundle'Entity'Apple $apples
     */
    public function addApple('Comakai'MyBundle'Entity'Apple $apples) {
        $this->apples[] = $apples;
    }
    /**
     * Get apples
     *
     * @return Doctrine'Common'Collections'Collection 
     */
    public function getApples() {
        return $this->apples;
    }

    /**
    * Add pigs
    *
    * @param Comakai'MyBundle'Entity'Pig $pigs
    */
    public function addPig('Comakai'MyBundle'Entity'Pig $pigs) {
        $this->pigs[] = $pigs;
    }
    /**
    * Get pigs
    *
    * @return Doctrine'Common'Collections'Collection 
    */
    public function getPigs() {
        return $this->pigs;
    }
}

而这个侦听器:

<?php
namespace Comakai'MyBundle'Listener;
use Comakai'MyBundle'Util'SluggerParser
    Doctrine'ORM'Event'OnFlushEventArgs,
    Comakai'MyBundle'Entity'Stuff,
    Comakai'MyBundle'Entity'Apple,
    Comakai'MyBundle'Entity'Pig;
class Listener {
    /**
     * @param 'Doctrine'ORM'Event'OnFlushEventArgs $ea
     */
    public function onFlush(OnFlushEventArgs $ea) {
        $em = $ea->getEntityManager();
        $uow = $em->getUnitOfWork();
        foreach ($uow->getScheduledEntityInsertions() AS $entity) {
            $this->save($entity, $em, $uow);
        }
        foreach ($uow->getScheduledEntityUpdates() AS $entity) {
            $this->save($entity, $em, $uow);
        }
    }
    public function save($entity, $em, $uow) {
        if ($entity instanceof Stuff) {
            $pigRepository = $em->getRepository('Comakai'MyBundle'Entity'Pig');
            $content = $entity->getContent();
            preg_match_all('/@@ pig:('d+) @@/i', $content, $matches);
            $entity->getPigs()->clear();
            foreach($matches[1] as $pigID) {
                $pig = $pigRepository->find($pigID);
                if(!empty($pig)) {
                    $entity->addPig($pig);
                }
            }
            $entity->setContent($content);
            $meta = $em->getClassMetadata(get_class($entity));
            $uow->recomputeSingleEntityChangeSet($meta, $entity);
            $uow->computeChangeSet($meta, $entity);
        }
    }
}

如果苹果的集合是空的,它工作正常,但如果它有一些项目,我会收到重复错误。

怎样才能告诉单位我只想重新计算猪的收藏?

更新

有一个新的 preFlush 事件 (https://github.com/doctrine/doctrine2/pull/169),我认为这种事情可以在那里完成。该 PR 不在我正在使用的分支中,但让我们尝试一下!

在侦听器的 onFlush 事件期间更新实体时,只需调用computeChangeSet()

// make changes to entity
$entity->field = 'value';
// or assign an existing entity to an assocation
$entity->user = $myExistingUserEntity;
$entity->tags->add($myExistingTagEntity);
$meta = $em->getClassMetadata(get_class($entity));
$uow->computeChangeSet($meta, $entity);

如果还要创建其他实体,则需要先保留它们并计算它们的更改!

$myNewUserEntity = new Entity'User;
$myNewTagEntity = new Entity'Tag;
$entity->user = $myNewUserEntity;
// make sure you call add() on the owning side for *ToMany associations
$entity->tags->add($myNewTagEntity);
$em->persist($myNewUserEntity);
$em->persist($myNewTagEntity);
$metaUser = $em->getClassMetadata(get_class($myNewUserEntity));
$uow->computeChangeSet($metaUser, $myNewUserEntity);
$metaTag = $em->getClassMetadata(get_class($myNewTagEntity));
$uow->computeChangeSet($metaTag, $myNewTagEntity);
$meta = $em->getClassMetadata(get_class($entity));
$uow->computeChangeSet($meta, $entity);

这可以通过新的preFlush事件(Symfony 2.1)来完成。

向事件添加侦听器(注入整个服务容器是一种不好的做法,但有时是要走的路):

services:
    mybundle.updater.listener:
        class: Foo'MyBundle'Listener'UpdaterListener
        arguments: ["@service_container"]
        tags:
            - { name: doctrine.event_listener, event: preFlush }

侦听器应该是这样的:

<?php
namespace Foo'MyBundle'Listener;
use Doctrine'ORM'Event'PreFlushEventArgs;
use Foo'MyBundle'SomeInterface;
class UpdaterListener
{
    /**
     * @param 'Doctrine'ORM'Event'PreFlushEventArgs $ea
     */
    public function preFlush(PreFlushEventArgs $ea)
    {
        /* @var $em 'Doctrine'ORM'EntityManager */
        $em = $ea->getEntityManager();
        /* @var $uow 'Doctrine'ORM'UnitOfWork */
        $uow = $em->getUnitOfWork();
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            if($entity instanceof SomeInterface) {
               /*
                * do your stuff here and don't worry because
                * it'll execute before the flush
                */
            }
        }
    }
}

当想要更新要发送到onFlush的当前实体并创建与该实体的关联时

(在本例中,我将使用Parent对象和child对象)

假设当我将父对象属性"stressed"更改为1时,我还想在我的onflush方法中将一个全新的child对象与父对象相关联,它将如下所示:

public function onFlush(onFlushEventArgs $args)
{
    ....
    $child = $this->createChild($em, $entity); // return the new object. just the object.
    $uow->persist($child);
    $childMeta = $em->getMetadataFactory()->getMetadataFor('AcmeFamilyTreeBundle:Child');
    $uow->computeChangeSet($childMeta, $child)
    $parent->setStressed(1);
    $parentMeta = $em->getMetadataFactory()->getMetadataFor('AcmeFamilyTreeBundle:Parent');
    $uow->recomputeSingleEntityChangeSet($parentMeta, $parent)
}

所以你看到:

  1. 您需要使用$uow->persist()而不是$em->persist()来持久化子对象
  2. computeChangeSet子对象上。
  3. 父对象recomputeSingleEntityChangeSet

有关创建 onFlush 方法的帮助,请参阅文档