Share
Embed
Transcript
51 slides
-
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,... -
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 ... -
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 -
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 -
use Symfony\Bridge\Twig\Mime\SystemEmail; $email = (new SystemEmail()) ->from('[email protected]') ->to('[email protected]') ->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 -
A typical System Email Color depends on the importance of
the email Lines Action -
{% 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>© MyApp</small></p> {% endblock %} ...and configurable Productivity && Flexibility -
/** * @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 -
Symfony HttpClient + Symfony Messenger + Built-on third-party providers =
Sending async SMS in one LOC! Symfony Texter Similar infrastructure as Mailer -
$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 -
$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 -
/** * @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 -
$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 -
$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 -
$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 -
Symfony HttpClient + Symfony Messenger + Built-on third-party providers =
Sending async Messages in one LOC! Symfony Chatter Similar infrastructure as Mailer -
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; } -
Building flexible high-level abstractions on top of low-level ones Mailer
+Texter + Chatter = ? -
/** * @Route("/checkout/thankyou") */ public function thankyou(NotifierInterface $notifier /* ...
*/) { $notification = new Notification('New customer!', ['sms', 'chat/slack', 'email']); $notifier->send($notification, new Receiver('[email protected]')); return $this->render('checkout/thankyou.html.twig', [ // ... ]); } Notify Messages! Channels: email, sms, chat, ... Transport: slack, telegram, twilio, ... -
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 -
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() -
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 -
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 -
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 ❤ -
/** * @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 -
/** * @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 -
framework: notifier: channel_policy: urgent: ['email', 'chat/slack', 'sms'] high: ['email', 'chat/slack']
medium: ['email'] low: ['pigeon'] Channels auto-configuration Importance Channels/ Transports -
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 -
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 -
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 -
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()); } -
Your own integration Use these new building blocks to create
more powerful high-level abstractions with a few lines of code -
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, ...