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).
Related
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).
Consider next steps in Symfony application:
Register listener for Doctrine`s postFlush event that dump results of getScheduledEntityInsertions() of Unit of Work.
Create instance of SomeEntity class.
Call Entity Manager`s persist and flush methods with previously created entity instance
postFlush listener fired
getScheduledEntityInsertions() call return nothing
Expected behaviour is that at last step I see a list of inserted entities, but if you look at executeInserts() method of UoF, unset($this->entityInsertions[$oid]) called for each entity so nothig is returned from getScheduledEntityInsertions() call.
Iam found declined patch for that case: https://github.com/doctrine/doctrine2/pull/5674 and Ocramius suggestion to use "onFlush" event to collect all needed data and fired custom event: https://groups.google.com/forum/#!topic/doctrine-user/GLJEx0p5kL4
But, I dont understand, how it can be done: if I register custom listener for myCustomEvent and fire it from onFlush with list of not inserted entities - it will be executed before entities actually saved to database (postFlush fired) and cant be received from database in this listener.
One of possible solutions is to use one Listener class for onFlush and postFlush event, and in onFlush populate internal property with necessary data, in postFlush read data from property. But this solution has limitations: both listener-methods must be defined same class; listener is not thread-safe.
The doctrine interface Doctrine\Common\Persistence\ObjectManager defines the flush method as having no parameters. Yet the implementation Doctrine\ORM\EntityManager allows a single entity to be passed.
Aside from the, IMO, bad programming style, Is this anything I need to be worried about?
I know that PHP will ignore any extra parameters if they are not declared in the method. Which would cause a non ORM manager to flush all entities.
I ask because I'm trying to write my code in such a way that the ORM is configurable and can switched at a later date. Now, while writing a batch import class, I have found that calling flush without an entity causes memory leaks, it also effects a 'progress/history' entity I use outside of the main import loop. So it's pretty important I only flush certain entities.
I have noticed the differences between the definition and implementation of flush() as well. That may be a question only the developers of doctrine can answer.
Short Answer
Don't worry about it.
Long Answer
We can still address the differences and how they affect your application.
According to doctrine's documentation, flush() is the only method that will persist changes to your domain objects.
Other methods, such as persist() and remove() only place that object in a queue to be updated.
It is very important to understand that only EntityManager#flush() ever causes write operations against the database to be executed. Any other methods such as EntityManager#persist($entity) or EntityManager#remove($entity) only notify the UnitOfWork to perform these operations during flush.
Not calling EntityManager#flush() will lead to all changes during that request being lost.
Performance
Flushing individual entities at a time may cause performance issues in itself. Each flush() is a new trip to the database. Large sums of calls to flush() may slow down your application.
The flush() method should not be affecting your progress/history entity unless you are intentionally making changes to it. But, if that is the case, and you still do not want progress/history entity to be updated when flush() is executed, you can detach the entity from doctrine. This will allow you to make changes to the entity without doctrine being aware of those changes. Therefore, it will not be affected by flush().
When you are ready for the entity to be re-attached to doctrine, you can use the merge method provided by your entity manager. Then call flush() one last time to merge the changes.
In my particular case I have the following:
I have an entity which at some point may be automatically removed as an orphan. Now, I need to react on its removal. Moreover, I do not know exactly if this reaction will or will not involve some doctrine operations . Particularly, I need to trigger some method on some decoupled component and I do not know this component`s implementation details. For example, my default implementation of that component makes use of Doctrine and need to remove some entity when the previously mentioned entity has been removed.
Now the problem itself:
I know that the EntityManager flush operation can not be triggered in the lifecycle events (It is mentioned in the docs, and it is because the lifecycle events occur in the flush method). Particularly it can not be triggered in the postRemove event. So, the problem is that, after some entity removal, I need to trigger some action that may (or may not) trigger doctrine entitymanager flush method but I can not do it in the postRemove event. And I do not know any other place where I can do it safely.
Ok, finaly I came to the following decision:
In the postRemove action I just add some action (as a simple callable) to the queue. Later on, in the postFlush event I process the queued actions (Its ok, because postFlush event happens after the flush process has been finished).
To implement this in some general way I created a very simple queue manager library (https://github.com/numesmat/QueueManager) and a corresponding symfony bundle to wire it up (https://github.com/numesmat/QueueManagerBundle).
Now, to add some postRemove logic I just need to do something like this in my event subscriber:
public function postRemove(LifecycleEventArgs $args) {
$queueManager = $this->container->get('arko.queue_manager');
$queueManager->add(function() {
// Some logic that can potentially use doctrine flush here
}, 'my_queue_name');
}
public function postFlush(PostFlushEventArgs $args) {
$queueManager = $this->container->get('arko.queue_manager');
$queueManager>process('my_queue_name');
}
In the next few days I gonna write readme to my small queue manager library and bundle. Right now, this library or bundle may be installed using composer (arko/queue-manager and arko/queue-manager-bundle composer packages respectively) and used as simple as described above.
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.