Composant Symfony Notifier : Création d'un notifier personnalisé

Comment utiliser le composant Notifier de Symfony pour créer un notifier personnalisé.

Jérémy 🤘
Jérémy 🤘

Historiquement quand on voulait envoyer des notifications dans un projet Symfony, on devait créer un event ainsi qu'un subscriber et cela pour tous les types de notifications que l'on souhaitait envoyer.

Mais avec le composant Notifier de Symfony, il est maintenant possible de grouper toutes ces notifications afin de tout centraliser et surtout gérer plus facilement grâce à une couche d'abstraction pour la gestion de ces envois.

Il suffira par la suite d'envoyer la notification en ciblant les "channels" (ou services) pour chaque composant qui doit envoyer la notification.

Installation

Copier
composer require symfony/notifier
composer require symfony/notifier

Utilisation

De base, le composant Notifier vient vide, il n'est pas possible de communiquer avec des API tiers pour envoyer des notifications. Si vous souhaitez communiquer il faudra installer des "extensions" à ce composant.

Configuration

La configuration est assez simple, il suffit de modifier le fichier config/packages/notifier.yaml pour définir les notifiers à utiliser.

Si vous souhaitez utiliser slack, installez l'extension, puis dans la partie chatter_transports indiquez slack: '%env(SLACK_DSN)%'.

Envoyer une notification

Que se soit dans un controller ou dans un service, il vous suffit d'injecter l'interface NotifierInterface puis de créer votre notification.

Copier
$notification = new Notification(
    'Hello World!',
    ['chat/slack'], // Le prefix chat/ fait référence aux chatter_transports dans le fichier notifier.yaml
);
$notification->content("My content.");
$notification->importance(Notification::IMPORTANCE_URGENT);

// Qui va recevoir la notification
// Dans le cas d'une notification chat, il instancier la classe NoRecipient
// Mais dans le cas d'un text ou autre la classe Recipient
$recipient = new Recipient(
    "email@domaine.fr",
    "01 02 03 04 05",
);
$notification = new Notification(
    'Hello World!',
    ['chat/slack'], // Le prefix chat/ fait référence aux chatter_transports dans le fichier notifier.yaml
);
$notification->content("My content.");
$notification->importance(Notification::IMPORTANCE_URGENT);

// Qui va recevoir la notification
// Dans le cas d'une notification chat, il instancier la classe NoRecipient
// Mais dans le cas d'un text ou autre la classe Recipient
$recipient = new Recipient(
    "email@domaine.fr",
    "01 02 03 04 05",
);

Notez bien que lors de la création d'une notification on cible dans le tableau les notifiers que nous souhaitons utiliser.

Et pour envoyer la notification :

Copier
$notifier->send($notification, $recipient);
$notifier->send($notification, $recipient);

Création d'un notifier personnalisé

Même s'il existe déjà beaucoup de système de notification, il peut être intéressant des fois de créer son propre système. Par exemple vous souhaitez notifier quelqu'un par Skype.

config/packages/notifier.yaml

Lors de la création il faut définir si notre système sera de type chatter ou texter. Dans notre cas il sera de type chatter.

Il faut donc dans le fichier yaml, dans la partie chatter_transports créer une nouvelle entrée. En clé il faut spécifier le nom de notre notifier. Nous allons mettre skype. En valeur '%env(SKYPE_DSN)%', ce qui nous permettra de définir via une URL toutes les informations nécessaires pour communiquer avec l'API de Skype.

Pour info, un DSN est construit comme cela : scheme://login:password@host?query=parameter.

La factory nous permettra d'indiquer à Symfony comment il va devoir instancier notre transport à partir de notre DSN que nous avons indiqué dans notre fichier yaml.

Il faut commencer par déclarer cette factory dans les services de Symfony :

Copier
App\Notifier\Transport\SkypeTransportFactory:
    tags: ['chatter.transport_factory']
App\Notifier\Transport\SkypeTransportFactory:
    tags: ['chatter.transport_factory']

Puis nous allons créer la factory en elle-même

Copier
<?php

declare(strict_types=1);

namespace App\Notifier\Transport;

use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Notifier\Transport\TransportInterface;

class SkypeTransportFactory extends AbstractTransportFactory
{
    protected const SCHEME = 'skype';

    public function create(Dsn $dsn): TransportInterface
    {
        return (new SkypeTransport(
            $dsn->getUser() ?? '',
            $dsn->getPassword() ?? '',
            $this->client,
            $this->dispatcher
        ))
            ->setHost($dsn->getHost())
        ;
    }

    protected function getSupportedSchemes(): array
    {
        return [static::SCHEME];
    }
}
<?php

declare(strict_types=1);

namespace App\Notifier\Transport;

use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Notifier\Transport\TransportInterface;

class SkypeTransportFactory extends AbstractTransportFactory
{
    protected const SCHEME = 'skype';

    public function create(Dsn $dsn): TransportInterface
    {
        return (new SkypeTransport(
            $dsn->getUser() ?? '',
            $dsn->getPassword() ?? '',
            $this->client,
            $this->dispatcher
        ))
            ->setHost($dsn->getHost())
        ;
    }

    protected function getSupportedSchemes(): array
    {
        return [static::SCHEME];
    }
}

Le transport

Maintenant que nous avons indiquer à Symfony comment il doit instancier notre transport, il faut créer ce transport :

Copier
<?php

declare(strict_types=1);

namespace App\Notifier\Transport;

use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\SentMessage;
use Symfony\Component\Notifier\Transport\AbstractTransport;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class SkypeTransport extends AbstractTransport
{
    protected const TRANSPORT = 'skype';

    protected string $username;
    protected string $password;

    public function __construct(
        string $username,
        string $password,
        HttpClientInterface $client = null,
        EventDispatcherInterface $dispatcher = null
    ) {
        parent::__construct($client, $dispatcher);

        $this->username = $username;
        $this->password = $password;
    }

    public function supports(MessageInterface $message): bool
    {
        return $message->getTransport() === static::TRANSPORT;
    }

    public function __toString(): string
    {
        return static::TRANSPORT;
    }

    /** Méthode qui sera exécutée à l'envoi de la notification */
    protected function doSend(MessageInterface $message): SentMessage
    {
        // C'est ici que va s'opérer la logique de la notification
        // [...]

        // À la fin du traitement avec l'API de notification, il faut retourner une instance de SentMessage qui
        // permettra d'avoir un objet générique en retour concernant l'envoi de celle-ci.
        $sentMessage = new SentMessage($message, (string) $this);

        // Si l'API nous donne une indication concernant l'unicité du message, il peut être stocké comme ci-après
        // $sentMessage->setMessageId(...);

        return $sentMessage;
    }
}
<?php

declare(strict_types=1);

namespace App\Notifier\Transport;

use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\SentMessage;
use Symfony\Component\Notifier\Transport\AbstractTransport;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class SkypeTransport extends AbstractTransport
{
    protected const TRANSPORT = 'skype';

    protected string $username;
    protected string $password;

    public function __construct(
        string $username,
        string $password,
        HttpClientInterface $client = null,
        EventDispatcherInterface $dispatcher = null
    ) {
        parent::__construct($client, $dispatcher);

        $this->username = $username;
        $this->password = $password;
    }

    public function supports(MessageInterface $message): bool
    {
        return $message->getTransport() === static::TRANSPORT;
    }

    public function __toString(): string
    {
        return static::TRANSPORT;
    }

    /** Méthode qui sera exécutée à l'envoi de la notification */
    protected function doSend(MessageInterface $message): SentMessage
    {
        // C'est ici que va s'opérer la logique de la notification
        // [...]

        // À la fin du traitement avec l'API de notification, il faut retourner une instance de SentMessage qui
        // permettra d'avoir un objet générique en retour concernant l'envoi de celle-ci.
        $sentMessage = new SentMessage($message, (string) $this);

        // Si l'API nous donne une indication concernant l'unicité du message, il peut être stocké comme ci-après
        // $sentMessage->setMessageId(...);

        return $sentMessage;
    }
}

Comme vous pouvez le voir, la classe parente a besoin d'un client HTTP et de l'event dispatcher.

Le client HTTP nous permet de faire notre appel plus facilement et offre lui aussi une couche d'abstraction quand à l'utilisation qui est derrière.

Quant à l'event dispatcher, il est présent car finalement derrière, c'est encore un event qui est dispatché pour envoyer la notification mais cette fois, pas besoin de le gérer.

Envoie de la notification

Comme dans l'exemple qui est plus haut, il suffit juste de créer notre notification et l'envoyer, sauf que cette fois à la place (ou en plus) de mettre ['chat/slack'] on peut mettre ['chat/slack', 'chat/skype']. Ainsi le même message sera envoyé à la fois sur Slack et mais aussi sur Skype.

Source