Mongodb-odm: Documents changed during preUpdate method don't get saved.

Created on 9 Apr 2019  路  14Comments  路  Source: doctrine/mongodb-odm

| Q | A
|------------ | -----
| Version | 2.0.0-beta2

I am not sure if this is bc break, or a bug or if I'm doing something wrong, so please pardon me if this is misplaced.

Support Question

  • I use deferred explicit persisting strategy.
  • I have a subscriber hooked to event manager subscribed to preUpdate event.
  • I have a document and an embedded document.
  • In the subscriber there is a method that takes embedded document property A, does something with it and stores it in the same embedded document to the property B. Then I persist the embedded document by calling $dm->persist($document)

The property doesn't get persisted.

I have to call $dm->getUnitOfWork()->computeChangeSets() manually after persist to make it work.

I don't know if this is expected behaviour but I am fairly sure that changing embedded document property in preUpdate resulted in persisting it correctly in Doctrine ODM 1. But I used notify strategy so that might be the difference?

Is this expected? What is the correct way to do it?

What I did to debug this:

  • I see that in the \Doctrine\ODM\MongoDB\UnitOfWork::commit there is calculateChangeSets() called.
  • Then preUpdate event is dispatched is \Doctrine\ODM\MongoDB\UnitOfWork::executeUpdates.
  • Then changed document gets scheduledForSynchronization in \Doctrine\ODM\MongoDB\UnitOfWork::doPersist
  • Then updates are executed back in \Doctrine\ODM\MongoDB\UnitOfWork::commit, but they take changeSets into account and they are not calculated (were calculated before my persist). So I call it explicitly.

All 14 comments

At which point do you modify and call persist? The title says preFlush, the text says preUpdate.

Changesets are computed after preFlush but before onFlush. In an onFlush listener, you may safely change more documents, but you'll have to compute their changesets yourself. Once you left the onFlush hook, there is no guarantee that any changes in further pre or post hooks will actually be persisted to the database. Even worse: since all pending changes are cleared in postFlush, it will look like everything was stored, but refreshing a document that was changed after onFlush may very well revert it back to the state before applying changes.

TL;DR: only modify documents/changesets in preFlush or onFlush where you'll need to recompute changesets.

At which point do you modify and call persist? The title says preFlush, the text says preUpdate.

Sorry, mistake in title. Fixed it.

only modify documents/changesets in preFlush or onFlush where you'll need to recompute changesets.

Okay, but now I have the problem to get to the persisted documents in the preFlush method. I can get to the unit of work and getScheduledDocumentUpdates. But they don't contain the embedded documents of course.

I don't simply see any solution to the problem I have now:

I have a Document (let's say User), it has an embedded document property (let's say Profile), it has a property (let's say $name). I need to perform an operation (let's say normalize the $user->profile->name) on the profile and I need external service for that. (So I can't use preFlush etc methods in the document itself but need to use event manager). Let's assert that profile can be embedded in any other document.

PreUpdate contains the document changed (Profile) but doesn't allow for document changing.

PreFlush can be used to change the document but doesn't contain the document being persisted.

Any suggestions? Am I still missing something?

Okay, but now I have the problem to get to the persisted documents in the preFlush method

As I've mentioned:

Changesets are computed after preFlush but before onFlush.

This means that you can access generated changesets in onFlush and modify them there. Any changes you make to documents will require you to recompute changesets for the affected document.

First and foremost, thank you for time and effort you put into this. I really appreciate it.

But to be honest, I am still confused.

This means that you can access generated changesets in onFlush

How do I access the generated changesets? I only see the \Doctrine\ODM\MongoDB\UnitOfWork::getDocumentChangeSet method, which requires the document to get the changeset for.

But I don't need the changeset, I need to get all the documents that were changed. I need the document this method asks me for in the first place.

I'll try to boil the problem down to the essence:

I need to get every document (including embedded) that is about to be persisted by Doctrine and be able to be able to make some changes to it in the way that it gets written back to database. I need to use external services (DI), I want to do it without globals, singletons and other hacks.

(Imagine adding updatedAt field with content generated by some external service, maybe some world time atomic clock service.)

Is this even possible?

PS: The scheduledForSynchronization is pretty much what I need. But it is private.

FWIW if you're going to modify a document that is being updated at the moment you can safely do that on the preUpdate event, just remember to recalculate the change set afterwards (or use setNewValue method from the event).

FWIW if you're going to modify a document that is being updated at the moment you can safely do that on the preUpdate event, just remember to recalculate the change set afterwards (or use setNewValue method from the event).

Oh man, I had it working like that this morning but I thought there may be some gotchas as @alcaeus stated that:

only modify documents/changesets in preFlush or onFlush where you'll need to recompute changesets.

Nevermind that :-) I learned quite a bit about Doctrine today, which is always a good thing anyways ;-)

Thank you guys!

PS: I will still happily wait for any comments from @alcaeus as I am trying to do things the best way possible as this solution will be heavily built on.

I've allowed myself to change title to "saved" instead of "persisted" - I guess @alcaeus thought you are talking about persisting new documents which indeed can be safely done onFlush latest :)

I've allowed myself to change title to "saved" instead of "persisted" - I guess @alcaeus thought you are talking about persisting new documents which indeed can be safely done onFlush latest :)

Hehe, and I forced myself to use "persist" instead of "save" thinking it was the right term. Even the subscriber I keep writing about is called SaveSubscriber :-) Thanks for the correction.

I'll try to polish and test the suggested solution and close the issue tomorrow. Thanks again for the help and for Doctrine ODM in the first place!

Okay, so my final solution is:

  • prePersist event: Just change the values.
  • preUpdate event:

    • change the values

    • $dm->persist($document)

    • $dm->getUnitOfWork->->computeChangeSet($classMetaData, $document)

Works like a charm.

Okay, so my final solution is:

Yes. Just a word of warning, once you start modifying values in other, non-embedded documents, you might get in trouble with your solution. That's where my suggestion regarding onFlush comes in (I assumed the worst-case scenario of changing unrelated documents).

you might get in trouble with your solution

I would really appreciate if you could elaborate a bit.

You know, I want to do things the good way. But I don't see any other way.

I want to keep the document mere data holding objects and strive against having any logic in them. Injecting services into them seems like a bad idea. And I don't want any singletons, globals and other crap. This pretty much rules @preFlush etc. annotations out.

How would YOU do it? You have a user document and you want to normalize a login for example. That means lowercasing it, stripping it of whitespace etc. It is a bad example and we actually do exactly that in a different place but our use case is a bit more complicated and I don't want to waste your time reading it :-)

The trouble @alcaeus was referring to is if you try to modify an other document than the one the preUpdate event is fired for. If you're modifying the document itself, or documents embedded (but not referenced) within it, you'll be fine :)

Cool, good to know. I guess I don't want to affect anything but the document so I will be fine. But heck, good to know :-)

Was this page helpful?
0 / 5 - 0 ratings