Reputation: 20286
I need to create changelog in the API for user actions on entities.
For example:
User updates entity Licensor I need to catch the changes and save them in the database in different table.
The first part I was able to do with Doctrine Event Listener
class ChangelogEventListener
{
public function preUpdate($obj, PreUpdateEventArgs $eventArgs)
{
if ($obj instanceof LoggableInterface) {
dump($eventArgs->getEntityChangeSet());
}
}
}
And with marking entity event listeners
/**
* @ORM\EntityListeners(value={"AppBundle\EventSubscriber\Changelogger\ChangelogEventListener"})
*/
class Licensor implements LoggableInterface
But I'm not sure if it's even possible and if it makes sense to access the ORM entity manager in a preUpdate
event.
If it isn't then what's the proper way to do it?
I've tried with Symfony's EventListener instead of Doctrine's but then I don't have access to getEntityChangeSet(
).
Upvotes: 0
Views: 5386
Reputation: 12750
You are better off using an event listener for such thing. What you want is more like a database trigger to log changes. See example below (tested and works fine) which logs User
entity changes in UserAudit
entity. For demonstration purposes, it only watches username
and password
field but you can modify it as you wish.
Note: If you want an entity listener then look at this example.
services.yml
services:
application_backend.event_listener.user_entity_audit:
class: Application\BackendBundle\EventListener\UserEntityAuditListener
arguments: [ @security.context ]
tags:
- { name: doctrine.event_listener, event: preUpdate }
- { name: doctrine.event_listener, event: postFlush }
UserEntityAuditListener
namespace Application\BackendBundle\EventListener;
use Application\BackendBundle\Entity\User;
use Application\BackendBundle\Entity\UserAudit;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Symfony\Component\Security\Core\SecurityContextInterface;
class UserEntityAuditListener
{
private $securityContext;
private $fields = ['username', 'password'];
private $audit = [];
public function __construct(SecurityContextInterface $securityContextInterface)
{
$this->securityContext = $securityContextInterface;
}
public function preUpdate(PreUpdateEventArgs $args) // OR LifecycleEventArgs
{
$entity = $args->getEntity();
if ($entity instanceof User) {
foreach ($this->fields as $field) {
if ($args->getOldValue($field) != $args->getNewValue($field)) {
$audit = new UserAudit();
$audit->setField($field);
$audit->setOld($args->getOldValue($field));
$audit->setNew($args->getNewValue($field));
$audit->setUser($this->securityContext->getToken()->getUsername());
$this->audit[] = $audit;
}
}
}
}
public function postFlush(PostFlushEventArgs $args)
{
if (! empty($this->audit)) {
$em = $args->getEntityManager();
foreach ($this->audit as $audit) {
$em->persist($audit);
}
$this->audit = [];
$em->flush();
}
}
}
Upvotes: 3
Reputation: 8276
Check out Doctrine events, and specifically the preUpdate event. This event is the most restrictive, but you do have access to all of the fields that have changed, and their old/new values. You can change the values here on the entity being updated, unless it's an associated entity.
Check out this answer, which suggests using an event subscriber, and then persisting to a logging entity.
There is also this blog post that uses the preUpdate
event to save a bunch of changesets to the internal listener class, then postFlush
it persists any entities that are being changed, and calls flush
again. However, I would not recommend this, as the Doctrine documentation explicitly states:
postFlush is called at the end of EntityManager#flush(). EntityManager#flush() can NOT be called safely inside its listeners.
If you went the route of that blog post you'd be better off using the onFlush()
event and then doing your computeChangeSets()
call after your persist()
, like the first answer I posted.
You can find a similar example here:
Upvotes: 3