em->getRepository(User::class)->findOneBy(['email' => $recipientEmail]); if ($recipient === null) { throw new \InvalidArgumentException('Recipient not found. They must have an account to receive shared photos.'); } if ($recipient->getId() === $sharer->getId()) { throw new \InvalidArgumentException('You cannot share a photo with yourself.'); } // Idempotent: return existing pending share if one already exists $existing = $this->em->getRepository(SharedImage::class)->findOneBy([ 'sourceImage' => $image, 'recipientUser' => $recipient, ]); if ($existing) { return $existing; } $shared = new SharedImage($image, $recipient, $sharer); $this->em->persist($shared); $approveToken = $this->tokenService->issue(TokenType::ShareApprove, $image, $recipient, $recipientEmail, $this->tokenTtlDays); $declineToken = $this->tokenService->issue(TokenType::ShareDecline, $image, $recipient, $recipientEmail, $this->tokenTtlDays); $this->em->flush(); $email = (new TemplatedEmail()) ->from($this->mailerSender) ->to($recipientEmail) ->subject($sharer->getEmail() . ' shared a photo with you') ->htmlTemplate('emails/share_notification.html.twig') ->textTemplate('emails/share_notification.txt.twig') ->context([ 'sharer' => $sharer, 'image' => $image, 'approveToken' => $approveToken, 'declineToken' => $declineToken, ]); $this->mailer->send($email); return $shared; } }