I have a Doctrine Event Listener that listens to the onFlush event. I use it to update an eTag on an entity when saved.
I need to get access to the entities scheduled for deletion, so I can access their associated object, however:
I'm using a soft-deletable filter, so the entities aren't actually in $uow->getScheduledEntityDeletions(), they're in $uow->extraUpdates marking the deleted flag changed instead.
This variable is private and I don't know of any programatic way to get notified of this change. Any ideas?
private function updateIfRequired(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
// Entities either updated or just inserted
$upsertedEntities = array_merge(
$uow->getScheduledEntityUpdates(),
$uow->getScheduledEntityInsertions()
);
foreach ($upsertedEntities as $entity) {
if ($entity instanceof ETaggableInterface || $entity instanceof ETagRelatedInterface) {
$this->updateETag($entity);
}
}
// When soft-deleted, this and getScheduledEntityUpdates are both empty!
$deletedEntities = $uow->getScheduledEntityDeletions();
foreach ($deletedEntities as $entity) {
$this->deletedEntities[spl_object_hash($entity)] = $entity;
$this->updateETag($entity);
}
}
So, the detailed answer for this question would be something like this:
Listen to the preSoftDelete event (Symfony2 DoctrineExtensions preSoftDelete event call):
tags:
- { name: doctrine.event_listener, event: preSoftDelete }
Then in your listener class, add the following method:
public function preSoftDelete(LifecycleEventArgs $args){
$entity = $args->getEntity();
$em = $args->getEntityManager();
if ($entity instanceof ETaggableInterface || $entity instanceof ETagRelatedInterface) {
$entity->updateEtag('foo'); // Was not clear what $this->updateEtag() do
$em->persist($entity);
$classMetadata = $em->getClassMetadata(get_class($entity));
$em->getUnitOfWork()->computeChangeSet($classMetadata, $entity);
}
}
This will update the entity, tell to persist it, calculate the changes.
Maybe you should listen to preSoftDelete events: https://github.com/Atlantic18/DoctrineExtensions/blob/master/lib/Gedmo/SoftDeleteable/SoftDeleteableListener.php#L74
Related
I'm trying to persist another entity in my preUpdate event listener, but it's not working...
Here is my code:
public function preUpdate(LifecycleEventArgs $args) {
$entity = $args->getEntity();
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$session = new Session();
$newLog = new Log();
$newLog->setDate(new DateTime());
if(!empty($this->toBePersisted))
{
array_push($toBePersisted, $historique);
}
else
{
$toBePersisted[0] = $historique;
}
}
public function postFlush(PostFlushEventArgs $event)
{
if(!empty($this->toBePersisted)) {
$em = $event->getEntityManager();
foreach ($this->toBePersisted as $element) {
$em->persist($element);
}
$this->toBePersisted = [];
$em->flush();
}
}
But my new log is not persisted...
Do you have any solution ?
Regards
You are using the wrong listeners to implement what you want to do. Quoting from Doctrine documentation for postFlush event:
postFlush is called at the end of EntityManager#flush(). EntityManager#flush() can NOT be called safely inside its listeners.
The right way to implement what you want is inside the onFlush event which is the more powerful doctrine event but is NOT a lifecycle callback. So you have to proper set up your listener in your services.yaml:
App\EventListener\LogListener:
tags:
- { name: doctrine.event_listener, event: onFlush }
and then in onFlush event:
class LogListener {
public function onFlush(OnFlushEventArgs $args) {
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if (!$entity instanceof YourUpdatedEntity) {
return;
}
$newLog = new Log();
$newLog->setDate(new DateTime());
$em->persist($newLog);
$classMetadata = $em->getClassMetadata(Log::class);
$uow->computeChangeSet($classMetadata, $newLog);
}
}
}
The computeChangeSet function call is needed because as stated in the documentation for onFlush event:
If you create and persist a new entity in onFlush, then calling EntityManager#persist() is not enough. You have to execute an additional call to $unitOfWork->computeChangeSet($classMetadata, $entity).
I have listener for encryption and decryption.
Encrypt:
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof Customer) {
$entity->setEmail($this->encryption->encrypt($entity->getEmail()));
$entity->setPhone($this->encryption->encrypt($entity->getPhone()));
} elseif ($entity instanceof Address) {
$entity->setFirstName($this->encryption->encrypt($entity->getFirstName()));
$entity->setLastName($this->encryption->encrypt($entity->getLastName()));
$entity->setCompanyName($this->encryption->encrypt($entity->getCompanyName()));
$entity->setStreet($this->encryption->encrypt($entity->getStreet()));
$entity->setCity($this->encryption->encrypt($entity->getCity()));
}
return;
}
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof Customer) {
$entity->setEmail($this->encryption->decrypt($entity->getEmail()));
$entity->setPhone($this->encryption->decrypt($entity->getPhone()));
} elseif ($entity instanceof Address) {
$entity->setFirstName($this->encryption->decrypt($entity->getFirstName()));
$entity->setLastName($this->encryption->decrypt($entity->getLastName()));
$entity->setCompanyName($this->encryption->decrypt($entity->getCompanyName()));
$entity->setStreet($this->encryption->decrypt($entity->getStreet()));
$entity->setCity($this->encryption->decrypt($entity->getCity()));
}
return;
}
Problem is with decryption part.
How to modify data on load without saving it in database? Right now it's updating encrypted data with decrypted onload.
Thanks
Your issue is that you shouldn't use a prePersist event as this event is only called when you add an entity to that database, but not when you update one.
In the list of Doctrine lifecycle events here, you can see that the event you'd want to use is preUpdate so that on every change, it is encrypted again before being saved
Event Listener example below works fine for prePersist() and postPersist() however browser times out for preUpdate() and postUpdate(). Anyone knows why this is happening?
Note: Event listener is the one which cause problem because controller works fine when used on its own. I checked the database.
Error:
Maximum execution time of 30 seconds exceeded in /var/www/html/local/listener/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 685
Event listener
class UserListener
{
//public function postUpdate(LifecycleEventArgs $args)
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof User) {
$userLog = new UserLog();
$userLog->setDescription('User Update.');
$em = $args->getEntityManager();
$em->persist($userLog);
$em->flush();
}
}
}
Controller:
public function updateUser()
{
$repo = $this->getDoctrine()->getRepository('SiteFrontBundle:User');
$user = $repo->findOneBy(array('id' => 1));
$user->setLock(true);
$em = $this->getDoctrine()->getManager();
$em->flush();
}
Service.yml
services:
entity.event_listener.user:
class: Site\FrontBundle\EventListener\Entity\UserListener
tags:
- { name: doctrine.event_listener, event: preUpdate }
preUpdate is very limited.
From: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#preupdate
Any calls to EntityManager#persist() or EntityManager#remove(),
even in combination with the UnitOfWork API are strongly discouraged and
don’t work as expected outside the flush operation.
Probably need to use onFlush, http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#onflush
Might also want to take look at how these guys do it: https://github.com/Atlantic18/DoctrineExtensions/tree/master/lib/Gedmo/Loggable
Or just setup a logging entity manager.
In my event I check if start date is changed if is, then I want to insert new row, and cancel update
if ($args->hasChangedField('startAt')) {
// cancel update
// insert new entity
}
I have did something like this
public function preFlush(PreFlushEventArgs $args)
{
$em = $args->getEntityManager();
/** #var UnitOfWork $unitOfWork */
$unitOfWork = $em->getUnitOfWork();
$identityMap = $unitOfWork->getIdentityMap();
if (0 === count($identityMap)) {
return;
}
foreach ($identityMap as $entities) {
foreach ($entities as $entity) {
if ($entity instanceof Settelment) {
$original = $unitOfWork->getOriginalEntityData($entity);
if ($entity->getStartAt() !== $original['startAt']) {
$em->detach($entity); // remove entity
}
}
}
}
}
But now I remove entity and its ok but I dont define here new object and persist it, and I got extra row in database (this is what I want) but how does it work ?? why i get extra row if I didnt create entity.
I have set up an event listener with a preUpdate function, and I am trying to use this to update an address entity related through a OneToMany relationship. The function runs, and the object is updated, but this is not then persisted tot he database.
You can see the code below, does anyone have any ideas?
public function preUpdate(LifecycleEventArgs $args){
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
if ($entity instanceof Company) {
$key = 0;
$areaCode = explode(' ', $this->parsePostcode($entity->getAddresses()[$key]->getPostCode() ));
$geo = $entityManager->getRepository('AlphaRMSBundle:SelGeo')->findOneByPostcode($areaCode[0]);
if ($geo instanceof SelGeo) {
$entity->getAddresses()[$key]->setEastings($geo->getEastings());
$entity->getAddresses()[$key]->setNorthings($geo->getNorthings());
$entity->getAddresses()[$key]->setLatitude($geo->getLatitude());
$entity->getAddresses()[$key]->setLongitude($geo->getLongitude());
$entity->getAddresses()[$key]->setMunicipality($geo->getTownCity());
$entity->getAddresses()[$key]->setRegion($geo->getRegion());
$entity->getAddresses()[$key]->setCountry($geo->getCountryString());
$entity->getAddresses()[$key]->setCountryUkCode($geo->getCountryUkCode());
//die($entity->getAddresses()[$key]->getMunicipality());
}
}
}