From b4cadb96607fe9f880f395d2bd46d305d4ec750c Mon Sep 17 00:00:00 2001 From: Cristoforo Cervino Date: Mon, 16 Oct 2023 17:37:18 +0200 Subject: [PATCH] Do not override your `createdAt` and `updatedAt` values when you set them explicitly --- README.md | 4 +++- .../TimestampableEventSubscriber.php | 15 ++++++++++---- tests/Functional/TimestampableTest.php | 20 +++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a024c25..e24d5f0 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,9 @@ $ composer require andanteproject/timestampable-bundle ## Features - No configuration required to be ready to go but fully customizabile; - `createdAt` and `updatedAt` properties are `?\DateTimeImmutable`; -- No annotation required; +- Uses [Symfony Clock](https://symfony.com/doc/current/components/clock.html); +- Does not override your `createdAt` and `updatedAt` values when you set them explicitly; +- No annotation/attributes required; - Works like magic ✨. ## Basic usage diff --git a/src/EventSubscriber/TimestampableEventSubscriber.php b/src/EventSubscriber/TimestampableEventSubscriber.php index 56d64c8..9a1cb5c 100644 --- a/src/EventSubscriber/TimestampableEventSubscriber.php +++ b/src/EventSubscriber/TimestampableEventSubscriber.php @@ -10,8 +10,9 @@ use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Event\LoadClassMetadataEventArgs; +use Doctrine\ORM\Event\PrePersistEventArgs; use Doctrine\ORM\Events; -use Doctrine\Persistence\Event\LifecycleEventArgs; +use Doctrine\Persistence\Event\PreUpdateEventArgs; use Psr\Clock\ClockInterface; class TimestampableEventSubscriber implements EventSubscriber @@ -36,7 +37,7 @@ public function getSubscribedEvents(): array ]; } - public function prePersist(LifecycleEventArgs $onFlushEventArgs): void + public function prePersist(PrePersistEventArgs $onFlushEventArgs): void { $entity = $onFlushEventArgs->getObject(); if ($entity instanceof CreatedAtTimestampableInterface && null === $entity->getCreatedAt()) { @@ -44,11 +45,17 @@ public function prePersist(LifecycleEventArgs $onFlushEventArgs): void } } - public function preUpdate(LifecycleEventArgs $onFlushEventArgs): void + public function preUpdate(PreUpdateEventArgs $onFlushEventArgs): void { $entity = $onFlushEventArgs->getObject(); if ($entity instanceof UpdatedAtTimestampableInterface) { - $entity->setUpdatedAt($this->clock->now()); + // Skipping update of updatedAt property if it has been changed manually + $metadata = $onFlushEventArgs->getObjectManager()->getClassMetadata(\get_class($entity)); + $updatedAtPropertyName = $this->configuration->getUpdatedAtPropertyNameForClass($metadata->getName()); + $entityUpdatedPropertiesNames = \array_keys($onFlushEventArgs->getEntityChangeSet()); + if (!\in_array($updatedAtPropertyName, $entityUpdatedPropertiesNames, true)) { + $entity->setUpdatedAt($this->clock->now()); + } } } diff --git a/tests/Functional/TimestampableTest.php b/tests/Functional/TimestampableTest.php index 7e49df2..f590840 100644 --- a/tests/Functional/TimestampableTest.php +++ b/tests/Functional/TimestampableTest.php @@ -122,5 +122,25 @@ public function testShouldSetTimestamps(): void $updatedAtOrganization1->format(\DateTimeInterface::ATOM), $organization1->getUpdatedAt()->format(\DateTimeInterface::ATOM) ); + + // Manual changing updatedAt property should not trigger the subscriber + $currentOrganization1CreatedAt = $organization1->getCreatedAt(); + /** @var \DateTimeImmutable $newOrganization1UpdatedAt */ + $newOrganization1UpdatedAt = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2023-01-01 00:00:00'); + $organization1->setUpdatedAt($newOrganization1UpdatedAt); + + \sleep(2); + $em->flush(); + \sleep(2); // Giving time to mysqlite to update file + + self::assertSame( + $currentOrganization1CreatedAt->format(\DateTimeInterface::ATOM), + $organization1->getCreatedAt()?->format(\DateTimeInterface::ATOM) + ); + + self::assertSame( + $newOrganization1UpdatedAt->format(\DateTimeInterface::ATOM), + $organization1->getUpdatedAt()?->format(\DateTimeInterface::ATOM) + ); } }