From f1a9b42930d91d4f9b5681888e9b97127057512b Mon Sep 17 00:00:00 2001 From: Matthieu Date: Fri, 19 Jun 2026 16:25:03 +0200 Subject: [PATCH] feat(core) : move notification into core and expose notifier contract --- config/services.yaml | 2 ++ src/Controller/MarkAllReadController.php | 4 +-- .../NotificationUnreadCountController.php | 4 +-- .../TaskNotificationListener.php | 27 ++++---------- .../Core/Domain}/Entity/Notification.php | 8 ++--- .../State/NotificationProvider.php | 8 ++--- .../DoctrineNotificationRepository.php} | 6 ++-- src/Module/Core/Infrastructure/Notifier.php | 29 +++++++++++++++ .../TaskNotificationListenerTest.php | 6 ++-- tests/Functional/Module/Core/NotifierTest.php | 35 +++++++++++++++++++ 10 files changed, 91 insertions(+), 38 deletions(-) rename src/{ => Module/Core/Domain}/Entity/Notification.php (92%) rename src/{ => Module/Core/Infrastructure/ApiPlatform}/State/NotificationProvider.php (84%) rename src/{Repository/NotificationRepository.php => Module/Core/Infrastructure/Doctrine/DoctrineNotificationRepository.php} (90%) create mode 100644 src/Module/Core/Infrastructure/Notifier.php create mode 100644 tests/Functional/Module/Core/NotifierTest.php diff --git a/config/services.yaml b/config/services.yaml index 97ad4a6..2b1c163 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -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' diff --git a/src/Controller/MarkAllReadController.php b/src/Controller/MarkAllReadController.php index 453cbbb..9c076c9 100644 --- a/src/Controller/MarkAllReadController.php +++ b/src/Controller/MarkAllReadController.php @@ -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)] diff --git a/src/Controller/NotificationUnreadCountController.php b/src/Controller/NotificationUnreadCountController.php index 746299a..b26512f 100644 --- a/src/Controller/NotificationUnreadCountController.php +++ b/src/Controller/NotificationUnreadCountController.php @@ -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)] diff --git a/src/EventListener/TaskNotificationListener.php b/src/EventListener/TaskNotificationListener.php index 9e32ac8..e34b84d 100644 --- a/src/EventListener/TaskNotificationListener.php +++ b/src/EventListener/TaskNotificationListener.php @@ -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 */ 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; } /** diff --git a/src/Entity/Notification.php b/src/Module/Core/Domain/Entity/Notification.php similarity index 92% rename from src/Entity/Notification.php rename to src/Module/Core/Domain/Entity/Notification.php index 601c0a0..1eded34 100644 --- a/src/Entity/Notification.php +++ b/src/Module/Core/Domain/Entity/Notification.php @@ -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 diff --git a/src/State/NotificationProvider.php b/src/Module/Core/Infrastructure/ApiPlatform/State/NotificationProvider.php similarity index 84% rename from src/State/NotificationProvider.php rename to src/Module/Core/Infrastructure/ApiPlatform/State/NotificationProvider.php index ff872c9..371debd 100644 --- a/src/State/NotificationProvider.php +++ b/src/Module/Core/Infrastructure/ApiPlatform/State/NotificationProvider.php @@ -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, ) {} diff --git a/src/Repository/NotificationRepository.php b/src/Module/Core/Infrastructure/Doctrine/DoctrineNotificationRepository.php similarity index 90% rename from src/Repository/NotificationRepository.php rename to src/Module/Core/Infrastructure/Doctrine/DoctrineNotificationRepository.php index ada482b..22618d5 100644 --- a/src/Repository/NotificationRepository.php +++ b/src/Module/Core/Infrastructure/Doctrine/DoctrineNotificationRepository.php @@ -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 */ -class NotificationRepository extends ServiceEntityRepository +class DoctrineNotificationRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { diff --git a/src/Module/Core/Infrastructure/Notifier.php b/src/Module/Core/Infrastructure/Notifier.php new file mode 100644 index 0000000..8be13e6 --- /dev/null +++ b/src/Module/Core/Infrastructure/Notifier.php @@ -0,0 +1,29 @@ +setUser($user); + $notification->setType($type); + $notification->setTitle($title); + $notification->setMessage($message); + $notification->setCreatedAt(new DateTimeImmutable()); + + $this->em->persist($notification); + $this->em->flush(); + } +} diff --git a/tests/Functional/EventListener/TaskNotificationListenerTest.php b/tests/Functional/EventListener/TaskNotificationListenerTest.php index 814fdef..682c0f9 100644 --- a/tests/Functional/EventListener/TaskNotificationListenerTest.php +++ b/tests/Functional/EventListener/TaskNotificationListenerTest.php @@ -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([]); diff --git a/tests/Functional/Module/Core/NotifierTest.php b/tests/Functional/Module/Core/NotifierTest.php new file mode 100644 index 0000000..66a3bb7 --- /dev/null +++ b/tests/Functional/Module/Core/NotifierTest.php @@ -0,0 +1,35 @@ +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); + } +}