Unique error persisting entity updates to db with Doctrine - php

I'm using Doctrine in my Symfony project to manage the persisting layer in my application.
Recently, I've been having some issues when persisting changes from my entities to my database. The problem I've been having is that when I update an entity and save it to my database, sometimes the EntityManager treats my entity as a new object, so instead of performing an update operation, it performs an insert operation, thus causing a unique exception error in my database.
As the docs say, when updating an object you should only perform these steps:
fetch the object from Doctrine
modify the object
(optional) call persist() on the entity manager
call flush() on the entity manager
Note I added (optional) to the persist() call because, as the docs say, it isn't necessary since Doctrine is already watching the object for changes
Now that things are explained, this is the work I do in my code:
$myEntity = $this->myEntityRepository->byId($id);
// make some changes to the entity
$myEntity->setSomething('something');
$this->myEntityRepository->save($entity);
Where the save() operation in my repository is as follows:
$this->entityManager->persist($entity);
$this->entityManager->flush();
And the byId() operation:
return $this->entityManager->getRepository()->find($id);
As I said, the persist operation should only be called when persisting new entities, but since Doctrine can differentiate between an already managed entity and a new one, it should be no problem. If I didn't call the persist() method, instead of executing and insert operation and cause a unique violation, it would literally do nothing as it wouldn't detect any changes to my operations.
The reason I always use the persist() method is because the save() operation in my repository is used with both new entities and updates to existing entities.
As I've seen in another answer, calling the merge() operation instead of persist() should solve the problem, but I don't see it right because I think it's just a "dirty" solution, plus the method is being deprecated in future versions of Doctrine.
So, what am I missing here? Why sometimes I get a unique error when running the above code? I only have one connection and one entity manager configured in my application.
I'd like to add that the only occurrences of this problem are found in code executed in consumers (async events), not in the API itself, but whenever I receive a new event, a new and fresh connection to the database is created to ensure I don't have overlapping problems with the entity manager used in some previous event.
When talking about consumers I mean that an event is published via RabbitMQ (the event contains the ID of the entity) and then it gets consumed in a separate process from the API, fetching the entity directly with the entity manager repository.
My guess is that between the line where I get my entity from the repository (i.e. I use the find() method) and the line where I save it into the database (i.e. I use the flush() method), the entity manager somehow removes the entity from its UnitOfWork so it treats it as a new entity instead of a managed one.

An issue I recently found in some code was where an entity was being passed - via a RabbitMq message and the entity created from scratch, including the id -- $x = new Entity(); $x->setId($data['id');$x->setName($data['name'); # ... etc).
Having an id set does not make the entity 'managed' by Doctrine however - only reading the original record (from the DB) and then updating it. Persisting $x as above will create a new record - and ignoring the id that was set.
I would ensure (perhaps with some assertions on the record that you are fetching that must pass - or die with an error, at least for now) so that know that you are always dealing with a known entity (I'm also presuming that your ->byId($id) call is just a ->find($id) internally).

Related

Symfony2 / Doctrine2 - Check Entity Object is new, never persisted

Short and easy question:
How to determine that Doctrine PHP Entity Object is new?
New - I mean is not in database and its not "update" action.
Most important thing - I need way to check it without any query like:
$manager->findOne($something);
Actually sometimes I tried to check "is ID null" but Im not 100% sure this method is valid or not. And other tip or probably core of this question - I seen something like:
$manager->getUnitOfWork()->isInIdentityMap($object);
Looks nice but I can't find what actually do function isInIdentityMap.
Is it true if it was persisted or removed? When this function say true and false?
//EDIT
I need to detect entity object created first time - existing only in php but not in database. Of course entity loaded from database to apply updates is not new for me - for example:
When you createing Comment object you need to increment commentCounter but it should be incremented only when you flushing this comment for first time.
You don't want to increment it when you updating or deleting existing comment.
How to check that comment is new and counter should be incremented.
Please explain in answers why your method is good/better
You can use
$entityManager->contains($entity)
Edit:
Based on your edit, i suggest to use an entity listener:
http://doctrine-orm.readthedocs.io/en/latest/reference/events.html#entity-listeners
So you don't need to check if the entity is new or not in many parts in your application when an entity is persisted, you only setup the entity listener and it works wherever you create and persist the entities.
I checked some docs and tested some things and I hope that everything I write is correct:
Method isInIdentityMap
$manager->getUnitOfWork()->isInIdentityMap($object);
is probably perfect for my case because it checks that my object exist in Identity Map, this map store only flushed objects or actually object with ID. New Object before persistence and after persist action still have NULL ID so its not in this map. This way as long as object was not flushed into database this method should work. (Map contains only objects loaded from database)
Method to check NULL ID
if($obj->getId() == null){
//code
}
should work too, its very similar to first methid but still Im not 100% sure it always work as I want and in addition we have first method so... Its better to use function prepared for it.
Method contains
$entityManager->contains($entity)
Do job but for other case. This method checks objects in entity manager but any persisted object, scheduled for any action is already tracked by entityManager. This function can help only as long as object is not persisted.
Other methods
Unit of work have many other methods, I didnt check them all but it seems we can easly check many other things using functions like:
->scheduleForInsert();
->scheduleForUpdate();
// etc
And other case - using Doctrine events event:
prePersist
prePersist - The prePersist event occurs for a given entity before the respective EntityManager persist operation for that entity is executed. It should be noted that this event is only triggered on initial persist of an entity (i.e. it does not trigger on future updates).

Persist object in PostPersist, PostUpdate events

I have a logger which use Doctrine to writes in DB. And I need log some events in Doctrine PostPersist, PostUpdate and PreRemove handlers of an Entity Listener.
But a flush operation is officialy unsupported (and sometimes cause fatal errors if neglect it) in these handlers.
I have encountered a similar question, but the solution is to defer a flush to the end of a currently performing flush, which is not suit me, cause I need to insert entry right in the handlers, e.g. in order not to lose object id during remove operation.
I have a LogManager and want that this add-log-entry operation be the same - no matter whether you invoke it from some handler or from some another place in code.
I wonder is there a way to persist objects in the handlers? (May be by using another EntityManager ...)
You can use onFlush event that I think suites well to your needs. Instead of using flush method you have to recompute/compute changes manually. See the link.
From Doctrine2 documentation:
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).
Changing primitive fields or associations requires you to explicitly trigger a re-computation of the changeset of the affected entity. This can be done by calling $unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity).

ORM/Doctrine2 - When to persist?

This has been bugging me for a while.
In Doctrine2, we have the: ObjectManager function:
void persist(object $object = null)
You only need to call it on new entities.
My question though, is "when" should it be called? Immediately after creating the entity, or immediately before flushing it?
I can't find any documentation indicating the convention. The reason this is important is because Doctrine dispatches the "persist event" when calling.
Given that the object might still be empty, it seems to imply that any functionality tagged on to that event should disregard the importance of the data the object contains at that point in time.
Am I correct in that statement or is there a convention Doctrine promotes?
What you want to do is create your new object, use it anyway you want, and when you're done with it and want to send it to your database, then persist it, just before flushing it.
If you persisted your entity just after creating it, any changes you would make wouldn't be taken into account when sent to the database.

Doctrine creates new row when editing entity from cache

I have retrieved a simple (no joins) serialised entity from a Redis cache.
When I edit this entity and call persist() and flush() on the entity manager, a new row is inserted instead of updating the existing entity.
The Doctrine documentation only seems to mention calling merge() with the entity but that fetches the item from the database which defeats the purpose of caching it.
How can I make the entity manager track this restored entity?
Thanks
Unfortunately, you do need to call merge() - this is just so that the EntityManager is properly aware of the entity. If you serialise the entity and then load it back up in a new session then the EntityManager now only knows the type and the attributes - it doesn't know if it has been previously saved to the database etc, which is why it just assumes it is new and creates a new row unless merged.
The EntityManager has an internal list of what's been saved, what's pending etc; it needs all this info to correctly do it's job - these details are not stored in the entities themselves. An unserialised entity read from cache is considered "unmanaged".
From the Doctrine docs on this:
In Doctrine an entity objects has to be “managed” by an EntityManager
to be updateable. Entities saved into the session are not managed in
the next request anymore. This means that you have to register these
entities with an EntityManager again if you want to change them or use
them as part of references between other entities. You can achieve
this by calling EntityManager#merge().
It doesn't completely defeat the purpose of caching it, because really you only need to merge at the point that you want to save back to the database.

Doctrine refresh copy of entity

I have a CustomerAccount entity. After that entity has had changes made to it via a form, but before the entity has been persisted to the database, I need to fetch a new copy of the same CustomerAccount with the entity as it currently exists in the database. The reason I need to do this is I want to fire off a changed event with both the old and new data in my service.
One hack I used was $oldAccount = unserialize(serialize($account)); and passing the old into my service, but thats really hackish.
What I would really like to do is have Doctrine pull back a copy of the original entity (while keeping the changes to the new version).
Is this even possible?
Update
It appears what I really want to do is ultimately impossible at this time with the way Doctrine is architected.
Update 2
I added the solution I ultimately ended up using at the bottom. I'm not completely happy with it because it feels hackish, but it gets the job done and allows me to move on.
It depends.
I mean, Doctrine2 use the IdentityMap that prevents you "accidentally" query the db for the same object over and over again into the same request. The only way to force doctrine fetch entity object again is to detach the entity from the entity manager and request entity again.
This, however, could lead to some strange behaviour that could "slip" out of your control:
you can't persist again a detached object
if you try to persist an object that is related ("linked") to your detached entity you will run into troubles (and sometimes is very difficult to debug)
So, why don't you try with php built-in clone function? Maybe is more suitable for you and could save you from a lot of debugging
Code example:
$em = $this->getDoctrine()->getManager();
$fetched_entity = $em->findOnById(12);
$cloned_entity = clone $fetched_entity;
//and so on ...
Here is the ultimate solution I ended up using. I created a duplicate entity manager in my config.yml and retrieved a second copy of the entity from the duplicate entity manager. Because I won't make any changes to the entity retrieved by the duplicate entity manager, this solution was the best for my use case.

Categories