Skip to content

omnibase/newsletter API reference

Floor
Prepared structure · Omnibase
Version
main
Verified for real
Its README does not say
Licence
MIT
composer require omnibase/newsletter:dev-main

Read from the package's own PHP files at the commit b9185af96c80: its namespaces, classes, interfaces and enums, their public constants, properties and methods with their PHPDoc, and the options its factories take. Nothing of it is run.

Base\Newsletter

  • NewsletterBundle class

    A newsletter with no account behind it: addresses that confirmed (Subscriber), letters written in the back office and sent through Messenger (Campaign), a monthly digest drafted from what the site has to announce (Digest\DigestSourceInterface).

    3 methods

Base\Newsletter\Admin\Widget

  • SubscribersWidgetType class

    The dashboard's tile: how many read the letters, how many have not confirmed yet, the last few to sign up, each opening the list.

    4 methods

Base\Newsletter\Command

  • DraftDigestCommand class

    The monthly digest, drafted for the coming month (a cron on the 20th, say): a DRAFT campaign in the back office, to read and send. Nothing is sent.

    1 method

Base\Newsletter\Controller\Admin\Crud

  • CampaignCrudController class

    Writing a letter: a subject, a text, maybe a button, who it goes to (one language, or everyone). Two buttons on a draft: "send me a test" - to the signed-in administrator's own address - and "send", which asks first and hands the letter to Messenger; the list sees SENDING, then SENT and how many it went to.

    8 methods

  • SubscriberCrudController class

    The list: who signed up, from where, who confirmed, who left. Read more than written - an address is added by itself confirming -, and the confirmed ones exported as a CSV for whatever the site moves to next.

    7 methods

Base\Newsletter\Controller\Client

  • NewsletterController class

    Signing up, confirming, leaving. Double opt-in: the address confirms from its mail before anything else is sent to it. The answer to a sign-up never says whether the address was already on the list - the same words for a new one, a pending one, a confirmed one -, so the form cannot be used to find out who reads the letters.

    6 methods

Base\Newsletter\DependencyInjection

Base\Newsletter\Digest

  • DigestSourceInterface interface

    Something the site announces in the monthly digest: the concerts of an agenda, the posts of a blog, the new recordings. A service implementing it is found by itself (tag newsletter.digest_source) and asked, for the coming month, what it has.

    2 methods

Base\Newsletter\Entity

  • Campaign class

    A letter: a subject, a text (plain, a blank line between two paragraphs), maybe a button to a page, and who it goes to - the subscribers of one language, or everyone. Written in the back office, tried on oneself, then sent: SENDING while the mails go out, SENT with the number they went to.

    22 methods

  • Subscriber class

    An address on the list - never an account. Double opt-in: it receives nothing before it confirms from the mail it was sent (confirmedAt). Going away keeps the row (unsubscribedAt): the history is known, nothing is sent to it any more, and coming back is a new confirmation, under a new token - the links of the old mails no longer answer.

    23 methods

Base\Newsletter\Enum

  • CampaignState enum

    Where a letter is: being written, on its way (the message dispatched, the mails going out), sent. A string enum (not omnibase's EnumType) so the column reads plainly in the database and in the admin's filters.

    3 cases

Base\Newsletter\Form

  • SubscribeType class

    The sign-up form - one address, and `source`: which page it was filled on.

    4 methods

Base\Newsletter\Message

  • SendCampaignMessage class

    "Send this letter": dispatched by the back office's send button, handled by MessageHandler\SendCampaignHandler. Routed to an async transport by the host (messenger.yaml: Base\Newsletter\Message\SendCampaignMessage: async), so the button answers at once whatever the list's length.

    1 method · 1 property

Base\Newsletter\MessageHandler

  • SendCampaignHandler class

    A letter sent: one mail per confirmed subscriber - of its language when it has one -, each remembered on the subscriber as it goes, so a send that fails half-way and is retried does not write twice to the first half. An address its transport refuses is skipped and logged, not fatal. Then the campaign is SENT, with the number of mails.

    2 methods

Base\Newsletter\Repository

Base\Newsletter\Service

  • DigestDrafter class

    The monthly digest, drafted: every source asked what it has between two dates, each a part of the letter under its name, its entries in date order - a title with its date, a line, a link. A Campaign in DRAFT comes out, to be read, touched up and sent from the back office; nothing is saved here (Command\DraftDigestCommand persists it), nothing is sent.

    3 methods

  • NewsletterMailer class

    The newsletter's two mails.

    3 methods

  • SignedTimestamp class

    The time the form was opened, signed with the kernel's secret: a robot cannot send an old timestamp of its own making to look patient.

    2 methods

  • SubscribeGuard class

    What stops someone using the form to flood a stranger's inbox with confirmation mails: a second sign-up from the same address within the flood interval, more than the limiter allows in the hour. The robots are the form's guard's (glitchr/omnibase's option `guard`); with a core from before it, the form's own trap and signed time are read here: the trap filled (thanked and dropped, so the robot learns nothing), a form sent faster than a person types or with a time it did not get from us. Each answers with a reason the controller turns into a flash.

    2 methods · 4 constants

Base\Newsletter\Twig

  • NewsletterExtension class

    What a host's own pages ask the newsletter: the sign-up form for a footer or a home page (its source: the page's key, kept with the address), and how many read the letters.

    2 methods

Base\Newsletter

NewsletterBundle class

class NewsletterBundle extends AbstractBaseBundle NewsletterBundle.php:15

A newsletter with no account behind it: addresses that confirmed (Subscriber), letters written in the back office and sent through Messenger (Campaign), a monthly digest drafted from what the site has to announce (Digest\DigestSourceInterface).

NewsletterBundle::__construct()

public function __construct()

NewsletterBundle::getPath()

public function getPath(): string

Modern layout: the class lives in src/, the bundle root is the package root.

NewsletterBundle::build()

public function build(ContainerBuilder $container): void

Base\Newsletter\Admin\Widget

SubscribersWidgetType class

final class SubscribersWidgetType implements DashboardWidgetTypeInterface SubscribersWidgetType.php:15

The dashboard's tile: how many read the letters, how many have not confirmed yet, the last few to sign up, each opening the list.

yield MenuItem::block('newsletter_subscribers', ...) in the dashboard's configureWidgetItems() places it.

SubscribersWidgetType::__construct()

public function __construct(readonly SubscriberRepository $subscribers)

SubscribersWidgetType::getName()

public static function getName(): string

SubscribersWidgetType::getTemplate()

public function getTemplate(): string

SubscribersWidgetType::getTemplateVars()

public function getTemplateVars(MenuItem $widget): array

Base\Newsletter\Command

DraftDigestCommand class

final class DraftDigestCommand extends Command DraftDigestCommand.php:22

The monthly digest, drafted for the coming month (a cron on the 20th, say): a DRAFT campaign in the back office, to read and send. Nothing is sent.

bin/console newsletter:draft-digest bin/console newsletter:draft-digest --from=2026-11-01 --to=2026-12-01 --locale=de

DraftDigestCommand::__construct()

public function __construct(readonly DigestDrafter $drafter, readonly EntityManagerInterface $entityManager, readonly bool $enabled = true, readonly string $defaultLocale = 'en')

Base\Newsletter\Controller\Admin\Crud

CampaignCrudController class

class CampaignCrudController extends AbstractCrudController CampaignCrudController.php:34

Writing a letter: a subject, a text, maybe a button, who it goes to (one language, or everyone). Two buttons on a draft: "send me a test" - to the signed-in administrator's own address - and "send", which asks first and hands the letter to Messenger; the list sees SENDING, then SENT and how many it went to.

CampaignCrudController::setNewsletterServices()

public function setNewsletterServices(NewsletterMailer $mailer, MessageBusInterface $bus, SubscriberRepository $subscribers, TranslatorInterface $translator, array $locales = []): void

CampaignCrudController::getEntityFqcn()

public static function getEntityFqcn(): string

CampaignCrudController::getPreferredIcon()

public static function getPreferredIcon(): ?string

CampaignCrudController::configureFilters()

public function configureFilters(Filters $filters): Filters

CampaignCrudController::configureFields()

public function configureFields(string $pageName): iterable

CampaignCrudController::configureActions()

public function configureActions(Actions $actions): Actions

CampaignCrudController::test()

public function test(string $entityId): Response

The letter as the subscribers will get it, to the signed-in administrator.

CampaignCrudController::send()

public function send(string $entityId): Response

SubscriberCrudController class

class SubscriberCrudController extends AbstractCrudController SubscriberCrudController.php:26

The list: who signed up, from where, who confirmed, who left. Read more than written - an address is added by itself confirming -, and the confirmed ones exported as a CSV for whatever the site moves to next.

SubscriberCrudController::setNewsletterServices()

public function setNewsletterServices(SubscriberRepository $subscribers): void

SubscriberCrudController::getEntityFqcn()

public static function getEntityFqcn(): string

SubscriberCrudController::getPreferredIcon()

public static function getPreferredIcon(): ?string

SubscriberCrudController::configureFilters()

public function configureFilters(Filters $filters): Filters

SubscriberCrudController::configureFields()

public function configureFields(string $pageName): iterable

SubscriberCrudController::configureActions()

public function configureActions(Actions $actions): Actions

SubscriberCrudController::export()

public function export(): Response

The confirmed addresses still on the list, as a CSV: e-mail, language, since, source.

Base\Newsletter\Controller\Client

NewsletterController class

class NewsletterController extends AbstractController NewsletterController.php:26

Signing up, confirming, leaving. Double opt-in: the address confirms from its mail before anything else is sent to it. The answer to a sign-up never says whether the address was already on the list - the same words for a new one, a pending one, a confirmed one -, so the form cannot be used to find out who reads the letters.

NewsletterController::__construct()

public function __construct(readonly SubscriberRepository $subscribers, readonly EntityManagerInterface $entityManager, readonly SubscribeGuard $guard, readonly NewsletterMailer $mailer, readonly bool $doubleOptIn = true)

NewsletterController::index()

public function index(): Response

A page of its own: what the letter is, how often, the form.

NewsletterController::subscribe()

public function subscribe(Request $request): Response

NewsletterController::confirm()

public function confirm(string $token): Response

NewsletterController::unsubscribe()

public function unsubscribe(Request $request, string $token): Response

GET from the mail's link: a page with one button (mail scanners follow links, they must not unsubscribe anyone). POST from that button, or from a mail client's one-click List-Unsubscribe (RFC 8058) - which sends no CSRF token: the token of the address is the secret. The row stays, marked; an unknown token is answered "done" all the same.

NewsletterController::form()

public function form(string $source = 'site'): FormInterface

The form a page embeds (the Twig function newsletter_form(), @Newsletter/client/_form.html.twig).

Base\Newsletter\DependencyInjection

NewsletterConfiguration class

class NewsletterConfiguration extends AbstractBaseConfiguration NewsletterConfiguration.php:8

NewsletterConfiguration::getConfigTreeBuilder()

public function getConfigTreeBuilder(): TreeBuilder

NewsletterExtension class

class NewsletterExtension extends AbstractBaseExtension implements PrependExtensionInterface NewsletterExtension.php:16

const LIMITER = 'newsletter_subscribe'

The limiter of the sign-up form: framework.rate_limiter.newsletter_subscribe.

NewsletterExtension::getConfiguration()

public function getConfiguration(array $config, ContainerBuilder $container): NewsletterConfiguration

NewsletterExtension::prepend()

public function prepend(ContainerBuilder $container): void

A few sign-ups an hour per visitor, so the form cannot be used to flood someone's inbox with confirmation mails. Declared first: a host's own framework.rate_limiter.newsletter_subscribe, read after, wins.

NewsletterExtension::load()

public function load(array $configs, ContainerBuilder $container): void

Base\Newsletter\Digest

DigestSourceInterface interface

interface DigestSourceInterface DigestSourceInterface.php:13

Something the site announces in the monthly digest: the concerts of an agenda, the posts of a blog, the new recordings. A service implementing it is found by itself (tag newsletter.digest_source) and asked, for the coming month, what it has.

DigestSourceInterface::getName()

public function getName(): string

The heading of its part of the letter: "Concerts", "On the blog".

DigestSourceInterface::digest()

public function digest(\DateTimeImmutable $from, \DateTimeImmutable $to, string $locale): array

What happens (or was published) between $from and $to, in $locale.

Base\Newsletter\Entity

Campaign class

class Campaign Campaign.php:16

A letter: a subject, a text (plain, a blank line between two paragraphs), maybe a button to a page, and who it goes to - the subscribers of one language, or everyone. Written in the back office, tried on oneself, then sent: SENDING while the mails go out, SENT with the number they went to.

Campaign::__construct()

public function __construct(?string $subject = null, ?string $body = null, ?string $locale = null)

Campaign::__toString()

public function __toString(): string

Campaign::getId()

public function getId(): ?int

Campaign::getSubject()

public function getSubject(): ?string

Campaign::setSubject()

public function setSubject(?string $subject): self

Campaign::getBody()

public function getBody(): ?string

Campaign::setBody()

public function setBody(?string $body): self
public function getLink(): ?string
public function setLink(?string $link): self

Campaign::getLinkLabel()

public function getLinkLabel(): ?string

Campaign::setLinkLabel()

public function setLinkLabel(?string $linkLabel): self

Campaign::getLocale()

public function getLocale(): ?string

Campaign::setLocale()

public function setLocale(?string $locale): self

Campaign::getState()

public function getState(): CampaignState

Campaign::isDraft()

public function isDraft(): bool

Campaign::isSent()

public function isSent(): bool

Campaign::getCreatedAt()

public function getCreatedAt(): \DateTimeImmutable

Campaign::getSentAt()

public function getSentAt(): ?\DateTimeImmutable

Campaign::getRecipients()

public function getRecipients(): int

Campaign::markSending()

public function markSending(): self

On its way: the message is dispatched. Only a draft goes.

Campaign::markSent()

public function markSent(int $recipients): self

Campaign::getParagraphs()

public function getParagraphs(): array
Returns

the paragraphs: what a blank line separates

Subscriber class

class Subscriber Subscriber.php:18

An address on the list - never an account. Double opt-in: it receives nothing before it confirms from the mail it was sent (confirmedAt). Going away keeps the row (unsubscribedAt): the history is known, nothing is sent to it any more, and coming back is a new confirmation, under a new token - the links of the old mails no longer answer.

The token is the key of the confirm and unsubscribe links: 32 hex characters, as hard to guess as a password and never shown elsewhere.

Subscriber::__construct()

public function __construct(string $email, string $locale = 'en', string $source = 'site', ?string $ip = null)

Subscriber::__toString()

public function __toString(): string

Subscriber::normalize()

public static function normalize(string $email): string

The way an address is kept and looked up: trimmed, lowercased.

Subscriber::getId()

public function getId(): ?int

Subscriber::getEmail()

public function getEmail(): string

Subscriber::getToken()

public function getToken(): string

Subscriber::getLocale()

public function getLocale(): string

Subscriber::setLocale()

public function setLocale(string $locale): self

Subscriber::getCreatedAt()

public function getCreatedAt(): \DateTimeImmutable

Subscriber::getConfirmedAt()

public function getConfirmedAt(): ?\DateTimeImmutable

Subscriber::getUnsubscribedAt()

public function getUnsubscribedAt(): ?\DateTimeImmutable

Subscriber::getSource()

public function getSource(): string

Subscriber::getIp()

public function getIp(): ?string

Subscriber::isConfirmed()

public function isConfirmed(): bool

It confirmed - whether or not it left since.

Subscriber::isUnsubscribed()

public function isUnsubscribed(): bool

Subscriber::isActive()

public function isActive(): bool

What the letters go to: confirmed, and still there.

Subscriber::isPending()

public function isPending(): bool

Signed up, not confirmed yet.

Subscriber::confirm()

public function confirm(): self

The link of its mail was followed. Once is enough (the first date is kept); an address that left is not brought back by an old link.

Subscriber::unsubscribe()

public function unsubscribe(): self

Subscriber::resubscribe()

public function resubscribe(bool $doubleOptIn = true): self

Back on the list after leaving. With double opt-in it confirms again, under a new token; without, it is on the list at once. An address still on the list is left as it is.

Subscriber::getLastCampaign()

public function getLastCampaign(): ?int

Subscriber::wasSent()

public function wasSent(Campaign $campaign): bool

Subscriber::sent()

public function sent(Campaign $campaign): self

Base\Newsletter\Enum

CampaignState enum

enum CampaignState CampaignState.php:10

Where a letter is: being written, on its way (the message dispatched, the mails going out), sent. A string enum (not omnibase's EnumType) so the column reads plainly in the database and in the admin's filters.

case DRAFT = 'draft'
case SENDING = 'sending'
case SENT = 'sent'

Base\Newsletter\Form

SubscribeType class

final class SubscribeType extends AbstractType SubscribeType.php:25

The sign-up form - one address, and source: which page it was filled on.

Guarded as glitchr/omnibase guards a form (its option guard, action "newsletter", newsletter.min_delay): a trap, the time it takes, the lists, the captcha when the site has glitchr/omnishield. With a glitchr/omnibase from before the guard, the form's own: a trap field (website) and the time it was opened (opened_at, signed with the kernel's secret), read by Service\SubscribeGuard - until the host takes a core that has the guard.

SubscribeType::__construct()

public function __construct(readonly string $secret, readonly int $minDelay = 3)

SubscribeType::buildForm()

public function buildForm(FormBuilderInterface $builder, array $options): void

SubscribeType::configureOptions()

public function configureOptions(OptionsResolver $resolver): void

SubscribeType::getBlockPrefix()

public function getBlockPrefix(): string

Base\Newsletter\Message

SendCampaignMessage class

final readonly class SendCampaignMessage SendCampaignMessage.php:11

"Send this letter": dispatched by the back office's send button, handled by MessageHandler\SendCampaignHandler. Routed to an async transport by the host (messenger.yaml: Base\Newsletter\Message\SendCampaignMessage: async), so the button answers at once whatever the list's length.

public int $campaignId

SendCampaignMessage::__construct()

public function __construct(public int $campaignId)

Base\Newsletter\MessageHandler

SendCampaignHandler class

final class SendCampaignHandler SendCampaignHandler.php:23

A letter sent: one mail per confirmed subscriber - of its language when it has one -, each remembered on the subscriber as it goes, so a send that fails half-way and is retried does not write twice to the first half. An address its transport refuses is skipped and logged, not fatal. Then the campaign is SENT, with the number of mails.

SendCampaignHandler::__construct()

public function __construct(readonly CampaignRepository $campaigns, readonly SubscriberRepository $subscribers, readonly NewsletterMailer $mailer, readonly EntityManagerInterface $entityManager, readonly LoggerInterface $logger = new NullLogger())

SendCampaignHandler::__invoke()

public function __invoke(SendCampaignMessage $message): void

Base\Newsletter\Repository

CampaignRepository class

class CampaignRepository extends ServiceEntityRepository CampaignRepository.php:10

CampaignRepository::__construct()

public function __construct(ManagerRegistry $registry)

SubscriberRepository class

class SubscriberRepository extends ServiceEntityRepository SubscriberRepository.php:11

SubscriberRepository::__construct()

public function __construct(ManagerRegistry $registry)

SubscriberRepository::findOneByEmail()

public function findOneByEmail(string $email): ?Subscriber

SubscriberRepository::findOneByToken()

public function findOneByToken(string $token): ?Subscriber

SubscriberRepository::findConfirmed()

public function findConfirmed(?string $locale = null): array

What the letters go to: confirmed and still there - of one language when one is given.

SubscriberRepository::countConfirmed()

public function countConfirmed(?string $locale = null): int

SubscriberRepository::countPending()

public function countPending(): int

Signed up, not confirmed yet, not gone.

SubscriberRepository::findLatest()

public function findLatest(int $limit = 5): array
Returns

the last ones signed up, whatever became of them

SubscriberRepository::findLastFromIp()

public function findLastFromIp(string $ip): ?Subscriber

The last sign-up from this address, to hold the flood interval.

Base\Newsletter\Service

DigestDrafter class

final class DigestDrafter DigestDrafter.php:19

The monthly digest, drafted: every source asked what it has between two dates, each a part of the letter under its name, its entries in date order - a title with its date, a line, a link. A Campaign in DRAFT comes out, to be read, touched up and sent from the back office; nothing is saved here (Command\DraftDigestCommand persists it), nothing is sent.

No source with anything to say: no draft.

DigestDrafter::__construct()

public function __construct(readonly iterable $sources = [], readonly ?TranslatorInterface $translator = null, readonly ?string $title = null)

DigestDrafter::nextMonth()

public static function nextMonth(?\DateTimeImmutable $today = null): array

The coming month: from its first day to the first of the next (exclusive).

@return array{0: \DateTimeImmutable, 1: \DateTimeImmutable}

DigestDrafter::draft()

public function draft(\DateTimeImmutable $from, \DateTimeImmutable $to, ?string $locale = null, string $fallbackLocale = 'en'): ?Campaign
$locale

the language of the letter and of who receives it; null: everyone, in $fallbackLocale

NewsletterMailer class

final class NewsletterMailer NewsletterMailer.php:34

The newsletter's two mails.

The confirmation goes through omnibase's notifier, as every mail of the site does (importance "email", from the site's address).

A letter does not: omnibase's Notification builds its e-mail itself and has no way to add a header, and a letter must carry List-Unsubscribe and List-Unsubscribe-Post (RFC 8058) - what Gmail and the others ask of a sender of bulk mail, and what gives the reader the "unsubscribe" button of their mail client. So it is a TemplatedEmail sent with Symfony's mailer, from the same address the notifier uses.

Both templates are overridden from templates/bundles/NewsletterBundle/email/.

NewsletterMailer::__construct()

public function __construct(readonly MailerInterface $mailer, readonly NotifierInterface $notifier, readonly UrlGeneratorInterface $urls, readonly SettingBagInterface $settings, readonly TranslatorInterface $translator, readonly ?string $fromName = null)

NewsletterMailer::confirm()

public function confirm(Subscriber $subscriber): void

"Confirm your address": the link that puts it on the list.

NewsletterMailer::campaign()

public function campaign(Subscriber $subscriber, Campaign $campaign, bool $test = false): void

A letter to one subscriber, with its way out: the link in the text, and the two headers of a one-click unsubscribe. A test is marked so in its subject.

SignedTimestamp class

final class SignedTimestamp SignedTimestamp.php:10

The time the form was opened, signed with the kernel's secret: a robot cannot send an old timestamp of its own making to look patient.

"1767225600.3f9a..." - the seconds, a dot, an HMAC of them.

SignedTimestamp::sign()

public static function sign(int $time, string $secret): string

SignedTimestamp::verify()

public static function verify(?string $value, string $secret): ?int

The time it carries, or null when it was not signed with $secret.

SubscribeGuard class

final class SubscribeGuard SubscribeGuard.php:22

What stops someone using the form to flood a stranger's inbox with confirmation mails: a second sign-up from the same address within the flood interval, more than the limiter allows in the hour. The robots are the form's guard's (glitchr/omnibase's option guard); with a core from before it, the form's own trap and signed time are read here: the trap filled (thanked and dropped, so the robot learns nothing), a form sent faster than a person types or with a time it did not get from us. Each answers with a reason the controller turns into a flash.

const TRAPPED = 'trapped'
const TOO_FAST = 'too_fast'
const FLOOD = 'flood'
const TOO_MANY = 'too_many'

SubscribeGuard::__construct()

public function __construct(readonly SubscriberRepository $subscribers, readonly string $secret, readonly int $minDelay = 3, readonly int $floodInterval = 60, readonly RateLimiterFactoryInterface|RateLimiterFactory|null $limiter = null)

SubscribeGuard::check()

public function check(FormInterface $form, Request $request): ?string

null when the address may be signed up.

Base\Newsletter\Twig

NewsletterExtension class

final class NewsletterExtension extends AbstractExtension NewsletterExtension.php:16

What a host's own pages ask the newsletter: the sign-up form for a footer or a home page (its source: the page's key, kept with the address), and how many read the letters.

NewsletterExtension::__construct()

public function __construct(readonly NewsletterController $controller, readonly SubscriberRepository $subscribers)

NewsletterExtension::getFunctions()

public function getFunctions(): array