- Documentation >
- Users >
- User notifications
Notification channels
the ibexa/notifications package offers an extension to the Symfony notifier allowing to subscribe to notifications and sent them to information channels like email addresses, SMS, communication platforms, etc., including the 🔔 Back Office user profile notification.
Those notifications must not be confused with the notification bars (sent with TranslatableNotificationHandlerInterface)
or the 🔔 user notifications (sent with Ibexa\Contracts\Core\Repository\NotificationService).
TODO: Introduce the Ibexa\Contracts\Notifications\Service\NotificationServiceInterface
Subscribe to notifications
Some events send notifications you can subscribe to with one or more channels.
Available notifications:
TODO: What about notifications outside the Ibexa\Contracts namespace??
Ibexa\Share\Notification\ContentEditInvitationNotification
Ibexa\Share\Notification\ContentViewInvitationNotification
Ibexa\Share\Notification\ExternalParticipantContentViewInvitationNotification
Available notification channels:
| php bin/console debug:container --tag=notifier.channel
|
For example, let's subscribe to Commerce activity with a Slack channel:
| composer require symfony/slack-notifier
|
browser - Notification as flash message TODO: Test from a controller to see if it works
chat - Notification sent to a communication platform like Slack, Microsoft Teams, Google Chat, etc.
desktop - Notification sent to JoliNotif TODO: Do we support this?
email - Notification sent to email addresses
ibexa - Notification sent to back office user profiles
push - TODO
sms - Notification sent to phone numbers
In a .env file, set the DSN for the targetted Slack channel or user:
| SLACK_DSN=slack://xoxb-token@default?channel=ibexa-notifications
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | framework:
notifier:
chatter_transports:
slack: '%env(SLACK_DSN)%'
ibexa:
system:
default:
notifier:
subscriptions:
Ibexa\OrderManagement\Notification\OrderStatusChange:
channels:
- chat
Ibexa\Payment\Notification\PaymentStatusChange:
channels:
- chat
Ibexa\Shipping\Notification\ShipmentStatusChange:
channels:
- chat
|
Create a notification class
A new notification class can be created to send a new type of message to a new set of channels.
It must extend Symfony\Component\Notifier\Notification\Notification
and optionally implements some interfaces depending on the channels it could be sent to.
- Some channels don't accept the notification if it doesn't implement its related notification interface.
- Some channels accept every notification and have a default behavior if the notification doesn't implement their related notification interface.
TODO: List what type of channel notification interfaces can be implemented
TODO: Namespaces, Ibexa custom vs Symfony native
| Channel |
Notification interface |
! |
Description |
chat |
ChatNotificationInterface |
|
TODO |
email |
EmailNotificationInterface |
✔ |
TODO |
ibexa |
SystemNotificationInterface |
✔ |
TODO |
sms |
SmsNotificationInterface |
✔ |
TODO |
TODO: About ibexa channel being the 🔔 user notification
https://github.com/ibexa/notifications/blob/v5.0.6/src/lib/SystemNotification/SystemNotificationChannel.php#L51
TODO: How to deal with channels not needing a user like chat + Slack channel?
TODO: About SymfonyNotificationAdapter and SymfonyRecipientAdapter
Example
The following example is a command that sends a notification to users on several channels simultaneously.
it could be a scheduled task, a cronjob, warning users about its final result.
First, a CommandExecuted notification type is created.
It is supported by two channels for the example but could be extended to more.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 | <?php declare(strict_types=1);
namespace App\Notifications;
use Ibexa\Contracts\Notifications\SystemNotification\SystemMessage;
use Ibexa\Contracts\Notifications\SystemNotification\SystemNotificationInterface;
use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface;
use Symfony\Bridge\Twig\Mime\NotificationEmail;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Throwable;
class CommandExecuted extends Notification implements SystemNotificationInterface, EmailNotificationInterface
{
/** @param array<int, Throwable> $exceptions */
public function __construct(
private readonly Command $command,
private readonly int $exitCode,
private readonly array $exceptions
) {
parent::__construct((Command::SUCCESS === $this->exitCode ? '✔' : '✖') . $this->command->getName());
$this->importance(Command::SUCCESS === $this->exitCode ? Notification::IMPORTANCE_LOW : Notification::IMPORTANCE_HIGH);
}
public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): ?EmailMessage
{
$body = '';
foreach ($this->exceptions as $exception) {
$body .= $exception->getMessage() . '<br>';
}
$email = NotificationEmail::asPublicEmail()
->to($recipient->getEmail())
->subject($this->getSubject())
->html($body);
return new EmailMessage($email);
}
public function asSystemNotification(UserRecipientInterface $recipient, ?string $transport = null): ?SystemMessage
{
$message = new SystemMessage($recipient->getUser());
$message->setContext([
'icon' => Command::SUCCESS === $this->exitCode ? 'check-circle' : 'discard-circle',
'subject' => $this->command->getName(),
'content' => Command::SUCCESS === $this->exitCode ? 'No error' : count($this->exceptions) . ' error' . (count($this->exceptions) > 1 ? 's' : ''),
]);
return $message;
}
}
|
The channels subscribing to this notification are set in config/packages/ibexa.yaml:
| system:
default:
notifier:
subscriptions:
# …
App\Notifications\CommandExecuted:
channels:
- ibexa
- email
|
TODO: Explain the command
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | <?php
declare(strict_types=1);
namespace App\src\Command;
use App\Notifications\CommandExecuted;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Notifications\Service\NotificationServiceInterface;
use Ibexa\Contracts\Notifications\Value\Notification\SymfonyNotificationAdapter;
use Ibexa\Contracts\Notifications\Value\Recipent\SymfonyRecipientAdapter;
use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipient;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
#[AsCommand(name: 'app:send_notification', description: 'Example of command sending a notification')]
class NotificationSenderCommand extends Command
{
/** @param array<int, string> $recipientLogins */
public function __construct(
private readonly NotificationServiceInterface $notificationService,
private readonly UserService $userService,
private readonly array $recipientLogins = ['admin'],
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var array<int, \Throwable> $exceptions */
$exceptions = [];
try {
// Do something
if (rand(0, 1) == 1) {
throw new \RuntimeException('Something went wrong');
}
$exitCode = Command::SUCCESS;
} catch (\Exception $exception) {
$exceptions[] = $exception;
$exitCode = Command::FAILURE;
}
$recipients = [];
foreach ($this->recipientLogins as $login) {
try {
$user = $this->userService->loadUserByLogin($login);
$recipients[] = new UserRecipient($user);
} catch (\Exception $exception) {
$exceptions[] = $exception;
}
}
$this->notificationService->send(
new SymfonyNotificationAdapter(new CommandExecuted($this, $exitCode, $exceptions)),
array_map(
static fn (RecipientInterface $recipient): SymfonyRecipientAdapter => new SymfonyRecipientAdapter($recipient),
$recipients
)
);
return $exitCode;
}
}
|
TODO: Screenshots
Create a channel
A channel is a service implementing Symfony\Component\Notifier\Channel\ChannelInterface, and tagged notifier.channel alongside a channel shortname.
The following example is a custom channel that sends notifications to the logger.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | <?php declare(strict_types=1);
namespace App\Notifier\Channel;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Notifier\Channel\ChannelInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
class LogChannel implements ChannelInterface, LoggerAwareInterface
{
use LoggerAwareTrait;
public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void
{
if (isset($this->logger)) {
$this->logger->info($notification->getSubject(), [
'class' => get_class($notification),
'importance' => $notification->getImportance(),
'content' => $notification->getContent(),
]);
}
}
public function supports(Notification $notification, RecipientInterface $recipient): bool
{
return true;
}
}
|
| services:
App\Notifier\Channel\LogChannel:
tags:
- { name: 'notifier.channel', channel: 'log' }
|