Skip to content

Ibexa Notifications#3090

Open
adriendupuis wants to merge 57 commits into5.0from
notifications
Open

Ibexa Notifications#3090
adriendupuis wants to merge 57 commits into5.0from
notifications

Conversation

@adriendupuis
Copy link
Copy Markdown
Contributor

@adriendupuis adriendupuis commented Mar 17, 2026

Question Answer
JIRA Ticket IBX-7057
Versions 5.0, 4.6
Edition All

Document ibexa/notifications

Checklist

  • Text renders correctly
  • Text has been checked with vale
  • Description metadata is up to date
  • Redirects cover removed/moved pages
  • Code samples are working
  • PHP code samples have been fixed with PHP CS fixer
  • Added link to this PR in relevant JIRA ticket or code PR

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 17, 2026

adriendupuis and others added 23 commits March 17, 2026 08:55
Mainly to avoid confusion with administration/back_office/notifications.md and fix duplicate:

WARNING -  Doc file 'administration/back_office/notifications.md' contains a link 'users/notifications.md', but the target 'administration/back_office/users/notifications.md' is not found among documentation files.
WARNING -  AutoLinksPlugin: Duplicate filename referred to in 'docs/administration/project_organization/bundles.md': 'notifications.md' exists at ['docs/administration/back_office/notifications.md', 'docs/users/notifications.md']
WARNING -  AutoLinksPlugin: Duplicate filename referred to in 'docs/api/event_reference/other_events.md': 'notifications.md' exists at ['docs/administration/back_office/notifications.md', 'docs/users/notifications.md']
WARNING -  AutoLinksPlugin: Duplicate filename referred to in 'docs/resources/new_in_doc.md': 'notifications.md' exists at ['docs/administration/back_office/notifications.md', 'docs/users/notifications.md']
Trying to fix "Cannot call method info() on Psr\Log\LoggerInterface|null."
@adriendupuis adriendupuis marked this pull request as ready for review March 31, 2026 08:12
sonarqubecloud
/ SonarCloud Code Analysis
Extract this nested ternary operation into an independent statement.
Comment thread docs/users/notification_channels.md Outdated
Comment thread docs/users/notification_channels.md Outdated
Co-authored-by: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

@adriendupuis adriendupuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Subscribe to notifications" sections is harsh to read…

Comment thread docs/users/notification_channels.md
Comment thread docs/users/notification_channels.md Outdated
Comment thread docs/users/notification_channels.md Outdated
Comment thread docs/users/notification_channels.md Outdated
Comment thread code_samples/user_management/notifications/assets/scss/notifications.scss Outdated
Comment thread code_samples/user_management/notifications/config/packages/notifications.yaml Outdated
Comment thread code_samples/user_management/notifications/config/packages/notifications.yaml Outdated
Comment thread code_samples/user_management/notifications/src/Notifications/CommandExecuted.php Outdated
Comment thread docs/users/notification_channels.md Outdated
Comment thread docs/users/notification_channels.md
Comment thread docs/users/notification_channels.md Outdated
Comment thread docs/users/notification_channels.md Outdated
Comment thread docs/users/notification_channels.md Outdated
Comment thread code_samples/user_management/notifications/config/packages/notifications.yaml Outdated
adriendupuis and others added 3 commits May 6, 2026 11:19
Co-authored-by: Marek Nocoń <mnocon@users.noreply.github.com>
Co-authored-by: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

code_samples/ change report

Before (on target branch)After (in current PR)

code_samples/user_management/notifications/assets/scss/notifications.scss


code_samples/user_management/notifications/assets/scss/notifications.scss

docs/users/notification_channels.md@232:``` scss
docs/users/notification_channels.md@233:[[= include_code('code_samples/user_management/notifications/assets/scss/notifications.scss') =]]
docs/users/notification_channels.md@234:```

001⫶@use '@ibexa-admin-ui/src/bundle/Resources/public/scss/_alerts.scss' as *;
002⫶
003⫶.ibexa-alert {
004⫶ &--notification {
005⫶ @extend .ibexa-alert--info;
006⫶ }
007⫶}


code_samples/user_management/notifications/config/packages/notifications.yaml


code_samples/user_management/notifications/config/packages/notifications.yaml

docs/users/notification_channels.md@101:``` yaml hl_lines="12-20"
docs/users/notification_channels.md@102:[[= include_code('code_samples/user_management/notifications/config/packages/notifications.yaml', 1, 20) =]]
docs/users/notification_channels.md@103:
docs/users/notification_channels.md@104:## Create a notification class
docs/users/notification_channels.md@105:
docs/users/notification_channels.md@106:You can define a new notification type and assign a new set of channels to it, customizing how it's delivered.
docs/users/notification_channels.md@107:It must extend the `Symfony\Component\Notifier\Notification\Notification` class
docs/users/notification_channels.md@108:and can optionally implement interfaces required by specific channels.
docs/users/notification_channels.md@109:
docs/users/notification_channels.md@110:- Some channels don't accept the notification if it doesn't implement their related notification interface. They have a checkmark in the ⚠ column below.
docs/users/notification_channels.md@111:- Some channels accept every notification and have a default behavior if the notification doesn't implement their related notification interface.
docs/users/notification_channels.md@112:
docs/users/notification_channels.md@113:| Channel | Notification interface | <span title="The channel needs the notification to implement its interface.">⚠</span> |
docs/users/notification_channels.md@114:|:----------|:-------------------------------|---------------------------------------------------------------------------------------|
docs/users/notification_channels.md@115:| `actito` | `EmailNotificationInterface` | ✔ |
docs/users/notification_channels.md@116:| `chat` | `ChatNotificationInterface` | |
docs/users/notification_channels.md@117:| `desktop` | `DesktopNotificationInterface` | |
docs/users/notification_channels.md@118:| `email` | `EmailNotificationInterface` | ✔ |
docs/users/notification_channels.md@119:| `ibexa` | `SystemNotificationInterface` | ✔ |
docs/users/notification_channels.md@120:| `push` | `PushNotificationInterface` | |
docs/users/notification_channels.md@121:| `sms` | `SmsNotificationInterface` | ✔ |
docs/users/notification_channels.md@122:
docs/users/notification_channels.md@123:The [`SystemNotificationInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-SystemNotification-SystemNotificationInterface.html) is not part of Symfony and has its own namespace.
docs/users/notification_channels.md@124:
docs/users/notification_channels.md@125:The `ibexa` channel sends notifications to users through their profile menu, exactly as [user notifications](notifications.md#user-notifications).
docs/users/notification_channels.md@126:The [`SystemNotificationChannel` uses the core `NotificationService`](https://github.com/ibexa/notifications/blob/v5.0.6/src/lib/SystemNotification/SystemNotificationChannel.php#L51) to do so.
docs/users/notification_channels.md@127:
docs/users/notification_channels.md@128:Some channels don't need a recipient:
docs/users/notification_channels.md@129:
docs/users/notification_channels.md@130:- `browser`: Always sends a flash message to the current user
docs/users/notification_channels.md@131:- `chat`: Always sends a message to the same connection resource
docs/users/notification_channels.md@132:
docs/users/notification_channels.md@133:### Notification sending
docs/users/notification_channels.md@134:
docs/users/notification_channels.md@135:Use the objects from the [`Ibexa\Contracts\Notifications`](/api/php_api/php_api_reference/namespaces/ibexa-contracts-notifications.html) namespace to work with notifications.
docs/users/notification_channels.md@136:
docs/users/notification_channels.md@137:The [`…\Service\NotificationServiceInterface::send()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Service-NotificationServiceInterface.html#method_send) expects two arguments:
docs/users/notification_channels.md@138:
docs/users/notification_channels.md@139:- The first argument is an [`…\Value\NotificationInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-NotificationInterface.html).
docs/users/notification_channels.md@140: This interface is implemented by the [`…\Value\Notification\SymfonyNotificationAdapter`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Notification-SymfonyNotificationAdapter.html)
docs/users/notification_channels.md@141: which allows you to wrap any class extending `Symfony\Component\Notifier\Notification\Notification`.
docs/users/notification_channels.md@142:- The optional second argument is an array of [`…\Value\RecipientInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-RecipientInterface.html).
docs/users/notification_channels.md@143: This interface is implemented by the [`…\Value\Recipent\SymfonyRecipientAdapter`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Recipent-SymfonyRecipientAdapter.html)
docs/users/notification_channels.md@144: used to wrap `Symfony\Component\Notifier\Recipient\RecipientInterface`.
docs/users/notification_channels.md@145: - This Symfony interface is implemented by [`…\Value\Recipent\UserRecipient`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Recipent-UserRecipient.html)
docs/users/notification_channels.md@146: which can wrap classes implementing the [`Ibexa\Contracts\Core\Repository\Values\User\UserReference` interface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-User-UserReference.html),
docs/users/notification_channels.md@147: - The [`UserService` methods to load a user](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-UserService.html#method_loadUser) are returning objects implementing this `UserReference` interface.
docs/users/notification_channels.md@148: - The [`PermissionResolver::getCurrentUserReference()` method](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-PermissionResolver.html#method_getCurrentUserReference) is returning objects implementing this `UserReference` interface.
docs/users/notification_channels.md@149:
docs/users/notification_channels.md@150:For example, to send a notification, you often use a combination like the following:
docs/users/notification_channels.md@151:
docs/users/notification_channels.md@152:```php hl_lines="8-11"

001⫶framework:
002⫶ notifier:
003⫶ chatter_transports:
004⫶ slack: '%env(SLACK_DSN)%'
005⫶ibexa:
006⫶ system:
007⫶ default:
008❇️ notifier:
009❇️ subscriptions:
010❇️ # The configuration below is added to the `default` scope without overriding the one defined in ibexa.yaml
011❇️ # Custom subscriptions:
012❇️ Ibexa\OrderManagement\Notification\OrderStatusChange:
013❇️ channels:
014❇️ - chat
015❇️ Ibexa\Payment\Notification\PaymentStatusChange:
016❇️ channels:
017❇️ - chat
018❇️ Ibexa\Shipping\Notification\ShipmentStatusChange:
019❇️ channels:
020❇️ - chat
021⫶
022⫶## Create a notification class
023⫶
024⫶You can define a new notification type and assign a new set of channels to it, customizing how it's delivered.
025⫶It must extend the `Symfony\Component\Notifier\Notification\Notification` class
026⫶and can optionally implement interfaces required by specific channels.
027⫶
028⫶- Some channels don't accept the notification if it doesn't implement their related notification interface. They have a checkmark in the ⚠ column below.
029⫶- Some channels accept every notification and have a default behavior if the notification doesn't implement their related notification interface.
030⫶
031⫶| Channel | Notification interface | <span title="The channel needs the notification to implement its interface.">⚠</span> |
032⫶|:----------|:-------------------------------|---------------------------------------------------------------------------------------|
033⫶| `actito` | `EmailNotificationInterface` | ✔ |
034⫶| `chat` | `ChatNotificationInterface` | |
035⫶| `desktop` | `DesktopNotificationInterface` | |
036⫶| `email` | `EmailNotificationInterface` | ✔ |
037⫶| `ibexa` | `SystemNotificationInterface` | ✔ |
038⫶| `push` | `PushNotificationInterface` | |
039⫶| `sms` | `SmsNotificationInterface` | ✔ |
040⫶
041⫶The [`SystemNotificationInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-SystemNotification-SystemNotificationInterface.html) is not part of Symfony and has its own namespace.
042⫶
043⫶The `ibexa` channel sends notifications to users through their profile menu, exactly as [user notifications](notifications.md#user-notifications).
044⫶The [`SystemNotificationChannel` uses the core `NotificationService`](https://github.com/ibexa/notifications/blob/v5.0.6/src/lib/SystemNotification/SystemNotificationChannel.php#L51) to do so.
045⫶
046⫶Some channels don't need a recipient:
047⫶
048⫶- `browser`: Always sends a flash message to the current user
049⫶- `chat`: Always sends a message to the same connection resource
050⫶
051⫶### Notification sending
052⫶
053⫶Use the objects from the [`Ibexa\Contracts\Notifications`](/api/php_api/php_api_reference/namespaces/ibexa-contracts-notifications.html) namespace to work with notifications.
054⫶
055⫶The [`…\Service\NotificationServiceInterface::send()`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Service-NotificationServiceInterface.html#method_send) expects two arguments:
056⫶
057⫶- The first argument is an [`…\Value\NotificationInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-NotificationInterface.html).
058⫶ This interface is implemented by the [`…\Value\Notification\SymfonyNotificationAdapter`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Notification-SymfonyNotificationAdapter.html)
059⫶ which allows you to wrap any class extending `Symfony\Component\Notifier\Notification\Notification`.
060⫶- The optional second argument is an array of [`…\Value\RecipientInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-RecipientInterface.html).
061⫶ This interface is implemented by the [`…\Value\Recipent\SymfonyRecipientAdapter`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Recipent-SymfonyRecipientAdapter.html)
062⫶ used to wrap `Symfony\Component\Notifier\Recipient\RecipientInterface`.
063⫶ - This Symfony interface is implemented by [`…\Value\Recipent\UserRecipient`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Notifications-Value-Recipent-UserRecipient.html)
064⫶ which can wrap classes implementing the [`Ibexa\Contracts\Core\Repository\Values\User\UserReference` interface](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-User-UserReference.html),
065⫶ - The [`UserService` methods to load a user](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-UserService.html#method_loadUser) are returning objects implementing this `UserReference` interface.
066⫶ - The [`PermissionResolver::getCurrentUserReference()` method](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-PermissionResolver.html#method_getCurrentUserReference) is returning objects implementing this `UserReference` interface.
067⫶
068⫶For example, to send a notification, you often use a combination like the following:

docs/users/notification_channels.md@182:``` yaml hl_lines="17-20"
docs/users/notification_channels.md@183:[[= include_code('code_samples/user_management/notifications/config/packages/notifications.yaml', 5, 24) =]]
docs/users/notification_channels.md@184:```

001⫶ibexa:
002⫶ system:
003⫶ default:
004⫶ notifier:
005⫶ subscriptions:
006⫶ # The configuration below is added to the `default` scope without overriding the one defined in ibexa.yaml
007⫶ # Custom subscriptions:
008⫶ Ibexa\OrderManagement\Notification\OrderStatusChange:
009⫶ channels:
010⫶ - chat
011⫶ Ibexa\Payment\Notification\PaymentStatusChange:
012⫶ channels:
013⫶ - chat
014⫶ Ibexa\Shipping\Notification\ShipmentStatusChange:
015⫶ channels:
016⫶ - chat
017❇️ App\Notifications\CommandExecuted:
018❇️ channels:
019❇️ - ibexa
020❇️ - email

docs/users/notification_channels.md@250:``` yaml hl_lines="13-15 43-45"
docs/users/notification_channels.md@251:[[= include_code('code_samples/user_management/notifications/config/packages/notifications.yaml', 5, 6) =]]
docs/users/notification_channels.md@252: # …
docs/users/notification_channels.md@253:[[= include_code('code_samples/user_management/notifications/config/packages/notifications.yaml', 26, 34) =]]
docs/users/notification_channels.md@254:[[= include_code('code_samples/user_management/notifications/config/packages/notifications.yaml', 36, 65) =]]
docs/users/notification_channels.md@255:[[= include_code('code_samples/user_management/notifications/config/packages/notifications.yaml', 67) =]]
docs/users/notification_channels.md@256:```

001⫶ibexa:
002⫶ system:
003⫶ # …
004⫶ admin_group:
005⫶ notifier:
006⫶ subscriptions:
007⫶ # The configuration below is added to the `admin_group` scope without overriding the one defined in ibexa_admin_ui.yaml
008⫶ # Custom subscriptions:
009⫶ App\Notifications\CommandExecuted:
010⫶ channels:
011⫶ - ibexa
012⫶ - email
013❇️ App\Notifications\ControllerFeedback:
014❇️ channels:
015❇️ - browser
016⫶ storefront_group:
017⫶ notifier:
018⫶ subscriptions:
019⫶ # The configuration defined in ibexa.yaml for `default` scope is repeated as the configuration below overrides it
020⫶ Ibexa\Contracts\User\Notification\UserPasswordReset:
021⫶ channels:
022⫶ - email
023⫶ Ibexa\Contracts\User\Notification\UserInvitation:
024⫶ channels:
025⫶ - email
026⫶ Ibexa\Contracts\FormBuilder\Notifications\FormSubmitted:
027⫶ channels:
028⫶ - email
029⫶ # Custom subscriptions:
030⫶ Ibexa\OrderManagement\Notification\OrderStatusChange:
031⫶ channels:
032⫶ - chat
033⫶ Ibexa\Payment\Notification\PaymentStatusChange:
034⫶ channels:
035⫶ - chat
036⫶ Ibexa\Shipping\Notification\ShipmentStatusChange:
037⫶ channels:
038⫶ - chat
039⫶ App\Notifications\CommandExecuted:
040⫶ channels:
041⫶ - ibexa
042⫶ - email

docs/users/notification_channels.md@297:``` yaml hl_lines="5"
docs/users/notification_channels.md@298:[[= include_code('code_samples/user_management/notifications/config/packages/notifications.yaml', 21, 25) =]]
docs/users/notification_channels.md@299:```

001⫶ App\Notifications\CommandExecuted:
002⫶ channels:
003⫶ - ibexa
004⫶ - email
005❇️ - log


code_samples/user_management/notifications/config/services.yaml


code_samples/user_management/notifications/config/services.yaml

docs/users/notification_channels.md@291:``` yaml
docs/users/notification_channels.md@292:[[= include_code('code_samples/user_management/notifications/config/services.yaml') =]]
docs/users/notification_channels.md@293:```

001⫶services:
002⫶ App\Notifier\Channel\LogChannel:
003⫶ tags:
004⫶ - { name: 'notifier.channel', channel: 'log' }


code_samples/user_management/notifications/src/Command/NotificationSenderCommand.php


code_samples/user_management/notifications/src/Command/NotificationSenderCommand.php

docs/users/notification_channels.md@190:[[= include_code('code_samples/user_management/notifications/src/Command/NotificationSenderCommand.php') =]]

001⫶<?php
002⫶
003⫶declare(strict_types=1);
004⫶
005⫶namespace App\Command;
006⫶
007⫶use App\Notifications\CommandExecuted;
008⫶use Ibexa\Contracts\Core\Repository\UserService;
009⫶use Ibexa\Contracts\Notifications\Service\NotificationServiceInterface;
010⫶use Ibexa\Contracts\Notifications\Value\Notification\SymfonyNotificationAdapter;
011⫶use Ibexa\Contracts\Notifications\Value\Recipent\SymfonyRecipientAdapter;
012⫶use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipient;
013⫶use Symfony\Component\Console\Attribute\AsCommand;
014⫶use Symfony\Component\Console\Command\Command;
015⫶use Symfony\Component\Console\Input\InputInterface;
016⫶use Symfony\Component\Console\Output\OutputInterface;
017⫶use Symfony\Component\Notifier\Recipient\RecipientInterface;
018⫶
019⫶#[AsCommand(name: 'app:send_notification', description: 'Example of command sending a notification')]
020⫶class NotificationSenderCommand extends Command
021⫶{
022⫶ /** @param array<int, string> $recipientLogins */
023⫶ public function __construct(
024⫶ private readonly NotificationServiceInterface $notificationService,
025⫶ private readonly UserService $userService,
026⫶ private readonly array $recipientLogins = ['admin'],
027⫶ ) {
028⫶ parent::__construct();
029⫶ }
030⫶
031⫶ protected function execute(InputInterface $input, OutputInterface $output): int
032⫶ {
033⫶ /** @var array<int, \Throwable> $exceptions */
034⫶ $exceptions = [];
035⫶
036⫶ try {
037⫶ // Do something
038⫶ if (random_int(0, 1) == 1) {
039⫶ throw new \RuntimeException('Something went wrong');
040⫶ }
041⫶ $exitCode = Command::SUCCESS;
042⫶ } catch (\Exception $exception) {
043⫶ $exceptions[] = $exception;
044⫶ $exitCode = Command::FAILURE;
045⫶ }
046⫶
047⫶ $recipients = [];
048⫶ foreach ($this->recipientLogins as $login) {
049⫶ try {
050⫶ $user = $this->userService->loadUserByLogin($login);
051⫶ $recipients[] = new UserRecipient($user);
052⫶ } catch (\Exception $exception) {
053⫶ $exceptions[] = $exception;
054⫶ }
055⫶ }
056⫶
057⫶ $this->notificationService->send(
058⫶ new SymfonyNotificationAdapter(new CommandExecuted($this, $exitCode, $exceptions)),
059⫶ array_map(
060⫶ static fn (RecipientInterface $recipient): SymfonyRecipientAdapter => new SymfonyRecipientAdapter($recipient),
061⫶ $recipients
062⫶ )
063⫶ );
064⫶
065⫶ return $exitCode;
066⫶ }
067⫶}


code_samples/user_management/notifications/src/Controller/NotificationSenderController.php


code_samples/user_management/notifications/src/Controller/NotificationSenderController.php

docs/users/notification_channels.md@209:``` php
docs/users/notification_channels.md@210:[[= include_code('code_samples/user_management/notifications/src/Controller/NotificationSenderController.php') =]]
docs/users/notification_channels.md@211:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Controller;
004⫶
005⫶use App\Notifications\ControllerFeedback;
006⫶use Ibexa\Contracts\Notifications\Service\NotificationServiceInterface;
007⫶use Ibexa\Contracts\Notifications\Value\Notification\SymfonyNotificationAdapter;
008⫶use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
009⫶use Symfony\Component\HttpFoundation\Response;
010⫶use Symfony\Component\Routing\Attribute\Route;
011⫶
012⫶class NotificationSenderController extends AbstractController
013⫶{
014⫶ public function __construct(
015⫶ private readonly NotificationServiceInterface $notificationService,
016⫶ ) {
017⫶ }
018⫶
019⫶ #[Route('/notification-sender')]
020⫶ public function index(): Response
021⫶ {
022⫶ $this->notificationService->send(
023⫶ new SymfonyNotificationAdapter((new ControllerFeedback('Message sent from controller'))->emoji('👍')),
024⫶ );
025⫶
026⫶ return $this->render('@ibexadesign/notification-sender-controller.html.twig');
027⫶ }
028⫶}


code_samples/user_management/notifications/src/Notifications/CommandExecuted.php


code_samples/user_management/notifications/src/Notifications/CommandExecuted.php

docs/users/notification_channels.md@176:``` php
docs/users/notification_channels.md@177:[[= include_code('code_samples/user_management/notifications/src/Notifications/CommandExecuted.php') =]]
docs/users/notification_channels.md@178:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Notifications;
004⫶
005⫶use Ibexa\Contracts\Notifications\SystemNotification\SystemMessage;
006⫶use Ibexa\Contracts\Notifications\SystemNotification\SystemNotificationInterface;
007⫶use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface;
008⫶use Symfony\Bridge\Twig\Mime\NotificationEmail;
009⫶use Symfony\Component\Console\Command\Command;
010⫶use Symfony\Component\Notifier\Message\EmailMessage;
011⫶use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
012⫶use Symfony\Component\Notifier\Notification\Notification;
013⫶use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
014⫶use Throwable;
015⫶
016⫶class CommandExecuted extends Notification implements SystemNotificationInterface, EmailNotificationInterface
017⫶{
018⫶ /** @param array<int, Throwable> $exceptions */
019⫶ public function __construct(
020⫶ private readonly Command $command,
021⫶ private readonly int $exitCode,
022⫶ private readonly array $exceptions
023⫶ ) {
024⫶ parent::__construct((Command::SUCCESS === $this->exitCode ? '✔' : '✖') . $this->command->getName());
025⫶ $this->importance(Command::SUCCESS === $this->exitCode ? Notification::IMPORTANCE_LOW : Notification::IMPORTANCE_HIGH);
026⫶ }
027⫶
028⫶ public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): ?EmailMessage
029⫶ {
030⫶ $body = '';
031⫶ foreach ($this->exceptions as $exception) {
032⫶ $body .= $exception->getMessage() . '<br>';
033⫶ }
034⫶
035⫶ $email = NotificationEmail::asPublicEmail()
036⫶ ->to($recipient->getEmail())
037⫶ ->subject($this->getSubject())
038⫶ ->html($body);
039⫶
040⫶ return new EmailMessage($email);
041⫶ }
042⫶
043⫶ public function asSystemNotification(UserRecipientInterface $recipient, ?string $transport = null): ?SystemMessage
044⫶ {
045⫶ $errorCount = 'No error';
046⫶ if (count($this->exceptions) > 0) {
047⫶ $plural = count($this->exceptions) > 1 ? 's' : '';
048⫶ $errorCount = count($this->exceptions) . ' error' . $plural;
049⫶ }
050⫶ $message = new SystemMessage($recipient->getUser());
051⫶ $message->setContext([
052⫶ 'icon' => Command::SUCCESS === $this->exitCode ? 'check-circle' : 'discard-circle',
053⫶ 'subject' => $this->command->getName(),
054⫶ 'content' => $errorCount,
055⫶ ]);
056⫶
057⫶ return $message;
058⫶ }
059⫶}


code_samples/user_management/notifications/src/Notifications/ControllerFeedback.php


code_samples/user_management/notifications/src/Notifications/ControllerFeedback.php

docs/users/notification_channels.md@203:``` php
docs/users/notification_channels.md@204:[[= include_code('code_samples/user_management/notifications/src/Notifications/ControllerFeedback.php') =]]
docs/users/notification_channels.md@205:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Notifications;
004⫶
005⫶use Symfony\Component\Notifier\Notification\Notification;
006⫶
007⫶class ControllerFeedback extends Notification
008⫶{
009⫶}


code_samples/user_management/notifications/src/Notifier/Channel/LogChannel.php


code_samples/user_management/notifications/src/Notifier/Channel/LogChannel.php

docs/users/notification_channels.md@287:``` php
docs/users/notification_channels.md@288:[[= include_code('code_samples/user_management/notifications/src/Notifier/Channel/LogChannel.php') =]]
docs/users/notification_channels.md@289:```

001⫶<?php declare(strict_types=1);
002⫶
003⫶namespace App\Notifier\Channel;
004⫶
005⫶use Psr\Log\LoggerAwareInterface;
006⫶use Psr\Log\LoggerAwareTrait;
007⫶use Symfony\Component\Notifier\Channel\ChannelInterface;
008⫶use Symfony\Component\Notifier\Notification\Notification;
009⫶use Symfony\Component\Notifier\Recipient\RecipientInterface;
010⫶
011⫶class LogChannel implements ChannelInterface, LoggerAwareInterface
012⫶{
013⫶ use LoggerAwareTrait;
014⫶
015⫶ public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void
016⫶ {
017⫶ if (isset($this->logger)) {
018⫶ $this->logger->info($notification->getSubject(), [
019⫶ 'class' => $notification::class,
020⫶ 'importance' => $notification->getImportance(),
021⫶ 'content' => $notification->getContent(),
022⫶ ]);
023⫶ }
024⫶ }
025⫶
026⫶ public function supports(Notification $notification, RecipientInterface $recipient): bool
027⫶ {
028⫶ return true;
029⫶ }
030⫶}


code_samples/user_management/notifications/templates/themes/admin/notification-sender-controller.html.twig


code_samples/user_management/notifications/templates/themes/admin/notification-sender-controller.html.twig

docs/users/notification_channels.md@217:``` twig
docs/users/notification_channels.md@218:[[= include_code('code_samples/user_management/notifications/templates/themes/admin/notification-sender-controller.html.twig') =]]
docs/users/notification_channels.md@219:```

001⫶{% extends '@ibexadesign/ui/layout.html.twig' %}


code_samples/user_management/notifications/templates/themes/storefront/notification-sender-controller.html.twig


code_samples/user_management/notifications/templates/themes/storefront/notification-sender-controller.html.twig

docs/users/notification_channels.md@222:``` twig
docs/users/notification_channels.md@223:[[= include_code('code_samples/user_management/notifications/templates/themes/storefront/notification-sender-controller.html.twig') =]]
docs/users/notification_channels.md@224:```

001⫶{% extends '@ibexadesign/storefront/layout.html.twig' %}


code_samples/user_management/notifications/webpack.config.js


code_samples/user_management/notifications/webpack.config.js

docs/users/notification_channels.md@238:``` javascript
docs/users/notification_channels.md@239:[[= include_code('code_samples/user_management/notifications/webpack.config.js', 50) =]]
docs/users/notification_channels.md@240:```



Download colorized diff

@adriendupuis adriendupuis requested a review from mnocon May 6, 2026 14:32
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 6, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
12.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants