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();
Related
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.
I am currently working on developing a project in SAAS, each client can access his platform by a personalized url (site1.com, site2.com, etc.).
For each domain name a set of template customization data is defined in the back office and I must be able to access it from my Twig files. So I defined a listener on the kernerl.request event that adds a global variable to Twig based on the current domain name. Everything works fine in most cases, except when a page is first displayed, Twig must be run upstream and I get the following error:
Unable to add global "site" as the runtime or the extensions have
already been initialized.
Listener class
class SiteListener
{
public function __construct(
SiteHelper $siteHelper,
\Twig_Environment $twig
) {
$this->siteHelper = $siteHelper;
$this->twig = $twig;
}
/**
* Add current contexts to twig global.
*/
public function addContextsToTwigGlobal(GetResponseEvent $event)
{
$this->twig->addGlobal('site', $this->siteHelper);
}
}
Listener service declaration
multisite.listener.site:
class: MultisiteBundle\Listener\SiteListener
arguments:
- "#multisite.helper.site"
- "#twig"
tags:
- { name: kernel.event_listener, event: kernel.request, method: addContextsToTwigGlobal }
SiteHelper service
class SiteHelper
{
/**
* #var RequestStack
*/
protected $requestStack;
/**
* #var ContextConfigManager;
*/
protected $contextConfigManager;
/**
* #var ContextConfig
*/
protected $contextConfig;
public function __construct(
RequestStack $requestStack,
ContextConfigManager $contextConfigManager
) {
$this->requestStack = $requestStack;
$this->contextConfigManager = $contextConfigManager;
$this->contextConfig = $this->contextConfigManager
->findByHostOrStandard($this->getHost());
}
/**
* Get host from current request.
*
* #return string|null
*/
public function getHost()
{
$request = $this->requestStack->getCurrentRequest();
return ($request) ? $request->getHost() : null;
}
/**
* Get current context config
*
* #return ContextConfig
*/
public function getContextConfig()
{
return $this->contextConfig;
}
}
Any idea ?
I decided to write a Twig function to avoid this kind of problem. This seem to be a good solution.
class SiteExtension extends \Twig_Extension
{
/**
* #var SiteHelper
*/
private $siteHelper;
/**
* Constructor.
*
* #param SiteHelper $siteHelper
*/
public function __construct(SiteHelper $siteHelper)
{
$this->siteHelper = $siteHelper;
}
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_Function('site', array($this->siteHelper, 'getContext')),
);
}
}
I have a symfony application where I am attempting to update an entity in the database using a setter. However when I update the entity and call $this->em->flush() the entity is not persisted to the database.
Here is my service:
<?php
namespace AppBundle\Service;
use AppBundle\Exceptions\UserNotFoundException;
use Doctrine\ORM\EntityManager;
use AppBundle\Entity\User;
/**
* Class MyService
* #package AppBundle\Service
*/
class MyService extends BaseService {
/**
* #var EntityManager
*/
protected $em;
/**
* #var User
*/
protected $user;
/**
* MyService constructor.
* #param EntityManager $em
*/
public function __construct(EntityManager $em){
$this->em = $em;
}
/**
* See if a user email exists
* #param string $email
* #return bool
*/
public function checkEmailExists($email){
$this->user = $this->em
->getRepository('AppBundle:User')
->findOneBy(['email' => $email]);
return !(is_null($this->user));
}
/**
* add credit to a users account
* #param User $user
* #param integer $credit
*/
public function addCredit(User $user, $credit){
$user->addCredit($credit);
$this->em->flush();
}
/**
* add a credit to a users account
* #param $email
* #param $credit
*/
public function addCreditByEmail($email, $credit){
if(!($this->checkEmailExists($email))){
throw new UserNotFoundException(sprintf('User with email %s not found.', $email));
}
$this->addCredit($this->user, $credit);
}
}
Here is my test:
<?php
namespace AppBundle\Tests\Service;
use AppBundle\DataFixtures\ORM\PurchaseFixture;
use AppBundle\Entity\Vendor;
use AppBundle\Repository\OfferRepository;
use AppBundle\Tests\TestCase;
use AppBundle\Entity\Offer;
use AppBundle\DataFixtures\ORM\OfferFixture;
use AppBundle\DataFixtures\ORM\PaymentSystemFixture;
/**
* Class UserOfferServiceTest
* #package AppBundle\Tests\Service
*/
class MyServiceTest extends TestCase implements ServiceTestInterface
{
function __construct($name = null, array $data = [], $dataName = '')
{
$this->setFixtures([
'AppBundle\DataFixtures\ORM\CityFixture',
'AppBundle\DataFixtures\ORM\CountryFixture',
'AppBundle\DataFixtures\ORM\PaymentSystemFixture',
'AppBundle\DAtaFixtures\ORM\UserFixture',
]);
parent::__construct($name, $data, $dataName);
}
/**
* test the checkEmailExists() of app.vendor
*/
public function testCheckEmailExists(){
$myService = $this->getService();
$this->assertTrue($myService->checkEmailExists('user1#user1.com'));
$this->assertFalse($myService->checkEmailExists($this->fake()->safeEmail));
}
/**
* test the addCredit functionality
*/
public function testAddCredit(){
$myService = $this->getService();
$user = $this->getUser();
$this->assertEquals(0, $user->getCredit());
$toAdd = $this->fake()->numberBetween(1, 500);
$myService->addCredit($user, $toAdd);
$this->assertEquals($toAdd, $user->getCredit());
}
/**
* test the addCreditByEmail functionality
*/
public function testAddCreditByEmail(){
$myService = $this->getService();
$user = $this->getUser();
$email = $this->getUser()->getEmail();
$this->assertEquals(0, $user->getCredit());
$toAdd = $this->fake()->numberBetween(1, 500);
$myService->addCreditByEmail($email, $toAdd);
$updatedUser = $this->getEntityManager()
->getRepository('AppBundle:User')
->findOneBy(['email' => $email]);
$this->assertEquals($toAdd, $updatedUser->getCredit());
}
/**
* #return \AppBundle\Service\VendorService|object
*/
public function getService(){
$this->seedDatabase();
$this->client = $this->createClient();
return $this->client->getContainer()->get('app.admin_kiosk');
}
}
The testAddCredit() test passes (because I'm checking the same object), but the testAddCreditByEmail fails with the following error: 1) AppBundle\Tests\Service\MyServiceTest::testAddCreditByEmail
Failed asserting that null matches expected 149.
I've tried persisting the entity, flushing the entity (like: $this->em->flush($user)) all to no avail. Please let me know how I can fix this.
I found the issue.
In the test case I just had to refresh the entity by doing $this->getEntityManager()->refresh($user) (on the original $user entity). Thanks!
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;
}
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