makeUser(7); $subscriber = new TimestampableBlamableSubscriber($this->providerReturning($user)); $entity = $this->makeEntity(); $subscriber->applyOnCreate($entity); self::assertInstanceOf(DateTimeImmutable::class, $entity->getCreatedAt()); self::assertInstanceOf(DateTimeImmutable::class, $entity->getUpdatedAt()); self::assertSame($user, $entity->getCreatedBy()); self::assertSame($user, $entity->getUpdatedBy()); } public function testApplyOnUpdateLeavesCreatedUntouched(): void { $creator = $this->makeUser(1); $editor = $this->makeUser(2); $entity = $this->makeEntity(); new TimestampableBlamableSubscriber($this->providerReturning($creator))->applyOnCreate($entity); $createdAt = $entity->getCreatedAt(); new TimestampableBlamableSubscriber($this->providerReturning($editor))->applyOnUpdate($entity); self::assertSame($createdAt, $entity->getCreatedAt()); self::assertSame($creator, $entity->getCreatedBy()); self::assertSame($editor, $entity->getUpdatedBy()); } public function testApplyOnCreateIgnoresNonTimestampableEntities(): void { $subscriber = new TimestampableBlamableSubscriber($this->providerReturning(null)); // Must not throw. $subscriber->applyOnCreate(new stdClass()); $this->addToAssertionCount(1); } private function providerReturning(?UserInterface $user): CurrentUserProviderInterface { return new class($user) implements CurrentUserProviderInterface { public function __construct(private ?UserInterface $user) {} public function getCurrentUser(): ?UserInterface { return $this->user; } }; } private function makeUser(int $id): UserInterface { return new class($id) implements UserInterface { public function __construct(private int $id) {} public function getId(): ?int { return $this->id; } public function getUserIdentifier(): string { return 'user-'.$this->id; } public function getUsername(): ?string { return 'user-'.$this->id; } /** @return list */ public function getRoles(): array { return ['ROLE_USER']; } public function getFirstName(): ?string { return null; } public function getLastName(): ?string { return null; } public function getAvatarUrl(): ?string { return null; } public function getIsEmployee(): bool { return false; } }; } private function makeEntity(): object { return new class implements TimestampableInterface, BlamableInterface { use TimestampableBlamableTrait; }; } }