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

Symfony Notifier Demystified

Symfony Notifier Demystified

SymfonyLive Online German Edition 2021

https://live.symfony.com/2021-germany/schedule#session-567

Mit Symfony 5.3 ist die Notifier Komponente nun nicht mehr als experimentell markiert. Ein guter Zeitpunkt um einmal genauer zu schauen wie der Symfony Notifier funktioniert, welche Integrationen es bereits gibt und was man damit alles machen kann.

Jan Schädlich

April 16, 2021
Tweet

More Decks by Jan Schädlich

Other Decks in Programming

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 chat sms email browser
  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 The Channel has to support the

    Recipient ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports
  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 LogicException(...); } 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()); } if (!$from = $dsn->getOption('from')) { throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn()); } return new CustomTransport($from, $this->client, $this->dispatcher); } }
  30. @jschaedl # .env CUSTOM_DSN=custom://default?from=Jan # config/packages/notifier.yaml framework: notifier: chatter_transports: custom:

    '%env(CUSTOM_DSN)%' 42 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); // ... } }
  31. @jschaedl Routing Step Sending Step 44 ChannelPolicy ChatChannel SmsChannel EmailChannel

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