Quickly create entities logs with onFlush method on Symfony
The more I develop, the more I'm logging everything I code.
These can be logs for the backend, but also histories for the users. A simple way to create a history is to log all changes made to the database.
Doctrine gives us access to the method onFlush to get all data that will be updated, added, or deleted before flushing.
This is a guide to quickly create logs inside Doctrine methods.
Connect onFlush method
Nothing here is out of usual things, we connect on the method onFlush
of Doctrine.
First, we will connect to the Doctrine event.
#config\services.yaml
App\EventListener\DatabaseOnFlushListener:
tags:
- # these are the options required to define the entity listener
name: 'doctrine.event_listener'
event: 'onFlush'
Then, we create our class with our onFlush
method.
# src\EventListener\DatabaseOnFlushListener.php
class DatabaseOnFlushListener
{
/**
* @param OnFlushEventArgs $eventArgs
*/
public function onFlush(OnFlushEventArgs $eventArgs): void
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
// We will add methods there
}
}
UnitOfWork
contains database context, meaning all modifications that will be applied to our database.
Observe entities changes
# src\EventListener\DatabaseOnFlushListener.php
class DatabaseOnFlushListener
{
/**
* @param OnFlushEventArgs $eventArgs
*/
public function onFlush(OnFlushEventArgs $eventArgs): void
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
}
//... Check the documentation to get others methods
}
}
Check the documentation to read more about available methods on UnitOfWork
:
We now have access to :
instanceof
to filter on an entity typegetEntityChangeSet
to compare old and new valuescomputeChangeSet
to insert a new entityrecomputeSingleEntityChangeSet
to update an existing entity (be really careful here)
Let's take an example: we want to create a log every time we update a user.
# src\EventListener\DatabaseOnFlushListener.php
class DatabaseOnFlushListener
{
/**
* @param OnFlushEventArgs $eventArgs
*/
public function onFlush(OnFlushEventArgs $eventArgs): void
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof User) {
$uow->computeChangeSets();
// We check if our entity contains some changes
$changeSet = $uow->getEntityChangeSet($entity);
if ($changeSet) {
$log = new Log();
$em->persist($log);
$uow->computeChangeSet($em->getClassMetadata(get_class($log)), $log);
}
}
}
}
}
We get changeSet
to check if the entity contains changes, then we create a new object Log
that we insert.
But changeSet
contains more information. It allows checking on each field of the user entity.
We suppose that the class User
has one field enabled, and we only want to log when the user is moving from enabled to disabled
changeSet
contains an index enabled
is an array with 2 values :
[0]
contains the old value[1]
contains the new value
if ($changeSet && isset($changeSet['enabled']) && $changeSet['enabled'][1] === false) {
$log = new Log();
$em->persist($log);
$uow->computeChangeSet($em->getClassMetadata(get_class($log)), $log);
}
Summary
onFlush
is quite an easy way to create logs on your entities since each time your entity is updated, it will call your function inside.
But, and this is a huge but!
Even if it's possible, I do not recommend modifying your entities inside the onFlush method.
Instead, update all your data in your service.
You should not add any logic inside onFlush, since as the name suggests, it is only used to flush your database.