Slide 1

Slide 1 text

Symfony Noti fi er Demysti fi ed

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

@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

Slide 4

Slide 4 text

@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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

@jschaedl 6 use Symfony\Component\Notifier\Notification\Notification; $notification = (new Notification()) ->subject('A nice subject') ->content('An even nicer content.') ;

Slide 7

Slide 7 text

@jschaedl 7 use Symfony\Component\Notifier\Recipient\Recipient; $recipient = new Recipient( 'schaedlich.jan@gmail.com', '+351 0815' );

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

@jschaedl 9

Slide 10

Slide 10 text

@jschaedl 10 Channels ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports chat sms email browser

Slide 11

Slide 11 text

@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

Slide 12

Slide 12 text

@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']) ;

Slide 13

Slide 13 text

@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']; } }

Slide 14

Slide 14 text

@jschaedl 14 use Symfony\Component\Notifier\Notification\Notification; $notification = (new Notification()) ->subject('A nice subject') ->content('An even nicer content.') ->importance(Notification::IMPORTANCE_URGENT) ;

Slide 15

Slide 15 text

@jschaedl 15 Routing to Channels by Importance ChannelPolicy ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports

Slide 16

Slide 16 text

@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'] # ...

Slide 17

Slide 17 text

@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

Slide 18

Slide 18 text

@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

Slide 19

Slide 19 text

@jschaedl 19 interface ChannelInterface { public function notify( Notification $notification, RecipientInterface $recipient, string $transportName = null ): void; public function supports( Notification $notification, RecipientInterface $recipient ): bool; }

Slide 20

Slide 20 text

@jschaedl 20 NoRecipient EmailRecipientInterface SmsRecipientInterface Recipient RecipientInterface

Slide 21

Slide 21 text

@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

Slide 22

Slide 22 text

@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; } }

Slide 23

Slide 23 text

@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');

Slide 24

Slide 24 text

@jschaedl 24 final class Notifier implements NotifierInterface { // ... public function send(Notification $notification, RecipientInterface ...$recipients): void { if (!$recipients) { $recipients = [new NoRecipient()]; } // ... } // ... }

Slide 25

Slide 25 text

@jschaedl 25 Noti fi cations to Messages ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports

Slide 26

Slide 26 text

@jschaedl 26 Channel Notification ChatMessage SmsMessage EmailMessage MessageInterface Transport

Slide 27

Slide 27 text

@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 // ... }

Slide 28

Slide 28 text

@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); } // ... } }

Slide 29

Slide 29 text

@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.') } }

Slide 30

Slide 30 text

@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; }

Slide 31

Slide 31 text

@jschaedl 31 Sending the messages Transports ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports

Slide 32

Slide 32 text

@jschaedl 32

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

@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

Slide 35

Slide 35 text

@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; }

Slide 36

Slide 36 text

@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); } // ... }

Slide 37

Slide 37 text

@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; } // ... }

Slide 38

Slide 38 text

@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); } }

Slide 39

Slide 39 text

@jschaedl 39 namespace Symfony\Component\Notifier\Transport; interface TransportFactoryInterface { public function create(Dsn $dsn): TransportInterface; public function supports(Dsn $dsn): bool; }

Slide 40

Slide 40 text

@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); } }

Slide 41

Slide 41 text

@jschaedl 41 # config/services.yaml App\Notifier\CustomTransportFactory: tags: ['chatter.transport_factory']

Slide 42

Slide 42 text

@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); // ... } }

Slide 43

Slide 43 text

@jschaedl 43

Slide 44

Slide 44 text

@jschaedl Routing Step Sending Step 44 ChannelPolicy ChatChannel SmsChannel EmailChannel BrowserChannel ChatMessage SmsMessage EmailMessage send(…) Notifier Recipient Notification Transports Transports Transports Recap

Slide 45

Slide 45 text

Thank you! https://speakerdeck.com/jschaedl