Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Symfony Notifier Demystified

Jan Schädlich
August 19, 2021
150

Symfony Notifier Demystified

SymfonyWorld Online Summer Edition 2021

The Notifier Component is marked stable with the Symfony 5.3 release. A good time to have a closer look at how the Symfony Notifier works and what you can do with it.

Jan Schädlich

August 19, 2021
Tweet

Transcript

  1. @jschaedl 3 use Symfony\Component\Notifier\NotifierInterface; final class NotificationController { #[Route('/notify', name:

    'notify', methods: ['GET'])] public function __invoke(NotifierInterface $notifier): Response { $notification = ??? $recipient = ??? $notifier->send( $notification, $recipient ); // ... } } The Recipient and how we can use it. Deep dive into how the Notifier works and how we can customize the process of sending a Notification. The Notification and how we can use it. Agenda
  2. @jschaedl Routing Step Sending Step 4 ChannelPolicy ChatChannel SmsChannel EmailChannel

    BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports Noti fi cation Flow
  3. @jschaedl 5 Noti fi er ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel

    ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  4. @jschaedl 10 Channels ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage

    EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  5. @jschaedl 11 framework: notifier: # ... chatter_transports: # use chat

    slack: '%env(SLACK_DSN)%' # use chat/slack telegram: '%env(TELEGRAM_DSN)%' # use chat/telegram texter_transports: # use sms twilio: '%env(TWILIO_DSN)%' # use sms/twilio nexmo: '%env(NEXMO_DSN)%' # use sms/nexmo # ... SLACK_DSN=slack://TOKEN@default?channel=CHANNEL TELEGRAM_DSN=telegram://TOKEN@default?channel=CHAT_ID TWILIO_DSN=twilio://SID:TOKEN@default?from=FROM NEXMO_DSN=nexmo://KEY:SECRET@default?from=FROM
  6. @jschaedl 12 use Symfony\Component\Notifier\Notification\Notification; $notification = (new Notification()) ->subject('A nice

    subject') ->content('An even nicer content.') # Channels: chat, sms, email, browser # Tranports: chat/slack, chat/telegram, sms/twilio, sms/nexmo # {channel}/{transport} ->channels(['chat']) ;
  7. @jschaedl 13 class InvoiceNotification extends Notification { private $price; public

    function __construct(int $price) { $this->price = $price; } public function getChannels(RecipientInterface $recipient) { if ($this->price > 10000 && $recipient instanceof SmsRecipientInterface) { return ['sms']; } return ['slack']; } }
  8. @jschaedl 14 use Symfony\Component\Notifier\Notification\Notification; $notification = (new Notification()) ->subject('A nice

    subject') ->content('An even nicer content.') ->importance(Notification::IMPORTANCE_URGENT) ;
  9. @jschaedl 15 Routing to Channels by Importance ChannelPolicy ChannelPolicy ChatChannel

    SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  10. @jschaedl 16 framework: notifier: # ... channel_policy: # use chat,

    sms, email, browser # or chat/slack, chat/telegram, sms/twilio, sms/nexmo urgent: ['email', 'chat', 'sms'] high: ['email', 'chat'] medium: ['email'] low: ['email'] # ...
  11. @jschaedl 17 The Channel has to support the Recipient Channels

    and Recipients ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  12. @jschaedl 18 AbstractChannel ChannelInterface ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage

    SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports The Channel has to support the Recipient
  13. @jschaedl 19 interface ChannelInterface { public function notify( Notification $notification,

    RecipientInterface $recipient, string $transportName = null ): void; public function supports( Notification $notification, RecipientInterface $recipient ): bool; }
  14. @jschaedl interface RecipientInterface { } interface EmailRecipientInterface extends RecipientInterface {

    public function getEmail(): string; } interface SmsRecipientInterface extends RecipientInterface { public function getPhone(): string; } 21 NoRecipient EmailRecipientInterface SmsRecipientInterface Recipient RecipientInterface
  15. @jschaedl 22 class ChatChannel implements ChannelInterface { // ... public

    function supports(Notification $notification, RecipientInterface $recipient): bool { return true; } } class SmsChannel extends AbstractChannel { // ... public function supports(Notification $notification, RecipientInterface $recipient): bool { return $recipient instanceof SmsRecipientInterface; } } class EmailChannel implements ChannelInterface { // ... public function supports(Notification $notification, RecipientInterface $recipient): bool { return $recipient instanceof EmailRecipientInterface; } } class BrowserChannel implements ChannelInterface { // ... public function supports(Notification $notification, RecipientInterface $recipient): bool { return true; } }
  16. @jschaedl 23 use Symfony\Component\Notifier\Recipient\NoRecipient; $noRecipient = new NoRecipient(); use Symfony\Component\Notifier\Recipient\Recipient;

    $recipient = new Recipient( '[email protected]', '+351 0815’ ); final class SmsRecipient implements SmsRecipientInterface { use SmsRecipientTrait; public function __construct(string $phone) { $this->phone = $phone; } } $smsRecipient = new SmsRecipient('+351 0815'); final class EmailRecipient implements EmailRecipientInterface { use EmailRecipientTrait; public function __construct(string $email) { $this->email = $email; } } $emailRecipient = new EmailRecipient('[email protected]');
  17. @jschaedl 24 final class Notifier implements NotifierInterface { // ...

    public function send(Notification $notification, RecipientInterface ...$recipients): void { if (!$recipients) { $recipients = [new NoRecipient()]; } // ... } // ... }
  18. @jschaedl 25 Noti fi cations to Messages ChannelPolicy ChatChannel SmsChannel

    EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  19. @jschaedl namespace Symfony\Component\Notifier\Message; final class ChatMessage implements MessageInterface { private

    $subject; private $options; // ... } 27 namespace Symfony\Component\Notifier\Message; final class SmsMessage implements MessageInterface { private $subject; private $phone; // ... } namespace Symfony\Component\Notifier\Message; final class EmailMessage implements MessageInterface { private $message; // Symfony\Component\Mime\Email // ... }
  20. @jschaedl 28 namespace Symfony\Component\Notifier\Channel; class ChatChannel extends AbstractChannel { public

    function notify( Notification $notification, RecipientInterface $recipient, string $transportName = null ): void { $message = null; if ($notification instanceof ChatNotificationInterface) { $message = $notification->asChatMessage($recipient, $transportName); } if (null === $message) { $message = ChatMessage::fromNotification($notification); } // ... } }
  21. @jschaedl 29 class InvoiceNotification extends Notification implements ChatNotificationInterface { private

    $price; public function __construct(int $price) { $this->price = $price; } public function asChatMessage( RecipientInterface $recipient, string $transport = null ): ?ChatMessage { return new ChatMessage('New Invoice for you: ' . $this->price . ' EUR.') } }
  22. @jschaedl 30 interface ChatNotificationInterface { public function asChatMessage( RecipientInterface $recipient,

    string $transport = null): ?ChatMessage; } interface SmsNotificationInterface { public function asSmsMessage( SmsRecipientInterface $recipient, string $transport = null): ?SmsMessage; } interface EmailNotificationInterface { public function asEmailMessage( EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage; }
  23. @jschaedl 31 Sending the messages Transports ChannelPolicy ChatChannel SmsChannel EmailChannel

    BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  24. @jschaedl 34 Create a custom Transport Custom Transport ChannelPolicy ChatChannel

    SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports AbstractTransport TransportInterface
  25. @jschaedl 35 namespace Symfony\Component\Notifier\Transport; interface TransportInterface { public function send(MessageInterface

    $message): ?SentMessage; public function supports(MessageInterface $message): bool; public function __toString(): string; }
  26. @jschaedl 36 final class CustomTransport extends AbstractTransport { private string

    $from; public function __construct( string $from, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null ) { $this->from = $from; parent::__construct($client, $dispatcher); } // ... }
  27. @jschaedl 37 final class CustomTransport extends AbstractTransport { // ...

    public function __toString(): string { return sprintf('custom://default?from=%s', $this->from); } public function supports(MessageInterface $message): bool { return $message instanceof ChatMessage; } // ... }
  28. @jschaedl 38 final class CustomTransport extends AbstractTransport { // ...

    protected function doSend(MessageInterface $message): SentMessage { if (!$message instanceof ChatMessage) { throw new UnsupportedMessageTypeException(...); } dump(sprintf(‘Deliver message "%s" from "%s"', $message->getSubject(), $this->from )); return new SentMessage($message, (string) $this); } }
  29. @jschaedl 40 final class CustomTransportFactory extends AbstractTransportFactory { protected function

    getSupportedSchemes(): array { return ['custom']; } public function create(Dsn $dsn): TransportInterface { if ('custom' !== $dsn->getScheme()) { throw new UnsupportedSchemeException($dsn, 'custom', $this->getSupportedSchemes()); } $from = $dsn->getRequiredOption('from'); return new CustomTransport($from, $this->client, $this->dispatcher); } }
  30. @jschaedl final class NotificationController { #[Route('/notify', name: 'notify', methods: ['GET'])]

    public function __invoke(NotifierInterface $notifier): Response { // ... $notification = new Notification('Thank you!'); $notification->channels(['chat/custom']); $notifier->send($notification); // ... } } 43
  31. @jschaedl Routing Step Sending Step 45 ChannelPolicy ChatChannel SmsChannel EmailChannel

    BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports Recap