Lines 100.00% 17 / 17
Functions and Methods 100.00% 4 / 4
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
ChatSender 100.00% 17 / 17 100.00% 4 / 4 10 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 recipients 100.00% 3 / 3 100.00% 1 / 1 3
 send 100.00% 6 / 6 100.00% 1 / 1 3
 sendTo 100.00% 7 / 7 100.00% 1 / 1 3
1<?php
2
3declare(strict_types=1);
4
5namespace App\Notifier;
6
7use Symfony\Component\DependencyInjection\Attribute\Autowire;
8use Symfony\Component\Messenger\Exception\ExceptionInterface as MessengerException;
9use Symfony\Component\Notifier\Bridge\Slack\SlackOptions;
10use Symfony\Component\Notifier\Bridge\Telegram\TelegramOptions;
11use Symfony\Component\Notifier\ChatterInterface;
12use Symfony\Component\Notifier\Exception\TransportExceptionInterface;
13use Symfony\Component\Notifier\Message\ChatMessage;
14
15/**
16 * Sends a ChatText to Slack and Telegram: to the feed's Slack channel (SLACK_CHANNEL,
17 * SLACK_ERRORS_CHANNEL) and Telegram chat (TELEGRAM_CHAT_ID, TELEGRAM_ERRORS_CHAT_ID). A chat
18 * whose channel or chat ID is empty is left out.
19 */
20final readonly class ChatSender
21{
22    public const string SLACK = 'slack';
23
24    public const string TELEGRAM = 'telegram';
25
26    public function __construct(
27        private ChatterInterface $chatter,
28        #[Autowire(env: 'SLACK_CHANNEL')]
29        private string $slackChannel,
30        #[Autowire(env: 'SLACK_ERRORS_CHANNEL')]
31        private string $slackErrorsChannel,
32        #[Autowire(env: 'TELEGRAM_CHAT_ID')]
33        private string $telegramChat,
34        #[Autowire(env: 'TELEGRAM_ERRORS_CHAT_ID')]
35        private string $telegramErrorsChat,
36    ) {
37    }
38
39    /**
40     * The chats a feed goes to, with their channel or chat ID.
41     *
42     * @return array<self::SLACK|self::TELEGRAM, string>
43     */
44    public function recipients(ChatFeed $feed): array
45    {
46        return \array_filter(match ($feed) {
47            ChatFeed::Activity => [self::SLACK => $this->slackChannel, self::TELEGRAM => $this->telegramChat],
48            ChatFeed::Errors => [self::SLACK => $this->slackErrorsChannel, self::TELEGRAM => $this->telegramErrorsChat],
49        }, static fn (string $recipient): bool => '' !== $recipient);
50    }
51
52    /**
53     * Sends the text to every chat of the feed, also when one of them fails.
54     *
55     * @return array<self::SLACK|self::TELEGRAM, TransportExceptionInterface|MessengerException> the failures
56     */
57    public function send(ChatFeed $feed, ChatText $text): array
58    {
59        $failures = [];
60        foreach ($this->recipients($feed) as $transport => $recipient) {
61            try {
62                $this->sendTo($transport, $recipient, $text);
63            } catch (TransportExceptionInterface|MessengerException $exception) {
64                $failures[$transport] = $exception;
65            }
66        }
67
68        return $failures;
69    }
70
71    /**
72     * @param self::SLACK|self::TELEGRAM $transport
73     *
74     * @throws TransportExceptionInterface|MessengerException
75     */
76    public function sendTo(string $transport, string $recipient, ChatText $text): void
77    {
78        $message = match ($transport) {
79            self::SLACK => new ChatMessage($text->forSlack(), new SlackOptions()->recipient($recipient)),
80            self::TELEGRAM => new ChatMessage($text->forTelegram(), new TelegramOptions()
81                ->chatId($recipient)
82                ->parseMode(TelegramOptions::PARSE_MODE_HTML)
83                ->disableWebPagePreview(true)),
84        };
85
86        $this->chatter->send($message->transport($transport));
87    }
88}