''])] private string $destineeEmail = ''; #[ORM\Column(type: 'uuid', unique: true, nullable: false)] private UuidInterface $uuid; #[ORM\Column(type: Types::STRING, length: 255, nullable: false)] private string $privateToken; #[ORM\Column(type: Types::INTEGER, nullable: false, options: ['default' => 0])] private int $numberOfErrorTrials = 0; /** * @var Collection */ #[ORM\OneToMany(mappedBy: 'send', targetEntity: EntityWorkflowSendView::class, cascade: ['remove'])] private Collection $views; public function __construct( #[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class, inversedBy: 'sends')] #[ORM\JoinColumn(nullable: false)] private EntityWorkflowStep $entityWorkflowStep, string|ThirdParty $destinee, #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: false)] private \DateTimeImmutable $expireAt, ) { $this->uuid = Uuid::uuid4(); $random = new Randomizer(); $this->privateToken = bin2hex($random->getBytes(48)); $this->entityWorkflowStep->addSend($this); if ($destinee instanceof ThirdParty) { $this->destineeThirdParty = $destinee; } else { $this->destineeEmail = $destinee; } $this->views = new ArrayCollection(); } /** * @internal use the @see{EntityWorkflowSendView}'s constructor instead */ public function addView(EntityWorkflowSendView $view): self { if (!$this->views->contains($view)) { $this->views->add($view); } return $this; } public function getDestineeEmail(): string { return $this->destineeEmail; } public function getDestineeThirdParty(): ?ThirdParty { return $this->destineeThirdParty; } public function getId(): ?int { return $this->id; } public function getNumberOfErrorTrials(): int { return $this->numberOfErrorTrials; } public function getPrivateToken(): string { return $this->privateToken; } public function getEntityWorkflowStep(): EntityWorkflowStep { return $this->entityWorkflowStep; } public function getEntityWorkflowStepChained(): ?EntityWorkflowStep { foreach ($this->getEntityWorkflowStep()->getEntityWorkflow()->getStepsChained() as $step) { if ($this->getEntityWorkflowStep() === $step) { return $step; } } return null; } public function getUuid(): UuidInterface { return $this->uuid; } public function getExpireAt(): \DateTimeImmutable { return $this->expireAt; } public function getViews(): Collection { return $this->views; } public function increaseErrorTrials(): void { ++$this->numberOfErrorTrials; } public function getDestinee(): string|ThirdParty { if (null !== $this->getDestineeThirdParty()) { return $this->getDestineeThirdParty(); } return $this->getDestineeEmail(); } /** * Determines the kind of destinee based on whether the destinee is a thirdParty or an emailAddress. * * @return 'thirdParty'|'email' 'thirdParty' if the destinee is a third party, 'email' otherwise */ public function getDestineeKind(): string { if (null !== $this->getDestineeThirdParty()) { return 'thirdParty'; } return 'email'; } public function isViewed(): bool { return $this->views->count() > 0; } public function isExpired(?\DateTimeImmutable $now = null): bool { return ($now ?? new \DateTimeImmutable('now')) >= $this->expireAt; } /** * Retrieves the most recent view. * * @return EntityWorkflowSendView|null returns the last view or null if there are no views */ public function getLastView(): ?EntityWorkflowSendView { $last = null; foreach ($this->views as $view) { if (null === $last) { $last = $view; } else { if ($view->getViewAt() > $last->getViewAt()) { $last = $view; } } } return $last; } /** * Retrieves an array of views grouped by their remote IP address. * * @return array> an associative array where the keys are IP addresses and the values are arrays of views associated with those IPs */ public function getViewsByIp(): array { $views = []; foreach ($this->getViews() as $view) { $views[$view->getRemoteIp()][] = $view; } return $views; } }