Reputation: 16649
I have this entity:
<?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;
}
}
and this listener:
<?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);
}
}
}
And it works fine if apple's collection is empty, but if it has some item I get a duplication error.
How can I tell to the UnitOfWork that I only want to recalculate the pig's collection?
UPDATE
There is a new preFlush event (https://github.com/doctrine/doctrine2/pull/169) and I think this kind of things can be done there. That PR is not in the branch I'm using but let's try it!
Upvotes: 7
Views: 5406
Reputation: 4251
When wanting to update the current entity you are sending to onFlush
and also creating an association to that entity
(for this example I will use Parent
object and child
object)
Let's say when I change the parent object property 'stressed' to 1
I also want to associate a brand new child
object to the parent object in my onflush
method, it will look something like this:
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)
}
So there you see:
$uow->persist()
not $em->persist()
computeChangeSet
on the child object.recomputeSingleEntityChangeSet
on the parent objectFor help with creating the onFlush method, please see the documentation
Upvotes: 0
Reputation: 16649
This can be done with the new preFlush event (Symfony 2.1).
Add a listener to the event (is a bad practice to inject the whole service container but sometimes is the way to go):
services:
mybundle.updater.listener:
class: Foo\MyBundle\Listener\UpdaterListener
arguments: ["@service_container"]
tags:
- { name: doctrine.event_listener, event: preFlush }
And the listener should be something like:
<?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
*/
}
}
}
}
Upvotes: 1
Reputation: 2887
When updating an entity during a listener's onFlush event, all you need to call is 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);
If you're creating other entities too, you need to persist them and compute their changes first!
$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);
Upvotes: 7