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

Symfony Notifier Demystified

Jan Schädlich
August 19, 2021
110

Symfony Notifier Demystified

SymfonyWorld Online Summer Edition 2021

The Notifier Component is marked stable with the Symfony 5.3 release. A good time to have a closer look at how the Symfony Notifier works and what you can do with it.

Jan Schädlich

August 19, 2021
Tweet

Transcript

  1. Symfony Noti
    fi
    er
    Demysti
    fi
    ed

    View Slide

  2. Siemens Mobility Portugal


    [email protected]


    @jschaedl
    Jan Schädlich

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. @jschaedl 6
    use Symfony\Component\Notifier\Notification\Notification;


    $notification = (new Notification())


    ->subject('A nice subject')


    ->content('An even nicer content.')


    ;


    View Slide

  7. @jschaedl 7
    use Symfony\Component\Notifier\Recipient\Recipient;


    $recipient = new Recipient(


    '[email protected]',


    '+351 0815'


    );


    View Slide

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


    View Slide

  9. @jschaedl 9

    View Slide

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

    View Slide

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

    View Slide

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


    ;


    View Slide

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


    }


    }


    View Slide

  14. @jschaedl 14
    use Symfony\Component\Notifier\Notification\Notification;


    $notification = (new Notification())


    ->subject('A nice subject')


    ->content('An even nicer content.')


    ->importance(Notification::IMPORTANCE_URGENT)


    ;


    View Slide

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

    View Slide

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




    # ...


    View Slide

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

    View Slide

  18. @jschaedl 18
    AbstractChannel
    ChannelInterface
    ChannelPolicy
    ChatChannel
    SmsChannel
    EmailChannel
    BrowserChannel
    ChatMessage
    SmsMessage
    EmailMessage
    send(…)
    Notifier
    Recipient
    Notification
    Transports
    Transports
    Transports
    The Channel has to support the Recipient

    View Slide

  19. @jschaedl 19
    interface ChannelInterface


    {


    public function notify(


    Notification $notification, RecipientInterface $recipient, string $transportName = null


    ): void;


    public function supports(


    Notification $notification, RecipientInterface $recipient


    ): bool;


    }


    View Slide

  20. @jschaedl 20
    NoRecipient
    EmailRecipientInterface SmsRecipientInterface
    Recipient
    RecipientInterface

    View Slide

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

    View Slide

  22. @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;


    }


    }


    View Slide

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


    View Slide

  24. @jschaedl 24
    final class Notifier implements NotifierInterface


    {


    // ...


    public function send(Notification $notification, RecipientInterface ...$recipients): void


    {


    if (!$recipients) {


    $recipients = [new NoRecipient()];


    }




    // ...


    }


    // ...


    }


    View Slide

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

    View Slide

  26. @jschaedl 26
    Channel
    Notification ChatMessage
    SmsMessage
    EmailMessage
    MessageInterface
    Transport

    View Slide

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


    // ...


    }


    View Slide

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


    }




    // ...


    }


    }


    View Slide

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


    }


    }


    View Slide

  30. @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;


    }


    View Slide

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

    View Slide

  32. @jschaedl 32

    View Slide

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

    View Slide

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

    View Slide

  35. @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;


    }


    View Slide

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


    }


    // ...


    }


    View Slide

  37. @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;


    }


    // ...


    }


    View Slide

  38. @jschaedl 38
    final class CustomTransport extends AbstractTransport


    {


    // ...




    protected function doSend(MessageInterface $message): SentMessage


    {


    if (!$message instanceof ChatMessage) {


    throw new UnsupportedMessageTypeException(...);


    }


    dump(sprintf(‘Deliver message "%s" from "%s"',


    $message->getSubject(),


    $this->from


    ));


    return new SentMessage($message, (string) $this);


    }


    }


    View Slide

  39. @jschaedl 39
    namespace Symfony\Component\Notifier\Transport;


    interface TransportFactoryInterface


    {


    public function create(Dsn $dsn): TransportInterface;


    public function supports(Dsn $dsn): bool;


    }


    View Slide

  40. @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());


    }


    $from = $dsn->getRequiredOption('from');




    return new CustomTransport($from, $this->client, $this->dispatcher);


    }


    }


    View Slide

  41. @jschaedl 41
    # config/services.yaml


    App\Notifier\CustomTransportFactory:


    tags: ['chatter.transport_factory']


    View Slide

  42. @jschaedl
    # .env


    CUSTOM_DSN=custom://default?from=Jan


    # config/packages/notifier.yaml


    framework:


    notifier:


    chatter_transports:


    custom: '%env(CUSTOM_DSN)%'


    42

    View Slide

  43. @jschaedl
    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);


    // ...


    }


    }


    43

    View Slide

  44. @jschaedl 44

    View Slide

  45. @jschaedl
    Routing Step


    Sending Step


    45
    ChannelPolicy
    ChatChannel
    SmsChannel
    EmailChannel
    BrowserChannel
    ChatMessage
    SmsMessage
    EmailMessage
    send(…)
    Notifier
    Recipient
    Notification
    Transports
    Transports
    Transports
    Recap

    View Slide

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

    View Slide