feat(core) : move notification into core and expose notifier contract

This commit is contained in:
Matthieu
2026-06-19 16:25:03 +02:00
parent 0b4874e94d
commit f1a9b42930
10 changed files with 91 additions and 38 deletions
+2
View File
@@ -68,3 +68,5 @@ services:
App\Service\Share\FileSource: '@App\Service\Share\SmbFileSource'
App\Module\Core\Domain\Repository\UserRepositoryInterface: '@App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository'
App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier'
+2 -2
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Controller;
use App\Repository\NotificationRepository;
use App\Module\Core\Infrastructure\Doctrine\DoctrineNotificationRepository;
use App\Shared\Domain\Contract\UserInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class MarkAllReadController extends AbstractController
{
public function __construct(
private readonly NotificationRepository $notificationRepository,
private readonly DoctrineNotificationRepository $notificationRepository,
) {}
#[Route('/api/notifications/mark-all-read', name: 'notification_mark_all_read', methods: ['POST'], priority: 1)]
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Controller;
use App\Repository\NotificationRepository;
use App\Module\Core\Infrastructure\Doctrine\DoctrineNotificationRepository;
use App\Shared\Domain\Contract\UserInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
class NotificationUnreadCountController extends AbstractController
{
public function __construct(
private readonly NotificationRepository $notificationRepository,
private readonly DoctrineNotificationRepository $notificationRepository,
) {}
#[Route('/api/notifications/unread-count', name: 'notification_unread_count', methods: ['GET'], priority: 1)]
+7 -20
View File
@@ -4,10 +4,9 @@ declare(strict_types=1);
namespace App\EventListener;
use App\Entity\Notification;
use App\Entity\Task;
use App\Shared\Domain\Contract\NotifierInterface;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;
@@ -21,7 +20,10 @@ final class TaskNotificationListener
/** @var list<array{user: UserInterface, type: string, task: Task}> */
private array $pending = [];
public function __construct(private readonly Security $security) {}
public function __construct(
private readonly Security $security,
private readonly NotifierInterface $notifier,
) {}
public function onFlush(OnFlushEventArgs $args): void
{
@@ -84,25 +86,10 @@ final class TaskNotificationListener
$pending = $this->pending;
$this->pending = [];
$em = $args->getObjectManager();
foreach ($pending as $item) {
$em->persist($this->buildNotification($item['user'], $item['type'], $item['task']));
[$title, $message] = $this->render($item['type'], $item['task']);
$this->notifier->notify($item['user'], $item['type'], $title, $message);
}
$em->flush();
}
private function buildNotification(UserInterface $user, string $type, Task $task): Notification
{
[$title, $message] = $this->render($type, $task);
$notification = new Notification();
$notification->setUser($user);
$notification->setType($type);
$notification->setTitle($title);
$notification->setMessage($message);
$notification->setCreatedAt(new DateTimeImmutable());
return $notification;
}
/**
@@ -2,14 +2,14 @@
declare(strict_types=1);
namespace App\Entity;
namespace App\Module\Core\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use App\Repository\NotificationRepository;
use App\Module\Core\Infrastructure\ApiPlatform\State\NotificationProvider;
use App\Module\Core\Infrastructure\Doctrine\DoctrineNotificationRepository;
use App\Shared\Domain\Contract\UserInterface;
use App\State\NotificationProvider;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
@@ -29,7 +29,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
denormalizationContext: ['groups' => ['notification:write']],
order: ['createdAt' => 'DESC'],
)]
#[ORM\Entity(repositoryClass: NotificationRepository::class)]
#[ORM\Entity(repositoryClass: DoctrineNotificationRepository::class)]
#[ORM\Index(columns: ['user_id'], name: 'idx_notification_user')]
#[ORM\Index(columns: ['user_id', 'is_read'], name: 'idx_notification_user_read')]
class Notification
@@ -2,14 +2,14 @@
declare(strict_types=1);
namespace App\State;
namespace App\Module\Core\Infrastructure\ApiPlatform\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\Pagination\Pagination;
use ApiPlatform\State\Pagination\TraversablePaginator;
use ApiPlatform\State\ProviderInterface;
use App\Entity\Notification;
use App\Repository\NotificationRepository;
use App\Module\Core\Domain\Entity\Notification;
use App\Module\Core\Infrastructure\Doctrine\DoctrineNotificationRepository;
use ArrayIterator;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use Symfony\Bundle\SecurityBundle\Security;
@@ -23,7 +23,7 @@ final readonly class NotificationProvider implements ProviderInterface
{
public function __construct(
private Security $security,
private NotificationRepository $notificationRepository,
private DoctrineNotificationRepository $notificationRepository,
private Pagination $pagination,
) {}
@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\Repository;
namespace App\Module\Core\Infrastructure\Doctrine;
use App\Entity\Notification;
use App\Module\Core\Domain\Entity\Notification;
use App\Shared\Domain\Contract\UserInterface as SharedUserInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
/**
* @extends ServiceEntityRepository<Notification>
*/
class NotificationRepository extends ServiceEntityRepository
class DoctrineNotificationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Module\Core\Infrastructure;
use App\Module\Core\Domain\Entity\Notification;
use App\Shared\Domain\Contract\NotifierInterface;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
final readonly class Notifier implements NotifierInterface
{
public function __construct(private EntityManagerInterface $em) {}
public function notify(UserInterface $user, string $type, string $title, string $message): void
{
$notification = new Notification();
$notification->setUser($user);
$notification->setType($type);
$notification->setTitle($title);
$notification->setMessage($message);
$notification->setCreatedAt(new DateTimeImmutable());
$this->em->persist($notification);
$this->em->flush();
}
}
@@ -7,7 +7,7 @@ namespace App\Tests\Functional\EventListener;
use App\Entity\Project;
use App\Entity\Task;
use App\Module\Core\Domain\Entity\User;
use App\Repository\NotificationRepository;
use App\Module\Core\Infrastructure\Doctrine\DoctrineNotificationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
@@ -19,7 +19,7 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
class TaskNotificationListenerTest extends KernelTestCase
{
private EntityManagerInterface $em;
private NotificationRepository $notifications;
private DoctrineNotificationRepository $notifications;
private TokenStorageInterface $tokenStorage;
private Project $project;
private User $actor;
@@ -31,7 +31,7 @@ class TaskNotificationListenerTest extends KernelTestCase
self::bootKernel();
$c = self::getContainer();
$this->em = $c->get(EntityManagerInterface::class);
$this->notifications = $c->get(NotificationRepository::class);
$this->notifications = $c->get(DoctrineNotificationRepository::class);
$this->tokenStorage = $c->get(TokenStorageInterface::class);
$project = $this->em->getRepository(Project::class)->findOneBy([]);
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Tests\Functional\Module\Core;
use App\Module\Core\Domain\Entity\User;
use App\Shared\Domain\Contract\NotifierInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
*/
final class NotifierTest extends KernelTestCase
{
public function testNotifyPersistsANotificationForTheUser(): void
{
self::bootKernel();
$em = self::getContainer()->get(EntityManagerInterface::class);
$notifier = self::getContainer()->get(NotifierInterface::class);
$user = $em->getRepository(User::class)->findOneBy(['username' => 'alice']);
self::assertNotNull($user);
$title = 'Titre-'.uniqid('', true);
$notifier->notify($user, 'task_assigned', $title, 'Message');
$count = (int) $em->createQuery(
'SELECT COUNT(n.id) FROM App\Module\Core\Domain\Entity\Notification n WHERE n.user = :u AND n.title = :t'
)->setParameter('u', $user)->setParameter('t', $title)->getSingleScalarResult();
self::assertSame(1, $count);
}
}