Doctrine: update column on insert in other table - php

I have 2 entities: Service and Session with one-to-many relationship
class Service{
/**
* #var int
*
* #ORM\Column(name="avg_score", type="integer")
*/
private $avgScore;
/**
* #ORM\OneToMany(targetEntity="Session", mappedBy="service")
*/
private $sessionList;
}
class Session{
/**
* #ORM\ManyToOne(targetEntity="Service", inversedBy="sessionList")
* #ORM\JoinColumn(name="service_id", referencedColumnName="id")
*/
private $service;
/**
* #var int
*
* #ORM\Column(name="score", type="integer", nullable=true)
*/
private $score;
}
With Doctrine QueryBuilder how can I update $avgScore of Service entity everytime new Session with $score is created?
This is what I tried to do:
$qb = $this->getDoctrine()->getEntityManager()->createQueryBuilder();
$q = $qb->update('AppBundle:Service', 's')
->join('AppBundle:Session', 'ss')
->addSelect('avg(ss.score) as score_avg')
->groupBy('ss.service')
->set('s.avgScore', 'score_avg')
->where('s.id = ?1')
->setParameter(1, $service->getId())
->getQuery();
$q->execute();

You need to create a Doctrine Events Listener
services:
my.listener:
class: AppBundle\EventListener\AvgScoreUpdater
tags:
- { name: doctrine.event_listener, event: prePersist }
And then in AvgScoreUpdater implement logic:
class AvgScoreUpdater
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!($entity instanceof Session) || !$entity->getScore()) {
return;
}
$entityManager = $args->getEntityManager();
$service = $entity->getService();
// Then realize logic to update avg_score on a service
}
}

Related

Controller doesn't return OneToMany relational field in Symfony 2

I need return full response with Document model. I have response but there are absent some fields, which are defined in entity. For example I need to have in response both 'campaign' and 'template' properties - but actually 'campaign' is absent.
Below are my controller and entity.
I have such action in my controller:
/**
* #REST\View(serializerGroups={"Default", "DocumentDetails"})
* #REST\Get("/{id}", requirements={"id" = "\d+"})
* #ParamConverter("document", class="AppBundle:Document");
*/
public function showAction(Request $request, Document $document)
{
return $document;
}
But the Document entity has relations:
/**
* Document entity
*
* #ORM\Entity(repositoryClass="AppBundle\Repository\DocumentRepository")
* #ORM\Table(name="document")
* #ORM\HasLifecycleCallbacks()
*
* #Serializer\ExclusionPolicy("all")
*/
class Document
{
.......
/**
* #var campaign
* #ORM\ManyToOne(targetEntity="Campaign", inversedBy="documents")
* #ORM\JoinColumn(name="campaign", referencedColumnName="id")
*
* #Serializer\Expose()
*/
protected $campaign; // **THIS FIELD IS ABSENT - WHY !???**
/**
* #var DocumentTemplate Szablon dokumentu
*
* #ORM\ManyToOne(targetEntity="DocumentTemplate")
* #ORM\JoinColumn(name="template_id", referencedColumnName="id")
*
* #Serializer\Expose()
*/
protected $template; // **THIS PROPERTY IS DISPLAYED**
.......
$document->template is present in $document response. But $document->campaign is absent. What is wrong ? Probably it is related somehow to serializerGroups ?? Thanks for any help.
Solved ! Thanks everyone for the help. The issue was related to JMSSerializer.
There was need to set this serializer in config file services.yml at first:
app.serializer.listener.document:
class: AppBundle\EventListener\Serializer\DocumentSerializationListener
tags:
- { name: jms_serializer.event_subscriber }
And then create this listener which is creating form child-field campaign and inserting there Campaign object:
<?php
namespace AppBundle\EventListener\Serializer;
use AppBundle\Entity\Campaign;
use AppBundle\Entity\Document;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
class DocumentSerializationListener implements EventSubscriberInterface
{
/**
* #param ObjectEvent $event
* #return void
*/
public function onPostSerialize(ObjectEvent $event)
{
$entity = $event->getObject();
if (!($entity instanceof Document)) {
return ;
}
$groups = $event->getContext()->attributes->get('groups')->getOrElse([]);
if (in_array('DocumentDetails', $groups)) {
$visitor = $event->getVisitor();
$campaign = $this->getCampaignClone($entity->getCampaign());
if ($visitor->hasData('campaign')) {
$visitor->setData('campaign', $campaign);
} else {
$visitor->addData('campaign', $campaign);
}
}
}
/**
* #inheritdoc
*/
public static function getSubscribedEvents()
{
return [
[
'event' => 'serializer.post_serialize',
'class' => 'AppBundle\Entity\Document',
'method' => 'onPostSerialize'
]
];
}
private function getCampaignClone(Campaign $documentCampaign)
{
$campaign = new \stdClass();
$campaign->id = $documentCampaign->getId();
$campaign->title = $documentCampaign->getTitle();
$campaign->status = $documentCampaign->getStatus();
$campaign->rows = $documentCampaign->getRows();
$campaign->createdAt = $documentCampaign->getCreatedAt()->format(DATE_W3C);
$campaign->updatedAt = $documentCampaign->getUpdated()->format(DATE_W3C);
return $campaign;
}
}
This looks weird I know - but this only solution I found to force inserting the Entity into the form request.

Symfony inject some entity into listener

Need some advice.
I want to update article which is now seen.
First i inject EM into listener. But parse url to get article id for load article not pretty for symfony, as it seemed to me.
services:
app.articles.action_listener:
class: FrontendBundle\EventListener\ArticleListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
arguments: ['#doctrine.orm.entity_manager']
Is there any options for how to get the entity like its happen in the controller ?
/**
* #Route("/{id}", requirements={"page": "\d+"}, name="article_view_full")
*
* #param Article $article
*
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function viewAction(Article $article)
UPDATE
/**
* Class ArticleListener
* #package FrontendBundle\EventListener
*/
class ArticleListener
{
/**
* #type EntityManager
*/
private $manager;
/**
* ArticleListener constructor.
*
* #param EntityManager $manager
*/
public function __construct(EntityManager $manager)
{
$this->manager = $manager;
}
/**
* #param $event
*/
public function onKernelController($event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof ArticlesController && $controller[1] == 'viewAction') {
// THIS IS THE FULL ARTICLE VIEW. HERE I NEED TO GET CURRENT ARTICLE INSTANCE AND UPDATE VIEWS COLUMN IN DB.
}
}
}
Thx for any help.
you can parse the service container as an argument
arguments: ['#doctrine.orm.entity_manager','#service_container']
and then get the parameters...
public function __construct( $em, Container $container ) {
$this->em = $em;
$this->container = $container;
$rq = $this->container->get( 'request_stack' );
$rq = $rq->getCurrentRequest();
$parameters = explode( '/', $rq->getRequestUri() );
you can also get the request from event:
$request = $event->getRequest();

Doctrine2 - Trigger event on property change (PropertyChangeListener)

I am not writing "what did I try" or "what is not working" since I can think of many ways to implement something like this. But I cannot believe that no one did something similar before and that is why I would like to ask the question to see what kind of Doctrine2 best practices show up.
What I want is to trigger an event on a property change. So let's say I have an entity with an $active property and I want a EntityBecameActive event to fire for each entity when the property changes from false to true.
Other libraries often have a PropertyChanged event but there is no such thing available in Doctrine2.
So I have some entity like this:
<?php
namespace Application\Entity;
class Entity
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var boolean
* #ORM\Column(type="boolean", nullable=false)
*/
protected $active = false;
/**
* Get active.
*
* #return string
*/
public function getActive()
{
return $this->active;
}
/**
* Is active.
*
* #return string
*/
public function isActive()
{
return $this->active;
}
/**
* Set active.
*
* #param bool $active
* #return self
*/
public function setActive($active)
{
$this->active = $active;
return $this;
}
}
Maybe ChangeTracking Policy is what you want, maybe it is not!
The NOTIFY policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that purpose,
a class that wants to use this policy needs to implement the
NotifyPropertyChanged interface from the Doctrine\Common namespace.
Check full example in link above.
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
}
UPDATE
This is a full example but silly one so you can work on it as you wish. It just demonstrates how you do it, so don't take it too serious!
entity
namespace Football\TeamBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="country")
*/
class Country extends DomainObject
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(type="smallint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(type="string", length=2, unique=true)
*/
protected $code;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set code
*
* #param string $code
* #return Country
*/
public function setCode($code)
{
if ($code != $this->code) {
$this->onPropertyChanged('code', $this->code, $code);
$this->code = $code;
}
return $this;
}
/**
* Get code
*
* #return string
*/
public function getCode()
{
return $this->code;
}
}
domainobject
namespace Football\TeamBundle\Entity;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->listeners[] = $listener;
}
protected function onPropertyChanged($propName, $oldValue, $newValue)
{
$filename = '../src/Football/TeamBundle/Entity/log.txt';
$content = file_get_contents($filename);
if ($this->listeners) {
foreach ($this->listeners as $listener) {
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
file_put_contents($filename, $content . "\n" . time());
}
}
}
}
controller
namespace Football\TeamBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Football\TeamBundle\Entity\Country;
class DefaultController extends Controller
{
public function indexAction()
{
// First run this to create or just manually punt in DB
$this->createAction('AB');
// Run this to update it
$this->updateAction('AB');
return $this->render('FootballTeamBundle:Default:index.html.twig', array('name' => 'inanzzz'));
}
public function createAction($code)
{
$em = $this->getDoctrine()->getManager();
$country = new Country();
$country->setCode($code);
$em->persist($country);
$em->flush();
}
public function updateAction($code)
{
$repo = $this->getDoctrine()->getRepository('FootballTeamBundle:Country');
$country = $repo->findOneBy(array('code' => $code));
$country->setCode('BB');
$em = $this->getDoctrine()->getManager();
$em->flush();
}
}
And have this file with 777 permissions (again, this is test) to it: src/Football/TeamBundle/Entity/log.txt
When you run the code, your log file will have timestamp stored in it, just for demonstration purposes.

Doctrine blameable extension 'on change' doesn't work

I'm on symfony 2.6.3 with stof Doctrine extension.
TimeStampable and SoftDeletable work well.
Also Blameable "on create" and "on update" are working well too:
/**
* #var User $createdBy
*
* #Gedmo\Blameable(on="create")
* #ORM\ManyToOne(targetEntity="my\TestBundle\Entity\User")
* #ORM\JoinColumn(name="createdBy", referencedColumnName="id")
*/
protected $createdBy;
/**
* #var User $updatedBy
*
* #Gedmo\Blameable(on="update")
* #ORM\ManyToOne(targetEntity="my\TestBundle\Entity\User")
* #ORM\JoinColumn(name="updatedBy", referencedColumnName="id")
*/
protected $updatedBy;
But "on change" seems not to be working.
/**
* #var User $deletedBy
*
* #Gedmo\Blameable(on="change", field="deletedAt")
* #ORM\ManyToOne(targetEntity="my\UserBundle\Entity\User")
* #ORM\JoinColumn(name="deletedBy", referencedColumnName="id")
*/
protected $deletedBy;
I've got SoftDeletable configured on "deletedAt" field. SoftDeletable works fine, but deletedBy is never filled.
How can I manage to make it work? I just want to set user id who deleted the entity.
Here my solution :
mybundle.soft_delete:
class: Listener\SoftDeleteListener
arguments:
- #security.token_storage
tags:
- { name: doctrine_mongodb.odm.event_listener, event: preSoftDelete }
class SoftDeleteListener
{
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* Method called before "soft delete" system happened.
*
* #param LifecycleEventArgs $lifeCycleEvent Event details.
*/
public function preSoftDelete(LifecycleEventArgs $lifeCycleEvent)
{
$document = $lifeCycleEvent->getDocument();
if ($document instanceof SoftDeletedByInterface) {
$token = $this->tokenStorage->getToken();
if (is_object($token)) {
$oldValue = $document->getDeletedBy();
$user = $token->getUser();
$document->setDeletedBy($user);
$uow = $lifeCycleEvent->getObjectManager()->getUnitOfWork();
$uow->propertyChanged($document, 'deletedBy', $oldValue, $user);
$uow->scheduleExtraUpdate($document, array('deletedBy' => array($oldValue, $user)));
}
}
}
}
The problem is you want to update entity (set user) when you call remove method on it.
Currently there may not be a perfect solution for registering user who soft-deleted an object using Softdeleteable + Blameable extensions.
Some idea might be to overwrite SoftDeleteableListener (https://github.com/Atlantic18/DoctrineExtensions/blob/master/lib/Gedmo/SoftDeleteable/SoftDeleteableListener.php) but I had a problem doing it.
My current working solution is to use Entity Listener Resolver.
MyEntity.php
/**
* #ORM\EntityListeners({„Acme\MyBundle\Entity\Listener\MyEntityListener" })
*/
class MyEntity {
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User")
* #ORM\JoinColumn(name="deleted_by", referencedColumnName="id")
*/
private $deletedBy;
public function getDeletedBy()
{
return $this->deletedBy;
}
public function setDeletedBy($deletedBy)
{
$this->deletedBy = $deletedBy;
}
MyEntityListener.php
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Acme\MyBundle\Entity\MyEntity;
class MyEntityListener
{
/**
* #var TokenStorageInterface
*/
private $token_storage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->token_storage = $token_storage;
}
public function preRemove(MyEntity $myentity, LifecycleEventArgs $event)
{
$token = $this->token_storage->getToken();
if (null !== $token) {
$entityManager = $event->getObjectManager();
$myentity->setDeletedBy($token->getUser());
$entityManager->persist($myentity);
$entityManager->flush();
}
}
}
An imperfection here is calling flush method.
Register service:
services:
myentity.listener.resolver:
class: Acme\MyBundle\Entity\Listener\MyEntityListener
arguments:
- #security.token_storage
tags:
- { name: doctrine.orm.entity_listener, event: preRemove }
Update doctrine/doctrine-bundle in composer.json:
"doctrine/doctrine-bundle": "1.3.x-dev"
If you have any other solutions, especially if it is about SoftDeleteableListener, please post it here.
This is my solution, I use preSoftDelete event:
app.event.entity_delete:
class: AppBundle\EventListener\EntityDeleteListener
arguments:
- #security.token_storage
tags:
- { name: doctrine.event_listener, event: preSoftDelete, connection: default }
and service:
<?php
namespace AppBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class EntityDeleteListener
{
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function preSoftDelete(LifecycleEventArgs $args)
{
$token = $this->tokenStorage->getToken();
$object = $args->getEntity();
$om = $args->getEntityManager();
$uow = $om->getUnitOfWork();
if (!method_exists($object, 'setDeletedBy')) {
return;
}
if (null == $token) {
throw new AccessDeniedException('Only authorized users can delete entities');
}
$meta = $om->getClassMetadata(get_class($object));
$reflProp = $meta->getReflectionProperty('deletedBy');
$oldValue = $reflProp->getValue($object);
$reflProp->setValue($object, $token->getUser()->getUsername());
$om->persist($object);
$uow->propertyChanged($object, 'deletedBy', $oldValue, $token->getUser()->getUsername());
$uow->scheduleExtraUpdate($object, array(
'deletedBy' => array($oldValue, $token->getUser()->getUsername()),
));
}
}
It's not consistence because I check setDeletedBy method exists and set deletedBy property, but it work for me, and you can upgrade this code for your needs
Here is another solution I found :
Register a service:
softdeleteable.listener:
class: AppBundle\EventListener\SoftDeleteableListener
arguments:
- '#security.token_storage'
tags:
- { name: doctrine.event_listener, event: preFlush, method: preFlush }
SoftDeleteableListener:
/**
* #var TokenStorageInterface|null
*/
private $tokenStorage;
/**
* DoctrineListener constructor.
*
* #param TokenStorageInterface|null $tokenStorage
*/
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* #param PreFlushEventArgs $event
*/
public function preFlush(PreFlushEventArgs $event)
{
$user = $this->getUser();
$em = $event->getEntityManager();
foreach ($em->getUnitOfWork()->getScheduledEntityDeletions() as $object) {
/** #var SoftDeleteableEntity|BlameableEntity $object */
if (method_exists($object, 'getDeletedBy') && $user instanceof User) {
$object->setDeletedBy($user);
$em->merge($object);
// Persist and Flush allready managed by other doctrine extensions.
}
}
}
/**
* #return User|void
*/
public function getUser()
{
if (!$this->tokenStorage || !$this->tokenStorage instanceof TokenStorageInterface) {
throw new \LogicException('The SecurityBundle is not registered in your application.');
}
$token = $this->tokenStorage->getToken();
if (!$token) {
/** #noinspection PhpInconsistentReturnPointsInspection */
return;
}
$user = $token->getUser();
if (!$user instanceof User) {
/** #noinspection PhpInconsistentReturnPointsInspection */
return;
}
return $user;
}

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

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!
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);
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
*/
}
}
}
}
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:
you need to persist your child object using $uow->persist() not $em->persist()
computeChangeSet on the child object.
recomputeSingleEntityChangeSet on the parent object
For help with creating the onFlush method, please see the documentation

Categories