Symfony Notifier

Symfony Notifier

9a22d09f92d50fa3d2a16766d0ba52f8?s=128

Fabien Potencier

September 13, 2019
Tweet

Transcript

  1. 2.

    Building flexible high-level abstractions
 on top of low-level ones Embrace

    the Linux philosophy Web: HttpFoundation, HttpKernel, Routing, ... Infrastructure: Dotenv, Console, DependencyInjection, Cache, Lock, Messenger, ... Language: VarDumper, VarExporter, HttpClient, Polyfill, Intl, Process, Ldap, ... Features: ExpressionLanguage, Workflow, Mailer,...
  2. 3.

    Symfony Mailer > Swiftmailer HttpClient < Easy third-party provider support

    (HTTP APIs) Messenger < Easy asynchronous emails Twig < Easy email composition Dotenv < Easy transport configuration via DSNs ...
  3. 4.

    Symfony Mailer > Swiftmailer PHPUnit test assertions Profiler support ...

    use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class SomeTest extends WebTestCase { public function testMailerAssertions() { $client = $this->createClient(); $client->request('GET', '/send_email'); $this->assertEmailCount(1); $this->assertQueuedEmailCount(1); $this->assertEmailIsQueued($this->getMailerEvent(0)); $this->assertEmailIsNotQueued($this->getMailerEvent(1)); $email = $this->getMailerMessage(0); $this->assertEmailHasHeader($email, 'To'); $this->assertEmailHeaderSame($email, 'To', 'fabien@symfony $this->assertEmailTextBodyContains($email, 'Bar'); $this->assertEmailHtmlBodyContains($email, 'Foo'); $this->assertEmailAttachementCount($email, 1); } } Consistent Well integrated
  4. 7.

    Symfony Mailer + Twig with TwigExtraBundle - for auto-configuration, Twig

    1.12+ + Twig inky-extra package - Twig 1.12+ + Zurb Foundation for Emails CSS stylesheet + Twig cssinliner-extra package - Twig 1.12+ + Optimized default Twig templates = Built-in responsive, flexible, and generic system emails
  5. 8.

    use Symfony\Bridge\Twig\Mime\SystemEmail; $email = (new SystemEmail()) ->from('fabien@symfony.com') ->to('fabien@symfony.com') ->subject('You have

    a new customer!') ->lines( 'Fabien has just signed up for the most expensive plan.',
 'That\'s an additional revenue of 1€ per year.' ) ->action( 'Check the invoice' $urlGen->generate('invoice', ['id' => 1], UrlGeneratorInterface::ABSOLUTE_URL), ) ->importance(SystemEmail::HIGH) ; $mailer->send($email); A typical System Email
  6. 12.

    {% extends "@email/system.html.twig" %} {% block style %} {{ parent()

    }} .container.body_alert { border-top: 30px solid #ec5840; } {% endblock %} {% block lines %} This is an automated email for the MyApp application. {{ parent() }} {% endblock %} {% block action %} {{ parent() }} <spacer size="16"></spacer> <button class="secondary" href="https://myapp.com/">Go to MyApp</button> {% endblock %} {% block exception %}{% endblock %} {% block footer_content %} <p><small>&copy; MyApp</small></p> {% endblock %} ...and configurable Productivity && Flexibility
  7. 15.

    /** * @Route("/checkout/thankyou") */ public function thankyou(Texter $texter /* ...

    */) { $sms = new SmsMessage('+1415999888', 'Revenue has just increased by 1€ per year!'); $texter->send($sms); return $this->render('checkout/thankyou.html.twig', [ // ... ]); } Sending SMS Messages the easy way
  8. 16.

    Symfony HttpClient + Symfony Messenger + Built-on third-party providers =

    Sending async SMS in one LOC! Symfony Texter Similar infrastructure
 as Mailer
  9. 17.

    $sms = new SmsMessage('+1415999888', 'Revenue has just increased!'); $twilio =

    Transport::fromDsn('twilio://SID:TOKEN@default?from=FROM'); $twilio->send($sms); $nexmo = Transport::fromDsn('nexmo://KEY:SECRET@default?from=FROM'); $nexmo->send($sms); SMS... low-level API Similar infrastructure
 as Mailer
  10. 18.

    $texter = new Texter($twilio, $bus); $texter->send($sms); $transports = new Transports(['twilio'

    => $twilio, 'nexmo' => $nexmo]); $texter = new Texter($transports, $bus); $texter->send($sms); $sms->setTransport('nexmo'); $texter->send($sms); $bus->dispatch($sms); SMS... higher-level API
  11. 21.

    /** * @Route("/checkout/thankyou") */ public function thankyou(Chatter $chatter /* ...

    */) { $message = new ChatMessage('Revenue increased by 1€ per year...'); $chatter->send($message); return $this->render('checkout/thankyou.html.twig', [ // ... ]); } Sending Messages the easy way
  12. 22.

    $message = new ChatMessage('Revenue increased by 1€ per year...'); $slack

    = Transport::fromDsn('slack://TOKEN@default?channel=CHANNEL'); $slack->send($sms); $telegram = Transport::fromDsn('telegram://TOKEN@default?channel=CHAT_ID'); $telegram->send($sms); Messages... low-level API
  13. 23.

    $transports = Transport::fromDsns([ 'slack' => 'slack://TOKEN@default?channel=CHANNEL', 'telegram' => 'telegram://TOKEN@default?channel=CHAT_ID' ]);

    $chatter = new Chatter($transports, $bus); $chatter->send($message); $message->setTransport('telegram'); $chatter->send($message); $bus->dispatch($message); Messages... higher-level API
  14. 24.

    $options = (new SlackOptions()) ->iconEmoji('tada') ->iconUrl('https://symfony.com') ->username('SymfonyNext') ->channel($channel) ->block((new SlackSectionBlock())->text('Some

    Text')) ->block(new SlackDividerBlock()) ->block((new SlackSectionBlock()) ->text('Some Text in another block') ->accessory(new SlackImageBlockElement('http://placekitten.com/700/500', 'kitten')) ) ; $message = new ChatMessage('Default Text', $options); Messages... higher-level API
  15. 26.

    Symfony HttpClient + Symfony Messenger + Built-on third-party providers =

    Sending async Messages in one LOC! Symfony Chatter Similar infrastructure
 as Mailer
  16. 27.

    interface TransportInterface { public function send(MessageInterface $message): void; public function

    supports(MessageInterface $message): bool; public function __toString(): string; } A common transport layer interface MessageInterface { public function getRecipientId(): ?string; public function getText(): string; public function getOptions(): ?MessageOptionsInterface; public function getTransport(): ?string; }
  17. 30.
  18. 31.

    /** * @Route("/checkout/thankyou") */ public function thankyou(NotifierInterface $notifier /* ...

    */) { $notification = new Notification('New customer!', ['sms', 'chat/slack', 'email']); $notifier->send($notification, new Receiver('fabien@symfony.com')); return $this->render('checkout/thankyou.html.twig', [ // ... ]); } Notify Messages! Channels: email, sms, chat, ... Transport: slack, telegram, twilio, ...
  19. 32.

    class InvoiceNotification extends Notification { private $price; public function __construct(int

    $price) { parent::__construct('You have a new invoice.'); $this->price = $price; } public function getChannels(Receiver $receiver): array { if ($this->price > 1000 && $receiver->hasPhone()) { return ['sms', 'email']; } return ['email']; } } Customize Notifications
  20. 33.

    class InvoiceNotification extends Notification implements ChatNotificationInterface { public function asChatMessage(Receiver

    $receiver, string $transport): ChatMessage { if ('slack' === $transport) { return new ChatMessage('FOR SLACK: '.$this->getText()); } if ('telegram' === $transport) { return new ChatMessage('FOR TELEGRAM: '.$this->getText()); } return null; } } Customize Notifications and/or SmsNotificationInterface ::asSmsMessage()
  21. 34.

    framework: notifier: chatter_transports: slack: '%env(SLACK_DSN)%' telegram: '%env(TELEGRAM_DSN)%' texter_transports: twilio: '%env(TWILIO_DSN)%'

    nexmo: '%env(NEXMO_DSN)%' $ composer req twilio-notifier telegram-notifier Notifier Semantic Configuration NotifierInterface $notifier Chatter $chatter Texter $texter
  22. 35.

    Channels Mailer Chatter Texter Browser ~= $request->getSession()->getFlashBag()->add('notice', 'New customer!'); Pusher

    - iOS/Android/Desktop native notifications Database - Web Notification Center ... = A unified way to notify Users
 via a unified Transport layer
  23. 36.

    Telegram ~ 40 LOCs Nexmo ~ 40 LOCs Twilio ~

    40 LOCs ... including DSN support Each integration is a few LOCs namespace Symfony\Component\Notifier\Bridge\Twilio; final class TwilioTransport extends AbstractTransport { protected const HOST = 'api.twilio.com'; public function __construct(string $accountSid, string $authToken, string $from, HttpClientInter { $this->accountSid = $accountSid; $this->authToken = $authToken; $this->from = $from; parent::__construct($client, $dispatcher); } public function __toString(): string { return sprintf('twilio://%s?from=%s', $this->getEndpoint(), $this->from); } public function supports(MessageInterface $message): bool { return $message instanceof SmsMessage; } protected function doSend(MessageInterface $message): void { if (!$message instanceof SmsMessage) { throw new LogicException(sprintf('The "%s" transport only support instances of "%s".', _ } $endpoint = sprintf('https://%s/2010-04-01/Accounts/%s/Messages.json', $this->getEndpoint(), $response = $this->client->request('POST', $endpoint, [ 'auth_basic' => $this->accountSid.':'.$this->authToken, 'body' => ['From' => $this->from, 'To' => $message->getPhone(), 'Body' => $message->getT ], ]); if (201 !== $response->getStatusCode()) { $error = json_decode($response->getContent(false), true); throw new TransportException(sprintf('Unable to send the SMS: %s (see %s).', $error['mes } } } Help needed
 to support more transports ❤
  24. 37.

    /** * @Route("/checkout/thankyou") */ public function thankyou(NotifierInterface $notifier /* ...

    */) { $notification = new Notification('Invoice paid!', ['browser']); $notifier->send($notification, new NoReceiver()); return $this->render('checkout/thankyou.html.twig', [ // ... ]); } Many Channels / Transports
  25. 39.

    /** * @Route("/checkout/thankyou") */ public function thankyou(NotifierInterface $notifier /* ...

    */) { $notifier->send(new SystemNotification('New customer!'), ...Notifier::getSystemReceivers()); return $this->render('checkout/thankyou.html.twig', [ // ... ]); } SystemNotification for admin messages Global
 Receivers Auto-configured Channels
  26. 41.

    framework: notifier: channel_policy: urgent: ['email', 'chat/slack', 'sms'] high: ['email', 'chat/slack']

    medium: ['email'] low: ['pigeon'] Channels auto-configuration Importance Channels/ Transports
  27. 42.

    class SystemNotification extends Notification implements ChatNotificationInterface, EmailNotificationInterface { public function

    asEmailMessage(Receiver $receiver): ?Email { return (new SystemEmail()) ->to($receiver->getEmail()) ->subject($this->getText()) ->text($this->getText()) ->importance($this->getImportance()) ; } public function asChatMessage(Receiver $receiver, string $transport): ?ChatMessage { if ('slack' === $transport) { return new ChatMessage($this->getText(), (new SlackOptions())->iconEmoji($this->getEmoji())); } return null; } } Enhanced Representation
  28. 44.

    Send Messages via a unified API (Email, Browser, SMS, Chat,

    ...) Send to one or many Receivers Default configuration or custom one Async via Messenger Extensible to more third-party providers and more channels Symfony Notifier
  29. 46.

    Monolog
 NotifierHandler Triggered on Error level logs Uses the Notifier

    channel configuration Converts Error level logs to importance levels Configurable like any other Notification 40 LOCs of glue code errors: type: fingers_crossed action_level: error handler: notifier excluded_http_codes: [404, 405] notifier: type: service id: notifier.monolog_handler
  30. 47.

    Messenger Failed Messages Listener Triggered when a failed message failed

    Uses the Notifier channel configuration 20 LOCs of glue code public function onMessageFailed(WorkerMessageFailedEvent $event) { if ($event->willRetry()) { return; } $throwable = $event->getThrowable(); if ($throwable instanceof HandlerFailedException) { $throwable = $throwable->getNestedExceptions()[0]; } $envelope = $event->getEnvelope(); $notification = (new SystemNotification(sprintf('A "%s" message has just failed.', \get_cla) ->importance(SystemNotification::HIGH); $this->notifier->notify($notification, ...Notifier::getSystemReceivers()); }
  31. 48.

    Your own
 integration Use these new building blocks to create

    more powerful high-level abstractions with a few lines of code
  32. 49.

    Many
 Possibilities Go fast with the Notifier and the Full-Stack

    framework Be creative with the standalone low-level classes SystemEmail, TwilioTransport, Texter, SlackTransport, Chatter, ...