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
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace App\Module\Core\Domain\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use App\Module\Core\Infrastructure\ApiPlatform\State\NotificationProvider;
use App\Module\Core\Infrastructure\Doctrine\DoctrineNotificationRepository;
use App\Shared\Domain\Contract\UserInterface;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ApiResource(
operations: [
new GetCollection(
provider: NotificationProvider::class,
security: "is_granted('IS_AUTHENTICATED_FULLY')",
),
new Patch(
security: "is_granted('IS_AUTHENTICATED_FULLY') and object.getUser() == user",
),
],
normalizationContext: ['groups' => ['notification:read']],
denormalizationContext: ['groups' => ['notification:write']],
order: ['createdAt' => 'DESC'],
)]
#[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
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
#[Groups(['notification:read'])]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: UserInterface::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[Groups(['notification:read'])]
private ?UserInterface $user = null;
#[ORM\Column(length: 50)]
#[Groups(['notification:read'])]
private ?string $type = null;
#[ORM\Column(length: 255)]
#[Groups(['notification:read'])]
private ?string $title = null;
#[ORM\Column(type: Types::TEXT)]
#[Groups(['notification:read'])]
private ?string $message = null;
#[ORM\Column]
#[Groups(['notification:read', 'notification:write'])]
private bool $isRead = false;
#[ORM\Column]
#[Groups(['notification:read'])]
private ?DateTimeImmutable $createdAt = null;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?UserInterface
{
return $this->user;
}
public function setUser(?UserInterface $user): static
{
$this->user = $user;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): static
{
$this->type = $type;
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
public function getMessage(): ?string
{
return $this->message;
}
public function setMessage(string $message): static
{
$this->message = $message;
return $this;
}
public function isRead(): bool
{
return $this->isRead;
}
public function setIsRead(bool $isRead): static
{
$this->isRead = $isRead;
return $this;
}
public function getCreatedAt(): ?DateTimeImmutable
{
return $this->createdAt;
}
public function setCreatedAt(DateTimeImmutable $createdAt): static
{
$this->createdAt = $createdAt;
return $this;
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
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\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;
use function count;
/**
* @implements ProviderInterface<Notification>
*/
final readonly class NotificationProvider implements ProviderInterface
{
public function __construct(
private Security $security,
private DoctrineNotificationRepository $notificationRepository,
private Pagination $pagination,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object
{
$user = $this->security->getUser();
[$page, $offset, $limit] = $this->pagination->getPagination($operation, $context);
$queryBuilder = $this->notificationRepository
->createUserNotificationsQueryBuilder($user)
->setFirstResult($offset)
->setMaxResults($limit)
;
$doctrinePaginator = new DoctrinePaginator($queryBuilder);
return new TraversablePaginator(
new ArrayIterator(iterator_to_array($doctrinePaginator, false)),
$page,
$limit,
(float) count($doctrinePaginator),
);
}
}
@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace App\Module\Core\Infrastructure\Doctrine;
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;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @extends ServiceEntityRepository<Notification>
*/
class DoctrineNotificationRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Notification::class);
}
public function createUserNotificationsQueryBuilder(UserInterface $user): QueryBuilder
{
return $this->createQueryBuilder('n')
->where('n.user = :user')
->setParameter('user', $user)
->orderBy('n.createdAt', 'DESC')
;
}
public function countUnreadByUser(SharedUserInterface $user): int
{
return (int) $this->createQueryBuilder('n')
->select('COUNT(n.id)')
->where('n.user = :user')
->andWhere('n.isRead = false')
->setParameter('user', $user)
->getQuery()
->getSingleScalarResult()
;
}
public function markAllReadByUser(SharedUserInterface $user): int
{
return $this->createQueryBuilder('n')
->update()
->set('n.isRead', 'true')
->where('n.user = :user')
->andWhere('n.isRead = false')
->setParameter('user', $user)
->getQuery()
->execute()
;
}
}
@@ -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();
}
}