$30 off During Our Annual Pro Sale. View Details »

Symfony Notifier Demystified

Symfony Notifier Demystified

SymfonyLive Online German Edition 2021

https://live.symfony.com/2021-germany/schedule#session-567

Mit Symfony 5.3 ist die Notifier Komponente nun nicht mehr als experimentell markiert. Ein guter Zeitpunkt um einmal genauer zu schauen wie der Symfony Notifier funktioniert, welche Integrationen es bereits gibt und was man damit alles machen kann.

Jan Schädlich

April 16, 2021
Tweet

More Decks by Jan Schädlich

Other Decks in Programming

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
    chat
    sms
    email
    browser

    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
    The Channel has to support the Recipient
    ChannelPolicy
    ChatChannel
    SmsChannel
    EmailChannel
    BrowserChannel
    ChatMessage
    SmsMessage
    EmailMessage
    send(…)
    Notifier
    Recipient
    Notification
    Transports
    Transports
    Transports

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


    }


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


    }


    if (!$from = $dsn->getOption('from')) {


    throw new IncompleteDsnException('Missing from.', $dsn->getOriginalDsn());


    }


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


    // ...


    }


    }


    View Slide

  43. @jschaedl
    43

    View Slide

  44. @jschaedl
    Routing Step


    Sending Step


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

    View Slide

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

    View Slide