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

Symfony Notifier Demystified

069a861584701a8b5f72a838bd7e0f2e?s=47 Jan Schädlich
August 19, 2021
34

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.

069a861584701a8b5f72a838bd7e0f2e?s=128

Jan Schädlich

August 19, 2021
Tweet

Transcript

  1. Symfony Noti fi er Demysti fi ed

  2. Siemens Mobility Portugal schaedlich.jan@gmail.com @jschaedl Jan Schädlich

  3. @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
  4. @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
  5. @jschaedl 5 Noti fi er ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel

    ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  6. @jschaedl 6 use Symfony\Component\Notifier\Notification\Notification; $notification = (new Notification()) ->subject('A nice

    subject') ->content('An even nicer content.') ;
  7. @jschaedl 7 use Symfony\Component\Notifier\Recipient\Recipient; $recipient = new Recipient( 'schaedlich.jan@gmail.com', '+351

    0815' );
  8. @jschaedl 8 $notifier->send($notification, $recipient);

  9. @jschaedl 9

  10. @jschaedl 10 Channels ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage

    EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  11. @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
  12. @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']) ;
  13. @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']; } }
  14. @jschaedl 14 use Symfony\Component\Notifier\Notification\Notification; $notification = (new Notification()) ->subject('A nice

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

    SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  16. @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'] # ...
  17. @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
  18. @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
  19. @jschaedl 19 interface ChannelInterface { public function notify( Notification $notification,

    RecipientInterface $recipient, string $transportName = null ): void; public function supports( Notification $notification, RecipientInterface $recipient ): bool; }
  20. @jschaedl 20 NoRecipient EmailRecipientInterface SmsRecipientInterface Recipient RecipientInterface

  21. @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
  22. @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; } }
  23. @jschaedl 23 use Symfony\Component\Notifier\Recipient\NoRecipient; $noRecipient = new NoRecipient(); use Symfony\Component\Notifier\Recipient\Recipient;

    $recipient = new Recipient( 'schaedlich.jan@gmail.com', '+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('schaedlich.jan@gmail.com');
  24. @jschaedl 24 final class Notifier implements NotifierInterface { // ...

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

    EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  26. @jschaedl 26 Channel Notification ChatMessage SmsMessage EmailMessage MessageInterface Transport

  27. @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 // ... }
  28. @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); } // ... } }
  29. @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.') } }
  30. @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; }
  31. @jschaedl 31 Sending the messages Transports ChannelPolicy ChatChannel SmsChannel EmailChannel

    BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  32. @jschaedl 32

  33. @jschaedl 33 Chatter Transports Texter Transports $ composer req symfony/slack-notifier

  34. @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
  35. @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; }
  36. @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); } // ... }
  37. @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; } // ... }
  38. @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); } }
  39. @jschaedl 39 namespace Symfony\Component\Notifier\Transport; interface TransportFactoryInterface { public function create(Dsn

    $dsn): TransportInterface; public function supports(Dsn $dsn): bool; }
  40. @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); } }
  41. @jschaedl 41 # config/services.yaml App\Notifier\CustomTransportFactory: tags: ['chatter.transport_factory']

  42. @jschaedl # .env CUSTOM_DSN=custom://default?from=Jan # config/packages/notifier.yaml framework: notifier: chatter_transports: custom:

    '%env(CUSTOM_DSN)%' 42
  43. @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
  44. @jschaedl 44

  45. @jschaedl Routing Step Sending Step 45 ChannelPolicy ChatChannel SmsChannel EmailChannel

    BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports Recap
  46. Thank you! https://speakerdeck.com/jschaedl