Pass a copy of the Carbon object to avoid unexpected side effects

This commit changes TimestampListener to pass a copy of the Carbon
object representing the current time to the entity properties updatedAt
and createdAt. Passing a copy instead of the object itself is needed to
avoid unexpected side effects. Before this change, modifying the time
of the createdAt or updatedAt field of a given entity could affect the
same field of another entity as it could be the same object used inside
TimestampListener for all entities.

[MAILPOET-3870]
This commit is contained in:
Rodrigo Primo
2021-11-09 11:19:19 -03:00
committed by Veljko V
parent 12e326596d
commit 2e050d7a32
2 changed files with 23 additions and 3 deletions

View File

@@ -27,11 +27,11 @@ class TimestampListener {
&& method_exists($entity, 'getCreatedAt')
&& !$entity->getCreatedAt()
) {
$entity->setCreatedAt($this->now);
$entity->setCreatedAt($this->now->copy());
}
if (in_array(UpdatedAtTrait::class, $entityTraits, true) && method_exists($entity, 'setUpdatedAt')) {
$entity->setUpdatedAt($this->now);
$entity->setUpdatedAt($this->now->copy());
}
}
@@ -40,7 +40,7 @@ class TimestampListener {
$entityTraits = $this->getEntityTraits($entity);
if (in_array(UpdatedAtTrait::class, $entityTraits, true) && method_exists($entity, 'setUpdatedAt')) {
$entity->setUpdatedAt($this->now);
$entity->setUpdatedAt($this->now->copy());
}
}

View File

@@ -72,6 +72,26 @@ class TimestampListenerTest extends \MailPoetTest {
expect($entity->getUpdatedAt())->equals($this->now);
}
public function testItUsesDifferentTimesWhenCreatingDifferentEntities() {
$entity1 = new TimestampEntity();
$entity1->setName('Entity 1');
$this->entityManager->persist($entity1);
$this->entityManager->flush();
$createdAt1 = $entity1->getCreatedAt();
$this->assertInstanceOf(Carbon::class, $createdAt1);
$createdAt1->subMonth();
$entity2 = new TimestampEntity();
$entity2->setName('Entity 2');
$this->entityManager->persist($entity2);
$this->entityManager->flush();
$this->assertEquals($this->now, $entity2->getCreatedAt());
}
public function _after() {
parent::_after();
$this->connection->executeStatement("DROP TABLE IF EXISTS $this->tableName");