1
0

refactor(notifications): remove factory indirection, interface, and listener duplication

Remove NotificationRepositoryFactory, NotificationServicesFactory, and
NotificationRepositoryInterface — all single-consumer abstractions with
no polymorphism benefit. Simplify container registrar to wire services
directly. Extract shared listener logic (sanitizeTenantIds,
resolveDisplayName, resolveUuid) into NotificationListenerTrait,
eliminating duplication across 4 listeners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 12:01:11 +01:00
parent 6ea3c2b88c
commit e746a2e874
13 changed files with 83 additions and 167 deletions

View File

@@ -0,0 +1,56 @@
<?php
namespace MintyPHP\Module\Notifications\Listeners;
trait NotificationListenerTrait
{
/**
* Resolve display name from payload with optional fallback to user record.
*
* Covers three variants:
* - payload['display_name'] is present → use it
* - fallback to user record (display_name, then first+last)
* - last resort: '#' + user ID
*/
protected function resolveDisplayName(array $payload, ?array $user, int $userId): string
{
$displayName = trim((string) ($payload['display_name'] ?? ''));
if ($displayName === '' && is_array($user)) {
$displayName = trim((string) ($user['display_name'] ?? ''));
if ($displayName === '') {
$displayName = trim((string) (($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')));
}
}
if ($displayName === '') {
$displayName = '#' . $userId;
}
return $displayName;
}
/**
* Resolve UUID from payload with optional fallback to user record.
*/
protected function resolveUuid(array $payload, ?array $user): string
{
$uuid = trim((string) ($payload['uuid'] ?? ''));
if ($uuid === '' && is_array($user)) {
$uuid = trim((string) ($user['uuid'] ?? ''));
}
return $uuid;
}
/**
* @return list<int>
*/
protected function sanitizeTenantIds(mixed $tenantIds): array
{
if (!is_array($tenantIds)) {
return [];
}
$ids = array_values(array_unique(array_map('intval', $tenantIds)));
return array_values(array_filter($ids, static fn (int $tenantId): bool => $tenantId > 0));
}
}

View File

@@ -11,6 +11,8 @@ use MintyPHP\Service\Access\PermissionService;
final class UserActivatedNotificationListener implements EventListener
{
use NotificationListenerTrait;
public function __construct(
private readonly NotificationService $notificationService,
private readonly UserReadRepositoryInterface $userReadRepository,
@@ -26,21 +28,8 @@ final class UserActivatedNotificationListener implements EventListener
}
$user = $this->userReadRepository->find($userId);
$displayName = trim((string) ($payload['display_name'] ?? ''));
if ($displayName === '' && is_array($user)) {
$displayName = trim((string) ($user['display_name'] ?? ''));
if ($displayName === '') {
$displayName = trim((string) (($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')));
}
}
if ($displayName === '') {
$displayName = '#' . $userId;
}
$uuid = trim((string) ($payload['uuid'] ?? ''));
if ($uuid === '' && is_array($user)) {
$uuid = trim((string) ($user['uuid'] ?? ''));
}
$displayName = $this->resolveDisplayName($payload, $user, $userId);
$uuid = $this->resolveUuid($payload, $user);
$tenantIds = $this->sanitizeTenantIds($payload['tenant_ids'] ?? []);
if ($tenantIds === []) {
@@ -79,17 +68,4 @@ final class UserActivatedNotificationListener implements EventListener
);
}
}
/**
* @return list<int>
*/
private function sanitizeTenantIds(mixed $tenantIds): array
{
if (!is_array($tenantIds)) {
return [];
}
$ids = array_values(array_unique(array_map('intval', $tenantIds)));
return array_values(array_filter($ids, static fn (int $tenantId): bool => $tenantId > 0));
}
}

View File

@@ -10,6 +10,8 @@ use MintyPHP\Service\Access\PermissionService;
final class UserAssignmentChangedNotificationListener implements EventListener
{
use NotificationListenerTrait;
public function __construct(
private readonly NotificationService $notificationService,
private readonly UserReadRepositoryInterface $userReadRepository
@@ -25,21 +27,8 @@ final class UserAssignmentChangedNotificationListener implements EventListener
$actorUserId = isset($payload['actor_user_id']) ? (int) $payload['actor_user_id'] : null;
$user = $this->userReadRepository->find($userId);
$displayName = trim((string) ($payload['display_name'] ?? ''));
if ($displayName === '' && is_array($user)) {
$displayName = trim((string) ($user['display_name'] ?? ''));
if ($displayName === '') {
$displayName = trim((string) (($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')));
}
}
if ($displayName === '') {
$displayName = '#' . $userId;
}
$uuid = trim((string) ($payload['uuid'] ?? ''));
if ($uuid === '' && is_array($user)) {
$uuid = trim((string) ($user['uuid'] ?? ''));
}
$displayName = $this->resolveDisplayName($payload, $user, $userId);
$uuid = $this->resolveUuid($payload, $user);
$tenantIds = $this->sanitizeTenantIds($payload['tenant_ids'] ?? []);
$changes = is_array($payload['changes'] ?? null) ? $payload['changes'] : [];
@@ -87,17 +76,4 @@ final class UserAssignmentChangedNotificationListener implements EventListener
);
}
}
/**
* @return list<int>
*/
private function sanitizeTenantIds(mixed $tenantIds): array
{
if (!is_array($tenantIds)) {
return [];
}
$ids = array_values(array_unique(array_map('intval', $tenantIds)));
return array_values(array_filter($ids, static fn (int $tenantId): bool => $tenantId > 0));
}
}

View File

@@ -11,6 +11,8 @@ use MintyPHP\Service\Access\PermissionService;
final class UserCreatedNotificationListener implements EventListener
{
use NotificationListenerTrait;
public function __construct(
private readonly NotificationService $notificationService,
private readonly UserReadRepositoryInterface $userReadRepository,
@@ -33,17 +35,13 @@ final class UserCreatedNotificationListener implements EventListener
return;
}
$displayName = trim((string) ($user['display_name'] ?? ''));
if ($displayName === '') {
$displayName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
}
$displayName = $this->resolveDisplayName($payload, $user, $userId);
$tenantIds = $this->userTenantRepository->listTenantIdsByUserId($userId);
if ($tenantIds === []) {
return;
}
// Only notify users who have the users.create permission (admins)
$adminUserIds = $this->userReadRepository->listPrivilegedUserIdsByPermissionKeys(
[PermissionService::USERS_CREATE]
);

View File

@@ -11,6 +11,8 @@ use MintyPHP\Service\Access\PermissionService;
final class UserDeactivatedNotificationListener implements EventListener
{
use NotificationListenerTrait;
public function __construct(
private readonly NotificationService $notificationService,
private readonly UserReadRepositoryInterface $userReadRepository,
@@ -26,21 +28,8 @@ final class UserDeactivatedNotificationListener implements EventListener
}
$user = $this->userReadRepository->find($userId);
$displayName = trim((string) ($payload['display_name'] ?? ''));
if ($displayName === '' && is_array($user)) {
$displayName = trim((string) ($user['display_name'] ?? ''));
if ($displayName === '') {
$displayName = trim((string) (($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? '')));
}
}
if ($displayName === '') {
$displayName = '#' . $userId;
}
$uuid = trim((string) ($payload['uuid'] ?? ''));
if ($uuid === '' && is_array($user)) {
$uuid = trim((string) ($user['uuid'] ?? ''));
}
$displayName = $this->resolveDisplayName($payload, $user, $userId);
$uuid = $this->resolveUuid($payload, $user);
$tenantIds = $this->sanitizeTenantIds($payload['tenant_ids'] ?? []);
if ($tenantIds === []) {
@@ -79,17 +68,4 @@ final class UserDeactivatedNotificationListener implements EventListener
);
}
}
/**
* @return list<int>
*/
private function sanitizeTenantIds(mixed $tenantIds): array
{
if (!is_array($tenantIds)) {
return [];
}
$ids = array_values(array_unique(array_map('intval', $tenantIds)));
return array_values(array_filter($ids, static fn (int $tenantId): bool => $tenantId > 0));
}
}

View File

@@ -10,6 +10,8 @@ use MintyPHP\Service\Access\PermissionService;
final class UserDeletedNotificationListener implements EventListener
{
use NotificationListenerTrait;
public function __construct(
private readonly NotificationService $notificationService,
private readonly UserReadRepositoryInterface $userReadRepository

View File

@@ -10,9 +10,8 @@ use MintyPHP\Module\Notifications\Listeners\UserAssignmentChangedNotificationLis
use MintyPHP\Module\Notifications\Listeners\UserCreatedNotificationListener;
use MintyPHP\Module\Notifications\Listeners\UserDeactivatedNotificationListener;
use MintyPHP\Module\Notifications\Listeners\UserDeletedNotificationListener;
use MintyPHP\Module\Notifications\Service\NotificationRepositoryFactory;
use MintyPHP\Module\Notifications\Repository\NotificationRepository;
use MintyPHP\Module\Notifications\Service\NotificationService;
use MintyPHP\Module\Notifications\Service\NotificationServicesFactory;
use MintyPHP\Repository\Tenant\UserTenantRepository;
use MintyPHP\Repository\User\UserReadRepository;
use MintyPHP\Service\Access\PermissionService;
@@ -24,13 +23,11 @@ final class NotificationsContainerRegistrar implements ContainerRegistrar
$container->set(NotificationsAuthorizationPolicy::class, static fn (AppContainer $c): NotificationsAuthorizationPolicy => new NotificationsAuthorizationPolicy(
$c->get(PermissionService::class)
));
$container->set(NotificationRepositoryFactory::class, static fn (): NotificationRepositoryFactory => new NotificationRepositoryFactory());
$container->set(NotificationServicesFactory::class, static fn (AppContainer $c): NotificationServicesFactory => new NotificationServicesFactory(
$c->get(NotificationRepositoryFactory::class)
));
$container->set(NotificationRepository::class, static fn (): NotificationRepository => new NotificationRepository());
$container->set(NotificationService::class, static fn (AppContainer $c): NotificationService => $c->get(NotificationServicesFactory::class)->createNotificationService(
$container->set(NotificationService::class, static fn (AppContainer $c): NotificationService => new NotificationService(
$c->get(NotificationRepository::class),
$c->get(UserTenantRepository::class)
));

View File

@@ -5,7 +5,7 @@ namespace MintyPHP\Module\Notifications\Repository;
use MintyPHP\DB;
use MintyPHP\Repository\Support\RepositoryArrayHelper;
class NotificationRepository implements NotificationRepositoryInterface
class NotificationRepository
{
private const DEDUPE_WINDOW_SECONDS = 1800;

View File

@@ -1,26 +0,0 @@
<?php
namespace MintyPHP\Module\Notifications\Repository;
interface NotificationRepositoryInterface
{
/** @return int|false Insert ID on success, false on failure */
public function create(array $data): int|false;
/**
* @return list<array<string, mixed>>
*/
public function listByUser(int $userId, ?int $tenantId, int $limit, int $offset): array;
public function countUnreadByUser(int $userId, ?int $tenantId): int;
public function markRead(int $id, int $userId, ?int $tenantId): bool;
public function markAllReadByUser(int $userId, ?int $tenantId): int;
public function delete(int $id, int $userId, ?int $tenantId): bool;
public function deleteAllByUser(int $userId): int;
public function purgeReadOlderThanDays(int $days): int;
}

View File

@@ -1,16 +0,0 @@
<?php
namespace MintyPHP\Module\Notifications\Service;
use MintyPHP\Module\Notifications\Repository\NotificationRepository;
use MintyPHP\Module\Notifications\Repository\NotificationRepositoryInterface;
class NotificationRepositoryFactory
{
private ?NotificationRepository $notificationRepository = null;
public function createNotificationRepository(): NotificationRepositoryInterface
{
return $this->notificationRepository ??= new NotificationRepository();
}
}

View File

@@ -2,7 +2,7 @@
namespace MintyPHP\Module\Notifications\Service;
use MintyPHP\Module\Notifications\Repository\NotificationRepositoryInterface;
use MintyPHP\Module\Notifications\Repository\NotificationRepository;
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
class NotificationService
@@ -18,7 +18,7 @@ class NotificationService
];
public function __construct(
private readonly NotificationRepositoryInterface $notificationRepository,
private readonly NotificationRepository $notificationRepository,
private readonly UserTenantRepositoryInterface $userTenantRepository
) {
}

View File

@@ -1,23 +0,0 @@
<?php
namespace MintyPHP\Module\Notifications\Service;
use MintyPHP\Repository\Tenant\UserTenantRepositoryInterface;
class NotificationServicesFactory
{
private ?NotificationService $notificationService = null;
public function __construct(
private readonly NotificationRepositoryFactory $notificationRepositoryFactory
) {
}
public function createNotificationService(UserTenantRepositoryInterface $userTenantRepository): NotificationService
{
return $this->notificationService ??= new NotificationService(
$this->notificationRepositoryFactory->createNotificationRepository(),
$userTenantRepository
);
}
}