将 getEntityChangeSet() 结果保存在 Doctrine EventListener 中

Saving the getEntityChangeSet() result in Doctrine EventListener

我需要在 API 中为用户对实体的操作创建更新日志。

例如:

用户更新实体许可方我需要捕获更改并将它们保存在不同的数据库中 table。

我能够使用 Doctrine 事件监听器完成的第一部分

class ChangelogEventListener
{
   public function preUpdate($obj, PreUpdateEventArgs $eventArgs)
   {
       if ($obj instanceof LoggableInterface) {
            dump($eventArgs->getEntityChangeSet());
       }
   }
}

并带有标记实体事件监听器

/**
 * @ORM\EntityListeners(value={"AppBundle\EventSubscriber\Changelogger\ChangelogEventListener"})
 */
class Licensor implements LoggableInterface

但我不确定在 preUpdate 事件中访问 ORM 实体管理器是否可行以及是否有意义。

如果不是,那么正确的方法是什么?

我试过使用 Symfony 的 EventListener 而不是 Doctrine 的,但是我无法访问 getEntityChangeSet()。

查看 Doctrine events, and specifically the preUpdate event。此事件是最严格的,但您确实可以访问所有已更改的字段及其 old/new 值。您可以更改正在更新的实体的值,除非它是关联实体。

检查 this answer, which suggests using an event subscriber,然后持久化到日志记录实体。

还有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.

如果你选择了那个博客 post 的路线,你最好使用 onFlush() 事件,然后在 persist() 之后进行 computeChangeSets() 调用,就像我 posted.

的第一个答案

你可以找到类似的 example here:

你最好使用 事件侦听器 来做这样的事情。您想要的更像是一个数据库触发器来记录更改。请参阅下面的示例( 测试并且工作正常),它记录了 User 实体在 UserAudit 实体中的更改。出于演示目的,它只监视 usernamepassword 字段,但您可以根据需要修改它。

注意:如果您想要 实体侦听器,请查看 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();
        }
    }
}