Matthew Krauss
Matthew Krauss

Reputation: 87

Can you make Doctrine return a fresh instance of an Entity generated from the DB than the one that was inserted?

We are upgrading a web API from:

To:

We have a large main entity, with many Collection objects, containing other entities. We accept new instances via a POST call, with a JSON body, that gets deserialized by JMS Serializer before eventually being inserted to the database. The Collection objects are represented by JSON arrays, of course.

After inserting the main entity, we flush, refresh, and findOneBy the entity back from the DB again, to return to the client. I believe the reason for this was to get back exactly what would be returned in a future GET request. In the old version, this worked.

EDIT TO ADD

It turns out that this was not actually working as I thought it was, in the pre-upgrade version, but that something else was going on. We have this unusual pattern, where we have two separate Doctrine connections, one for "read" and one for "write". Since this has always seemed like a useless complexity, we had taken advantage of the upgrade process to go ahead and simplify that, so one connection would be used everywhere. Yes, it was a bad idea to mix in more changes than necessary at this time, and we got stung by that.

In our pre-upgrade branch, the write-connection was used to write the new entity to the DB, but the read-connection was used to read it back again. It appears the copy of the entity with the null values could not be shared between the two separate Doctrine connections, so the read-connection returned a fresh copy from the DB with appropriate empty collections.

This is obviously over-complicated and must have at least some performance penalty, but we won't be given time to make deeper changes at this point, so we've put it back that way. It's surprising and disappointing to me, if true, that Doctrine just can't do this without this hack.

END EDIT TO ADD

On our upgraded branch, we have noticed that when the POST is missing a property that that represents a Collection, the instance that we get to return to the client has null instead of an empty collection. In fact, on investigation, I find that if I === compare the instance we inserted with persist to the one returned from findOneBy, they are the same object. I would expect the intervening refresh call to prevent that, on the upgraded branch, it does not. On the old branch, we do get back an empty Collection for the property.

I have read that the official position of Doctrine is that collections should always be initialized in the constructor. That makes perfect sense to me! However, the JMS Serializer doesn't use the constructor - these objects are never constructed before being passed to Doctrine to store, they are just deserialized.

JMS Serializer, on the other hand, doesn't seem to provide any way to set a Collection as a default for a missing property, or to run a constructor.

I'm open to any suggestions, here, but I would prefer to fix this by restoring the Doctrine behavior we see on our old branch, of getting a new instance of the Entity from the DB. The reason this would be preferable is that this is already a complicated and high-risk upgrade, and I want to minimize any additional risk of something else, unanticipated, being different, and breaking our clients.

Upvotes: 1

Views: 74

Answers (1)

Matthew Krauss
Matthew Krauss

Reputation: 87

I've found an answer, that is what we, at least, will be going forward with for now. It may actually be impossible to do what is stated in the question, I do not know, so I'm still hoping somebody comes up with a better answer, out of curiosity if nothing else.

This can be done using two separate connections, connection-A to write the new entity to the DB, but connection-B to read it back again. The copy of the entity with the null values cannot be shared between the two separate Doctrine connections, so connection-B will return a fresh copy from the DB with appropriate empty collections.

I don't recommend using separate connections. If possible, it would be best to fix the underlying design so that this isn't necessary, but sometimes a bad solution is better than no solution.

Upvotes: 0

Related Questions