Newsletter Management: Extending
Extend the newsletter plugin for Sylius - PRE_SUBSCRIBE event for spam protection, form extension via FormTypeExtension, template overrides and notes on replacing services
Newsletter Management: Extending
This documentation always describes the latest published version.
The plugin offers two documented extension points for third parties: the PRE_SUBSCRIBE event, with which a listener can reject signups, and the signup form NewsletterSignupType, to which fields can be added through a FormTypeExtension. The ALTCHA plugin uses both for spam protection. Template overrides cover the look.
The PRE_SUBSCRIBE event
| Property | Value |
|---|---|
| Constant | Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionEvents::PRE_SUBSCRIBE |
| Event name | mmd_sylius_newsletter_management_plugin.newsletter_subscription.pre_subscribe |
| Payload | Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionPreSubscribeEvent |
| When | Before anything is stored or sent, right after the check that the newsletter is enabled for the channel |
The NewsletterSubscriptionManager dispatches the event for every signup that goes through it: the signup page (/newsletter), signing up through the Shop API and the newsletter checkbox on registration and profile. The footer teaser form does not dispatch it itself, it only forwards to the signup page. Unsubscribing, confirming and changing preferences do not dispatch the event.
The payload offers:
| Method | Description |
|---|---|
getEmail() |
The address, already lowercased and trimmed |
getChannel() |
The channel of the signup |
getRequest() |
The current request, for headers, body fields or the client address |
getSource() |
The entry point as a NewsletterSubscriptionSource: Storefront, Api, Registration or Admin |
reject(?string $reason = null) |
Rejects the signup. $reason is a translation key shown to the customer, or null for the default text |
isRejected(), getRejectionReason() |
Query the state, for example from further listeners |
A rejection shows an error message (flash) in the storefront and answers the Shop API request with 422.
Example: a listener that rejects disposable domains
<?php
declare(strict_types=1);
namespace App\Newsletter;
use Mmd\SyliusNewsletterManagementPlugin\Entity\NewsletterSubscriptionSource;
use Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionEvents;
use Mmd\SyliusNewsletterManagementPlugin\Event\NewsletterSubscriptionPreSubscribeEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: NewsletterSubscriptionEvents::PRE_SUBSCRIBE)]
final class BlockDisposableDomains
{
private const BLOCKED = ['mailinator.com', 'trashmail.com'];
public function __invoke(NewsletterSubscriptionPreSubscribeEvent $event): void
{
// Registration has its own protection.
if ($event->getSource() === NewsletterSubscriptionSource::Registration) {
return;
}
$domain = substr((string) strrchr($event->getEmail(), '@'), 1);
if (\in_array($domain, self::BLOCKED, true)) {
$event->reject('app.newsletter.disposable_domain');
}
}
}
With the #[AsEventListener] attribute and autoconfigure enabled, the class in your project is enough. Alternatively, register the listener with a tag:
services:
App\Newsletter\BlockDisposableDomains:
tags:
- { name: kernel.event_listener, event: 'mmd_sylius_newsletter_management_plugin.newsletter_subscription.pre_subscribe' }
You add the translation key from the example to your own translation file, for example translations/flashes.en.yaml. In the storefront the key is translated as a flash message. If you pass null, the plugin's default text appears.
Sylius core's newsletter checkbox (registration and profile) dispatches the event with the source
Registration. Sylius' own Shop API registration (POST /shop/customers) sets the core flag without an event and without the plugin creating a signup. Headless clients sign up throughPOST /shop/newsletter-subscriptions.
Extending the form
Mmd\SyliusNewsletterManagementPlugin\Form\Type\NewsletterSignupType is deliberately not final. That lets you attach fields to the signup form with a Symfony FormTypeExtension. This is how the ALTCHA plugin adds its proof-of-work widget, while the server-side check listens on PRE_SUBSCRIBE.
<?php
declare(strict_types=1);
namespace App\Form\Extension;
use Mmd\SyliusNewsletterManagementPlugin\Form\Type\NewsletterSignupType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
final class NewsletterSignupExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return [NewsletterSignupType::class];
}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('companyWebsite', TextType::class, ['mapped' => false, 'required' => false]);
}
}
An unmapped field does not end up in the NewsletterSignup data model; you read it from $event->getRequest() in your PRE_SUBSCRIBE listener.
After the consent checkbox and before the submit button, the signup page renders the Twig hook mmd_altcha.widget.newsletter. It belongs to the ALTCHA plugin and stays empty while that plugin is missing or the protection target is not enabled. How to enable the target is described under ALTCHA: Configuration.
Overriding templates
You customize the look of the signup page, confirmation, preferences, unsubscribe page, footer teaser and mails with the regular Sylius mechanism: place a template with the same path under templates/bundles/MmdSyliusNewsletterManagementPlugin/ in your project or theme. Which templates and hooks exist is listed under Storefront integration. The admin templates are interface internals of the plugin and not meant to be overridden.
Which placeholders newsletter authors may use in the editor is described on the Newsletters page.
Replacing services
The plugin's services are wired through interfaces (for example NewsletterSubscriptionManagerInterface, NewsletterMailerInterface, NewsletterMailSenderInterface). You can replace or extend them with regular Symfony service decoration.
Technically that is possible, but it is not promised as a stable public API. The documented extension points are
PRE_SUBSCRIBEandNewsletterSignupType. Interface methods can change, so re-check a decoration with every update.