Slide 1

Slide 1 text

Back to the basics... Fabien Potencier @fabpot

Slide 2

Slide 2 text

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,...

Slide 3

Slide 3 text

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 ...

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Building flexible high-level abstractions
 on top of low-level ones What's next?

Slide 6

Slide 6 text

Building flexible high-level abstractions
 on top of low-level ones System Emails?

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

A typical System Email Color depends
 on the importance
 of the email Lines Action

Slide 10

Slide 10 text

$email = (new SystemEmail()) ->from('fabien@symfony.com') ->to('fabien@symfony.com') ->exception($exception) ->... ; $mailer->send($email); When something goes wrong?

Slide 11

Slide 11 text

# PROJECT_DIR/templates/email/system.html.twig $email = (new SystemEmail()) ->htmlTemplate('email/system.html.twig') ->textTemplate('email/system.txt.twig') ->... ; ...and configurable

Slide 12

Slide 12 text

{% 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() }} Go to MyApp {% endblock %} {% block exception %}{% endblock %} {% block footer_content %}

© MyApp

{% endblock %} ...and configurable Productivity && Flexibility

Slide 13

Slide 13 text

Email are Messages...

Slide 14

Slide 14 text

Sending SMS Messages

Slide 15

Slide 15 text

/** * @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

Slide 16

Slide 16 text

Symfony HttpClient + Symfony Messenger + Built-on third-party providers = Sending async SMS in one LOC! Symfony Texter Similar infrastructure
 as Mailer

Slide 17

Slide 17 text

$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

Slide 18

Slide 18 text

$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

Slide 19

Slide 19 text

$dsn = 'failover(twilio://SID:TOKEN@default?from=FROM nexmo://KEY:SECRET@default?from=FROM)'; SMS... higher-level API Similar infrastructure
 as Mailer

Slide 20

Slide 20 text

Sending ... Messages

Slide 21

Slide 21 text

/** * @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

Slide 22

Slide 22 text

$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

Slide 23

Slide 23 text

$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

Slide 24

Slide 24 text

$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

Slide 25

Slide 25 text

$dsn = 'all(slack://TOKEN@default?channel=CHANNEL telegram://TOKEN@default?channel=CHAT_ID)'; Messages... higher-level API

Slide 26

Slide 26 text

Symfony HttpClient + Symfony Messenger + Built-on third-party providers = Sending async Messages in one LOC! Symfony Chatter Similar infrastructure
 as Mailer

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

failover(twilio://SID:TOKEN@default?from=FROM telegram://TOKEN@default?channel=CHAT_ID) all(twilio://SID:TOKEN@default?from=FROM telegram://TOKEN@default?channel=CHAT_ID) SMS or/and Chat Message?

Slide 29

Slide 29 text

Building flexible high-level abstractions
 on top of low-level ones Mailer +Texter + Chatter = ?

Slide 30

Slide 30 text

Notifier

Slide 31

Slide 31 text

/** * @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, ...

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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()

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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 ❤

Slide 37

Slide 37 text

/** * @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

Slide 38

Slide 38 text

What about a SystemNotification? Remember SystemEmail?

Slide 39

Slide 39 text

/** * @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

Slide 40

Slide 40 text

(new SystemNotification('New customer!')->importance(SystemNotification::URGENT)) Importance to configure the Channels Defines the channels

Slide 41

Slide 41 text

framework: notifier: channel_policy: urgent: ['email', 'chat/slack', 'sms'] high: ['email', 'chat/slack'] medium: ['email'] low: ['pigeon'] Channels auto-configuration Importance Channels/ Transports

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

$notification = new ExceptionNotification(new \RuntimeException('Something went ...')); ExceptionNotification Uses SystemEmail with Exception support

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

How can we leverage this new infrastructure?

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Your own
 integration Use these new building blocks to create more powerful high-level abstractions with a few lines of code

Slide 49

Slide 49 text

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, ...

Slide 50

Slide 50 text

Experimental Component in 5.0 Notifier

Slide 51

Slide 51 text

Thank you! ❤