Skip to content

Commit

Permalink
Merge pull request #6 from andanteproject/prevent-unaxpected-updated-…
Browse files Browse the repository at this point in the history
…at-override

Do not override your `createdAt` and `updatedAt` values when you set them explicitly
  • Loading branch information
cristoforocervino committed Oct 16, 2023
2 parents 7746860 + e2d7400 commit 2fc05f6
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 5 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 11 additions & 4 deletions src/EventSubscriber/TimestampableEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -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\Event\PreUpdateEventArgs;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Psr\Clock\ClockInterface;

class TimestampableEventSubscriber implements EventSubscriber
Expand All @@ -36,19 +37,25 @@ 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()) {
$entity->setCreatedAt($this->clock->now());
}
}

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

Expand Down
20 changes: 20 additions & 0 deletions tests/Functional/TimestampableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}
}

0 comments on commit 2fc05f6

Please sign in to comment.