fix: propagate updated at timestamp to parent entity
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Entity\BlameableEntity;
|
||||
use App\Entity\BlameableEntityInterface;
|
||||
use App\Entity\TimestampableEntity;
|
||||
use App\Entity\TimestampableEntityInterface;
|
||||
use App\Entity\TimestampableParentAwareInterface;
|
||||
use App\Enum\Groups\PriceType;
|
||||
use App\Enum\Groups\Season;
|
||||
use App\Repository\Groups\AccommodationPriceRepository;
|
||||
@@ -14,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AccommodationPriceRepository::class)]
|
||||
class AccommodationPrice implements BlameableEntityInterface, TimestampableEntityInterface
|
||||
class AccommodationPrice implements BlameableEntityInterface, TimestampableEntityInterface, TimestampableParentAwareInterface
|
||||
{
|
||||
use BlameableEntity;
|
||||
use TimestampableEntity;
|
||||
@@ -89,6 +90,11 @@ class AccommodationPrice implements BlameableEntityInterface, TimestampableEntit
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimestampableParent(): ?TimestampableEntityInterface
|
||||
{
|
||||
return $this->accommodation;
|
||||
}
|
||||
|
||||
public function getDateFrom(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->dateFrom;
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\BlameableEntity;
|
||||
use App\Entity\BlameableEntityInterface;
|
||||
use App\Entity\TimestampableEntity;
|
||||
use App\Entity\TimestampableEntityInterface;
|
||||
use App\Entity\TimestampableParentAwareInterface;
|
||||
use App\Enum\Groups\AdditionalServiceType;
|
||||
use App\Repository\Groups\AdditionalServiceRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
@@ -13,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: AdditionalServiceRepository::class)]
|
||||
class AdditionalService implements BlameableEntityInterface, TimestampableEntityInterface
|
||||
class AdditionalService implements BlameableEntityInterface, TimestampableEntityInterface, TimestampableParentAwareInterface
|
||||
{
|
||||
use BlameableEntity;
|
||||
use TimestampableEntity;
|
||||
@@ -87,6 +88,11 @@ class AdditionalService implements BlameableEntityInterface, TimestampableEntity
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimestampableParent(): ?TimestampableEntityInterface
|
||||
{
|
||||
return $this->accommodation;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
|
||||
@@ -6,13 +6,14 @@ use App\Entity\BlameableEntity;
|
||||
use App\Entity\BlameableEntityInterface;
|
||||
use App\Entity\TimestampableEntity;
|
||||
use App\Entity\TimestampableEntityInterface;
|
||||
use App\Entity\TimestampableParentAwareInterface;
|
||||
use App\Repository\Groups\BoardServiceRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: BoardServiceRepository::class)]
|
||||
class BoardService implements BlameableEntityInterface, TimestampableEntityInterface
|
||||
class BoardService implements BlameableEntityInterface, TimestampableEntityInterface, TimestampableParentAwareInterface
|
||||
{
|
||||
use BlameableEntity;
|
||||
use TimestampableEntity;
|
||||
@@ -67,6 +68,11 @@ class BoardService implements BlameableEntityInterface, TimestampableEntityInter
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimestampableParent(): ?TimestampableEntityInterface
|
||||
{
|
||||
return $this->accommodation;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
interface TimestampableParentAwareInterface
|
||||
{
|
||||
/**
|
||||
* The entity whose updatedAt should be bumped whenever this entity is created, changed or removed.
|
||||
*/
|
||||
public function getTimestampableParent(): ?TimestampableEntityInterface;
|
||||
}
|
||||
@@ -3,13 +3,16 @@
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\TimestampableEntityInterface;
|
||||
use App\Entity\TimestampableParentAwareInterface;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
#[AsDoctrineListener(Events::prePersist)]
|
||||
#[AsDoctrineListener(Events::preUpdate)]
|
||||
#[AsDoctrineListener(Events::onFlush)]
|
||||
class TimestampableEntityListener
|
||||
{
|
||||
public function prePersist(PrePersistEventArgs $args): void
|
||||
@@ -31,4 +34,55 @@ class TimestampableEntityListener
|
||||
$entity->setUpdatedAt($now);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Doctrine has no notion of "collection member changed, so the owner changed". Entities that
|
||||
* only make sense as part of their parent therefore push their own changes upwards, so the
|
||||
* parent's updatedAt reflects the last change to the aggregate as a whole.
|
||||
*/
|
||||
public function onFlush(OnFlushEventArgs $args): void
|
||||
{
|
||||
$entityManager = $args->getObjectManager();
|
||||
$unitOfWork = $entityManager->getUnitOfWork();
|
||||
|
||||
$scheduled = [
|
||||
...$unitOfWork->getScheduledEntityInsertions(),
|
||||
...$unitOfWork->getScheduledEntityUpdates(),
|
||||
...$unitOfWork->getScheduledEntityDeletions(),
|
||||
];
|
||||
|
||||
/** @var array<int, TimestampableEntityInterface> $parents */
|
||||
$parents = [];
|
||||
|
||||
foreach ($scheduled as $entity) {
|
||||
if (!$entity instanceof TimestampableParentAwareInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parent = $entity->getTimestampableParent();
|
||||
|
||||
// A parent created in this very flush already gets its createdAt, and one being
|
||||
// removed must not be resurrected by an update.
|
||||
if (null === $parent
|
||||
|| $unitOfWork->isScheduledForInsert($parent)
|
||||
|| $unitOfWork->isScheduledForDelete($parent)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parents[spl_object_id($parent)] = $parent;
|
||||
}
|
||||
|
||||
$now = new \DateTimeImmutable();
|
||||
|
||||
foreach ($parents as $parent) {
|
||||
$parent->setUpdatedAt($now);
|
||||
|
||||
// Registers the parent as an update even when it had no changeset of its own. The
|
||||
// preUpdate listeners run afterwards, so updatedBy stays in sync with updatedAt.
|
||||
$unitOfWork->recomputeSingleEntityChangeSet(
|
||||
$entityManager->getClassMetadata($parent::class),
|
||||
$parent,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user