feat: replace *Service suffix with role-based class names

This commit is contained in:
Björn Fromme
2026-04-16 13:34:54 +02:00
parent 0d2cc5b998
commit d1c92f2957
88 changed files with 435 additions and 435 deletions
+152
View File
@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Email\Mailer;
use App\Exception\NewsletterProviderException;
use App\Entity\NewsletterOptInConfirmation;
use App\Model\NewsletterConfirmationResult;
use App\Repository\NewsletterOptInConfirmationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
class NewsletterManager
{
public function __construct(
private readonly NewsletterOptInConfirmationRepository $confirmationRepository,
private readonly EntityManagerInterface $entityManager,
private readonly MailjetApiClient $newsletterService,
private readonly Mailer $mailer,
private readonly LoggerInterface $logger,
private readonly int $newsletterConfirmationTtlHours,
) {
}
public function requestConfirmation(string $email): void
{
$normalizedEmail = $this->normalizeEmail($email);
if (false === filter_var($normalizedEmail, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException('Invalid email for newsletter confirmation request.');
}
$token = $this->generateToken();
$tokenHash = $this->hashToken($token);
$expiresAt = new \DateTimeImmutable(sprintf('+%d hours', $this->newsletterConfirmationTtlHours));
$this->confirmationRepository->deleteExpiredPendingByEmail($normalizedEmail);
$pendingConfirmation = $this->confirmationRepository->findPendingByEmail($normalizedEmail);
$wasExisting = null !== $pendingConfirmation;
$previousTokenHash = null;
$previousExpiresAt = null;
if (null !== $pendingConfirmation) {
$previousTokenHash = $pendingConfirmation->getTokenHash();
$previousExpiresAt = $pendingConfirmation->getExpiresAt();
$pendingConfirmation->refreshRequest($tokenHash, $expiresAt);
} else {
$pendingConfirmation = new NewsletterOptInConfirmation(
email: $normalizedEmail,
tokenHash: $tokenHash,
expiresAt: $expiresAt,
);
$this->entityManager->persist($pendingConfirmation);
}
$this->entityManager->flush();
try {
$context = [
'token' => $token,
];
$options = [
'to' => $normalizedEmail,
'subject' => 'Deine Anmeldung zum E&P Newsletter',
'template' => 'email/newsletter_opt_in.html.twig',
];
$this->mailer->createAndSendEmail($context, $options);
} catch (\Throwable $exception) {
if (true === $wasExisting && null !== $previousTokenHash && null !== $previousExpiresAt) {
$pendingConfirmation->refreshRequest($previousTokenHash, $previousExpiresAt);
} else {
$this->entityManager->remove($pendingConfirmation);
}
$this->entityManager->flush();
throw new NewsletterProviderException('Could not send newsletter confirmation email.', previous: $exception);
}
$this->logger->info('Newsletter confirmation requested', [
'email' => $normalizedEmail,
'expires_at' => $expiresAt->format(DATE_ATOM),
]);
}
public function confirmToken(string $token): NewsletterConfirmationResult
{
$normalizedToken = trim($token);
if ('' === $normalizedToken) {
return new NewsletterConfirmationResult(
NewsletterConfirmationResult::STATUS_INVALID,
);
}
$tokenHash = $this->hashToken($normalizedToken);
$confirmation = $this->confirmationRepository->findByTokenHash($tokenHash);
if (null === $confirmation) {
return new NewsletterConfirmationResult(
NewsletterConfirmationResult::STATUS_INVALID,
);
}
if ($confirmation->isConfirmed()) {
return new NewsletterConfirmationResult(
NewsletterConfirmationResult::STATUS_ALREADY_USED,
$confirmation->getEmail(),
);
}
if ($confirmation->isExpired()) {
$this->entityManager->remove($confirmation);
$this->entityManager->flush();
return new NewsletterConfirmationResult(
NewsletterConfirmationResult::STATUS_EXPIRED,
$confirmation->getEmail(),
);
}
$this->newsletterService->ensureSubscribed($confirmation->getEmail());
$confirmation->markConfirmed();
$this->entityManager->flush();
$this->logger->info('Newsletter double opt-in confirmed', [
'email' => $confirmation->getEmail(),
]);
return new NewsletterConfirmationResult(
NewsletterConfirmationResult::STATUS_CONFIRMED,
$confirmation->getEmail(),
);
}
private function generateToken(): string
{
return rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
}
private function hashToken(string $token): string
{
return hash('sha256', $token);
}
private function normalizeEmail(string $email): string
{
return mb_strtolower(trim($email));
}
}