在更新另一个实体的同时创建实体

时间:2018-07-11 16:04:31

标签: symfony doctrine-orm

我有一个问题似乎找不到答案。

我的用户实体带有“状态”字段。

我想做的是,每次更改用户状态以跟踪我的用户状态历史记录时,都会在另一表“ StatusEvent”中存储新行。

我尝试使用PreUpdate方法,但是此步骤不允许创建新的实体。

我可能在想其他事件(可能是onFlush吗?)可能会发生,但是这些事件没有PreUpdate的LifecycleEventArgs方法(可以知道某个字段是否已更改)。

任何人都已经遇到过相同的模式,或者对如何实现它有想法?

先谢谢了

3 个答案:

答案 0 :(得分:3)

这是使用自定义事件和侦听器的好例子。

创建一个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
    }
}

然后您需要将您的侦听器注册为服务并对其进行标记

AppBundle\Event\Listener\UserListener:
    tags:
        - { name: kernel.event_listener, event: user.status.changed, method: onStatusChanged }

现在,您要做的就是每次状态更改时都分配一个新的事件实例,并将刚刚保留的用户传递给该事件。

$eventDispatcher->dispatch(
    UserEvents::STATUS_CHANGED,
    $user
);

编辑:为维护自定义事件的手动调度与onFlush的自动调度之间的关系,即使对于不知道如何/何时触发生命周期事件或如何触发事件生命周期的新手,也更容易阅读自定义事件代码。实体经理在内部工作。最重要的是,分派可以很好地提醒您那里有一个侦听器,这将在几个月后重新访问代码时很有用。

答案 1 :(得分:2)

@Dimitris提供的解决方案可行,但需要您手动调度事件。

我会像您提到的那样使用private void obtainAllRequirements() { String output = executeRESTCall(baseUrl + "/abstractitems?itemType=43&startAt=0"); int totalResults = new JSONObject(output).getJSONObject("meta").getJSONObject("pageInfo") .getInt("totalResults"); ExecutorService service = Executors.newFixedThreadPool(THREADS); List<Callable<Void>> tasks = new ArrayList<>(); for (int i = 0; i < Math.ceil(totalResults / MAX_RESULTS); i++) { final int iteration = i; tasks.add(new Callable<Void>() { @Override public Void call() throws Exception { try final String request = baseUrl + "/abstractitems?maxResults=" + MAX_RESULTS + "&itemType=43&startAt=" + (iteration * MAX_RESULTS); // hash codes to tie the request and responses together, // since multithreading will have them printing interleaved System.out.println(hashCode() + ":request: " + request); String response = executeRESTCall(request); System.out.println(hashCode() + ":response: " + response); JSONObject obj = new JSONObject(response); createRequirements(obj); return null; } catch (Exception e) { e.printStackTrace(); } } }); } try { service.invokeAll(tasks); service.shutdown(); // you might want to await termination ? service.awaitTermination(1, TimeUnit.MINUTES); // catch all exceptions ? // you'll need some better error handling } catch (Exception e) { e.printStackTrace(); } } 方法。 (如果您正在编写库,那么最好使用自定义事件)

您可以使用UnitOfWork获取更改集。

onFlush

此侦听器的权衡之处在于,它只会将内容记录在flush上。 如果您的实体在刷新前多次更改状态,则仅记录最后一个状态。例如state1-> 2-> 3将仅记录为state1-> 3

如果您打算创建具有许多状态和转换的复杂状态字段,请查看工作流程组件并从那里使用侦听器。还有很多工作,但是值得。

答案 2 :(得分:0)

所以我按照Dimitris和Padam67的建议做了什么。

  • 定义一个侦听onFlush事件的DoctrineListener并将其注册
  • 在DoctrineListener中调度自定义事件
  • 定义一个监听我的自定义事件的EventSubscriber
  • 定义处理程序以管理逻辑
  • 从EventSubscriber调用处理程序

我知道它包含很多文件,但我想尽可能地将所有内容分开,以使代码更清晰,更简单:)

定义一个侦听onFlush事件的DoctrineListener:

config/services.yaml

App\EventListener\Doctrine\DoctrineListener:
    tags:
        - { name: doctrine.event_listener, event: onFlush }

App\EventListener\Doctrine\DoctrineListener.php

<?php

namespace App\EventListener\Doctrine;

use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\UnitOfWork;
use App\Event\TalentStatusChangedEvent;
use App\Entity\Talent;
use App\Event\Constants\TalentEvents;

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

App\Event\TalentStatusChangedEvent.php

<?php

namespace App\Event;

use App\Entity\Talent;
use Symfony\Component\EventDispatcher\Event;

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(定义了一个单独的文件,其中包含每种类型的所有事件)

App\EventListener\Admin\User\TalentSubscriber.php

<?php

namespace App\EventListener\Admin\User;

use App\Domain\User\StatusChanged\StatusChangedHandler;
use App\Entity\Talent;
use App\Event\Constants\TalentEvents;
use App\Event\TalentStatusChangedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

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);
    }
}

定义一个处理程序以实际管理链接实体的创建

App\Domain\User\StatusChanged.php

<?php

namespace App\Domain\User\StatusChanged;

use App\Entity\Talent;
use App\Entity\TalentStatusEvent;
use App\Event\TalentStatusChangedEvent;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;

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');
    }
}