我有一个问题似乎找不到答案。
我有一个带有"状态"字段的用户实体。
我想做的是,每次用户的状态更改时,在另一个表"StatusEvent"中存储一行新行,以跟踪我的用户的状态历史。
我尝试使用PreUpdate方法,但它不允许在此步骤中创建新实体。
我可能在想,其他事件(可能是onFlush?(也有可能,但这些事件没有PreUpdate中的LifecycleEventArgs方法(可以知道字段是否已更改(。
有人已经遇到过同样的模式,或者对我如何实现它有想法吗?
提前感谢
这是一个使用自定义事件和侦听器的好例子。
创建一个类UserEvents来保存一个事件名为的常量
class UserEvents
{
const STATUS_CHANGED = 'user.status.changed';
}
创建一个UserStatusChangedEvent,它扩展Event并将用户作为参数。
class UserChangedEvent extends Event
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function getUser(): User
{
return $this->user;
}
}
然后创建并注册一个侦听器来捕获/处理该事件,并使用用户对象的数据创建所需的条目,该数据在事件被调度时传递给了用户对象。
class UserListener
{
public function onStatusChanged(UserChangedEvent $event)
{
$user = $event->getUser();
//TODO: Create your new status change entry. If you need the entity manager, just inject it in the constructor, like with any other service
}
}
然后,您需要将侦听器注册为服务,并将其标记为
AppBundleEventListenerUserListener:
tags:
- { name: kernel.event_listener, event: user.status.changed, method: onStatusChanged }
现在,您所要做的就是在每次状态更改时调度一个新的事件实例,并将其传递给您刚刚持久化的用户。
$eventDispatcher->dispatch(
UserEvents::STATUS_CHANGED,
$user
);
编辑:为了保护自定义事件的手动调度与onFlush的自动调度,即使是不知道条令生命周期事件是如何/何时触发的,也不知道实体管理器内部是如何工作的新手,也更容易阅读自定义事件代码。最重要的是,调度可以很好地提醒您有一个监听器,这在几个月后重新访问代码时会很有用。
@Dimitris的解决方案可以工作,但需要手动调度事件。
我会使用你提到的onFlush
方法。(如果你正在写一个库,你最好使用自定义事件(
您可以使用UnitOfWork来获取变更集。
public function onFlush(OnFlushEventArgs $event)
{
$em = $event->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
$this->newEntities[] = $entity;
if ($entity instanceof User) {
$changeSet = $uow->getEntityChangeSet($entity);
// if the $changeSet contains the status, log the change
$log = new Log();
$em->persist($log);
$uow->computeChangeSet($em->getClassMetadata(Log::class), $log);
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
// same here, create a private method to avoid duplication
}
}
这个监听器的优点是它只会在flush上记录事情。如果实体在刷新前多次更改状态,则只记录最后一个状态。例如状态1->2->3将只记录为状态1->3
如果您计划创建一个包含许多状态和转换的复杂状态字段,请查看工作流组件并使用其中的侦听器。这是一项更多的工作,但非常值得。
所以我听从了Dimitris和Padam67的建议。
- 定义一个DoctrineListener来侦听onFlush事件并注册它
- 在DoctrineListener中发送自定义事件
- 定义EventSubscriber侦听我的自定义事件
- 定义一个处理程序来管理逻辑
- 从EventSubscriber调用处理程序
我知道它会生成很多文件,但我喜欢尽可能地将所有内容分开,以便读取更干净、更简单的代码:(
定义一个侦听onFlush事件的DoctrineListener:
config/services.yaml
AppEventListenerDoctrineDoctrineListener:
tags:
- { name: doctrine.event_listener, event: onFlush }
AppEventListenerDoctrineDoctrineListener.php
<?php
namespace AppEventListenerDoctrine;
use PsrLogLoggerInterface;
use SymfonyComponentEventDispatcherEventDispatcherInterface;
use DoctrineORMEventOnFlushEventArgs;
use DoctrineORMUnitOfWork;
use AppEventTalentStatusChangedEvent;
use AppEntityTalent;
use AppEventConstantsTalentEvents;
class DoctrineListener
{
private $logger;
private $dispatcher;
public function __construct(
LoggerInterface $logger,
EventDispatcherInterface $dispatcher
) {
$this->logger = $logger;
$this->dispatcher = $dispatcher;
}
public function onFlush(OnFlushEventArgs $event)
{
$entityManager = $event->getEntityManager();
$uow = $entityManager->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof Talent) {
$this->createTalentStatusChangedEvent($entity, $uow);
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof Talent) {
$this->createTalentStatusChangedEvent($entity, $uow);
}
}
}
private function createTalentStatusChangedEvent(Talent $entity, UnitOfWork $uow)
{
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - start');
$changeSet = $uow->getEntityChangeSet($entity);
if (array_key_exists('status', $changeSet)) {
$talentStatusChangedEvent = new TalentStatusChangedEvent($entity, new DateTime());
$this->dispatcher->dispatch(TalentEvents::STATUS_CHANGED, $talentStatusChangedEvent);
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - success');
} else {
$this->logger->info(self::class . ' - talentStatusChanged: ' . $entity . ' - fail');
}
}
}
定义TalentStatusChangedEvent
AppEventTalentStatusChangedEvent.php
<?php
namespace AppEvent;
use AppEntityTalent;
use SymfonyComponentEventDispatcherEvent;
class TalentStatusChangedEvent extends Event
{
private $talent;
private $statusChangedDate;
public function __construct(Talent $talent, DateTime $date)
{
$this->talent = $talent;
$this->statusChangedDate = $date;
}
public function getTalent()
{
return $this->talent;
}
public function getStatus()
{
return $this->talent->getStatus();
}
public function getStatusChangedDate()
{
return $this->statusChangedDate;
}
}
为我的事件定义EventSubscriber(定义了一个单独的文件,其中包含每个类型的所有事件(
AppEventListenerAdminUserTalentSubscriber.php
<?php
namespace AppEventListenerAdminUser;
use AppDomainUserStatusChangedStatusChangedHandler;
use AppEntityTalent;
use AppEventConstantsTalentEvents;
use AppEventTalentStatusChangedEvent;
use SymfonyComponentEventDispatcherEventSubscriberInterface;
class TalentSubscriber implements EventSubscriberInterface
{
private $statusChangedHandler;
public function __construct(
StatusChangedHandler $statusChangedHandler
) {
$this->statusChangedHandler = $statusChangedHandler;
}
public static function getSubscribedEvents()
{
return array(
TalentEvents::STATUS_CHANGED => 'statusChanged',
);
}
public function statusChanged(TalentStatusChangedEvent $event) {
$this->statusChangedHandler->handle($event);
}
}
定义一个处理程序来实际管理链接实体的创建
AppDomainUserStatusChanged.php
<?php
namespace AppDomainUserStatusChanged;
use AppEntityTalent;
use AppEntityTalentStatusEvent;
use AppEventTalentStatusChangedEvent;
use DoctrineORMEntityManagerInterface;
use PsrLogLoggerInterface;
class StatusChangedHandler
{
private $entityManager;
private $logger;
public function __construct(
EntityManagerInterface $entityManager,
LoggerInterface $logger
) {
$this->entityManager = $entityManager;
$this->logger = $logger;
}
public function handle(TalentStatusChangedEvent $event)
{
$this->logger->info(self::class . ' - Talent ' . $event->getTalent() . ' - start');
$talentStatusEvent = new TalentStatusEvent();
$talentStatusEvent->setTalent($event->getTalent());
$talentStatusEvent->setStatus($event->getStatus());
$this->entityManager->persist($talentStatusEvent);
// Calling ComputeChangeSet and not flush because we are during the onFlush cycle
$this->entityManager->getUnitOfWork()->computeChangeSet(
$this->entityManager->getClassMetadata(TalentStatusEvent::class),
$talentStatusEvent
);
$this->logger->info(self::class . ' - Talent ' . $event->getTalent() . ' - success');
}
}