Skip to content

Commit

Permalink
IBX-8736: Allowed to sent user notification via symfony/notifier
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwojs committed Nov 9, 2024
1 parent 8e90d28 commit 540f66c
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
imports:
- { resource: services/channel.yaml }
- { resource: services/mappers.yaml }
- { resource: services/papi.yaml }
- { resource: services/subscription_resolver.yaml }
11 changes: 11 additions & 0 deletions src/bundle/Resources/config/services/channel.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

Ibexa\Notifications\Channel\SystemNotificationChannel:
tags:
- name: notifier.channel
channel: ibexa

98 changes: 98 additions & 0 deletions src/contracts/SystemNotification/SystemMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Notifications\SystemNotification;

use Ibexa\Contracts\Core\Repository\Values\User\UserReference;
use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface;
use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\MessageOptionsInterface;
use Symfony\Component\Notifier\Notification\Notification;

final class SystemMessage implements MessageInterface
{
private ?string $transport = null;

private string $subject;

private UserReference $user;

/** @var array<string, mixed> */
private array $context;

/**
* @param array<string, mixed> $context
*/
public function __construct(UserReference $user, string $subject, array $context = [])
{
$this->user = $user;
$this->subject = $subject;
$this->context = $context;
}

public function getUser(): UserReference
{
return $this->user;
}

public function setUser(UserReference $user): void
{
$this->user = $user;
}

public function getRecipientId(): ?string
{
return (string) $this->user->getUserId();
}

public function getSubject(): string
{
return $this->subject;
}

public function setSubject(string $subject): void
{
$this->subject = $subject;
}

public function getTransport(): ?string
{
return $this->transport;
}

public function setTransport(?string $transport): void
{
$this->transport = $transport;
}

public function getOptions(): ?MessageOptionsInterface
{
return null;
}

/**
* @return array<string, mixed>
*/
public function getContext(): array
{
return $this->context;
}

/**
* @param array<string, mixed> $context
*/
public function setContext(array $context): void
{
$this->context = $context;
}

public static function fromNotification(Notification $notification, UserRecipientInterface $recipient): self
{
return new self($recipient->getUser(), $notification->getSubject());
}
}
19 changes: 19 additions & 0 deletions src/contracts/SystemNotification/SystemNotificationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Notifications\SystemNotification;

use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface;

interface SystemNotificationInterface
{
public function asSystemNotification(
UserRecipientInterface $recipient,
?string $transport = null
): ?SystemMessage;
}
8 changes: 7 additions & 1 deletion src/contracts/Value/Recipent/UserRecipient.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
namespace Ibexa\Contracts\Notifications\Value\Recipent;

use Ibexa\Contracts\Core\Repository\Values\User\User;
use Ibexa\Contracts\Core\Repository\Values\User\UserReference;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;

final class UserRecipient implements EmailRecipientInterface
final class UserRecipient implements EmailRecipientInterface, UserRecipientInterface
{
private User $user;

Expand All @@ -24,4 +25,9 @@ public function getEmail(): string
{
return $this->user->email;
}

public function getUser(): UserReference
{
return $this->user;
}
}
17 changes: 17 additions & 0 deletions src/contracts/Value/Recipent/UserRecipientInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\Notifications\Value\Recipent;

use Ibexa\Contracts\Core\Repository\Values\User\UserReference;
use Symfony\Component\Notifier\Recipient\RecipientInterface;

interface UserRecipientInterface extends RecipientInterface
{
public function getUser(): UserReference;
}
63 changes: 63 additions & 0 deletions src/lib/Channel/SystemNotificationChannel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Notifications\Channel;

use Ibexa\Contracts\Core\Repository\NotificationService;
use Ibexa\Contracts\Core\Repository\Repository;
use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct;
use Ibexa\Contracts\Notifications\SystemNotification\SystemNotificationInterface;
use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface;
use Symfony\Component\Notifier\Channel\ChannelInterface;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\RecipientInterface;
use Throwable;

final class SystemNotificationChannel implements ChannelInterface
{
private Repository $repository;

private NotificationService $notificationService;

public function __construct(Repository $repository, NotificationService $notificationService)
{
$this->repository = $repository;
$this->notificationService = $notificationService;
}

/**
* @param \Symfony\Component\Notifier\Notification\Notification&\Ibexa\Contracts\Notifications\SystemNotification\SystemNotificationInterface $notification
* @param \Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface $recipient
*/
public function notify(Notification $notification, RecipientInterface $recipient, ?string $transportName = null): void
{
$message = $notification->asSystemNotification($recipient, $transportName);
if ($message === null) {
return;
}

$createStruct = new CreateStruct();
$createStruct->ownerId = $message->getUser()->getUserId();
$createStruct->type = $message->getSubject();
$createStruct->data = $message->getContext();

$this->repository->beginTransaction();
try {
$this->notificationService->createNotification($createStruct);
$this->repository->commit();
} catch (Throwable $e) {
$this->repository->rollback();
throw $e;
}
}

public function supports(Notification $notification, RecipientInterface $recipient): bool
{
return $notification instanceof SystemNotificationInterface && $recipient instanceof UserRecipientInterface;
}
}
139 changes: 139 additions & 0 deletions tests/lib/Channel/SystemNotificationChannelTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Tests\Notifications\Channel;

use Ibexa\Contracts\Core\Repository\NotificationService;
use Ibexa\Contracts\Core\Repository\Repository;
use Ibexa\Contracts\Core\Repository\Values\Notification\CreateStruct;
use Ibexa\Contracts\Core\Repository\Values\Notification\Notification as SystemNotification;
use Ibexa\Contracts\Core\Repository\Values\User\UserReference;
use Ibexa\Contracts\Notifications\SystemNotification\SystemMessage;
use Ibexa\Contracts\Notifications\SystemNotification\SystemNotificationInterface;
use Ibexa\Contracts\Notifications\Value\Recipent\UserRecipientInterface;
use Ibexa\Notifications\Channel\SystemNotificationChannel;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\RecipientInterface;

final class SystemNotificationChannelTest extends TestCase
{
private const EXAMPLE_USER_ID = 12;

/** @var \Ibexa\Contracts\Core\Repository\Repository&\PHPUnit\Framework\MockObject\MockObject */
private Repository $repository;

/** @var \Ibexa\Contracts\Core\Repository\NotificationService&\PHPUnit\Framework\MockObject\MockObject */
private NotificationService $notificationService;

private SystemNotificationChannel $channel;

protected function setUp(): void
{
$this->repository = $this->createMock(Repository::class);
$this->notificationService = $this->createMock(NotificationService::class);

$this->channel = new SystemNotificationChannel($this->repository, $this->notificationService);
}

/**
* @dataProvider dataProviderForSupports
*/
public function testSupports(Notification $notification, RecipientInterface $recipient, bool $expectedResult): void
{
self::assertEquals($expectedResult, $this->channel->supports($notification, $recipient));
}

/**
* @return iterable<string, array{\Symfony\Component\Notifier\Notification\Notification, \Symfony\Component\Notifier\Recipient\RecipientInterface, bool}>
*/
public function dataProviderForSupports(): iterable
{
yield 'supported' => [
$this->createSupportedNotification(),
$this->createSupportedRecipient(),
true,
];

yield 'unsupported recipient' => [
$this->createSupportedNotification(),
$this->createMock(RecipientInterface::class),
false,
];

yield 'unsupported notification' => [
$this->createMock(Notification::class),
$this->createSupportedRecipient(),
false,
];
}

public function testNotify(): void
{
$expectedCreateStruct = new CreateStruct();
$expectedCreateStruct->ownerId = self::EXAMPLE_USER_ID;
$expectedCreateStruct->data = ['foo' => 'bar'];
$expectedCreateStruct->type = 'example';

$this->notificationService
->expects(self::once())
->method('createNotification')
->willReturnCallback(function (CreateStruct $createStruct) use ($expectedCreateStruct): SystemNotification {
$this->assertEquals($expectedCreateStruct, $createStruct);

return new SystemNotification();
});

$user = $this->createMock(UserReference::class);
$user->method('getUserId')->willReturn(self::EXAMPLE_USER_ID);

$this->channel->notify(
$this->createSupportedNotification(
new SystemMessage($user, 'example', ['foo' => 'bar'])
),
$this->createSupportedRecipient(self::EXAMPLE_USER_ID)
);
}

/**
* @return \Symfony\Component\Notifier\Notification\Notification&\Ibexa\Contracts\Notifications\SystemNotification\SystemNotificationInterface
*/
private function createSupportedNotification(?SystemMessage $message = null): Notification
{
return new class($message) extends Notification implements SystemNotificationInterface {
private ?SystemMessage $message;

public function __construct(?SystemMessage $message)
{
parent::__construct('example');

$this->message = $message;
}

public function asSystemNotification(
UserRecipientInterface $recipient,
?string $transport = null
): ?SystemMessage {
return $this->message;
}
};
}

private function createSupportedRecipient(?int $userId = null): UserRecipientInterface
{
$recipient = $this->createMock(UserRecipientInterface::class);
if ($userId !== null) {
$user = $this->createMock(UserReference::class);
$user->method('getUserId')->willReturn($userId);

$recipient->method('getUser')->willReturn($user);
}

return $recipient;
}
}

0 comments on commit 540f66c

Please sign in to comment.