NotificationOrphanCleaner
in package
Detaches admin notifications whose target record no longer exists (deleted order, user, payment…) — the bell and the list would otherwise offer a link ending in 404.
IMPORTANT: notifications are not deleted when their target disappears. The orphan keeps its type and text, only its target
column id is set to NULL — the bell and the list then show it as plain text without a link.
ORM deletes detach right away (NotificationTargetDetachSubscriber); this cleaner
catches the rest (raw SQL deletes, DB-level ON DELETE CASCADE, history before the subscriber).
IMPORTANT: housekeeping, never a side effect of rendering (a GET that mutates data is triggered by crawlers). It runs only from:
- the explicit administration action "Detach orphaned" (Api operation
api/system/notification/clean-orphans, RBAC keycleanOrphans, POST signal), - the cron job
/cron/system/notification/clean-orphans(at most once a day).
Lives on the Api side so both callers share one implementation: the cron has no administrator identity, so a bridge/RBAC based cleaner could not run there.
Target existence is answered by the modules via NotificationTargetProbeInterface
(one id IN (…) read per type). A type no probe owns is left alone, and so is anything
whose check fails (fail-safe: rather a dead link than a wrongly detached notification).
Table of Contents
Constants
- CHECK_LIMIT : int = 500
- How many of the newest notifications that still point to a target one run checks.
Properties
- $em : EntityManagerInterface
- $notificationManager : NotificationManager
- $probes : array<string|int, mixed>
Methods
- __construct() : mixed
- detachOrphans() : int
- Checks the newest {@see CHECK_LIMIT} notifications with a target; returns the number detached.
- findOrphanIds() : array<int, int>
- Notification ids whose target is confirmed missing.
- findExistingIds() : array<int, int>|null
- loadNewest() : array<int, array{notificationId: int, typeKey: string, itemId: int}>
- Newest notifications that still point to a target, with their type key — scalar read, no hydration. Already detached rows (`id` NULL) are skipped, so they do not eat the limit.
Constants
CHECK_LIMIT
How many of the newest notifications that still point to a target one run checks.
private
int
CHECK_LIMIT
= 500
Properties
$em read-only
private
EntityManagerInterface
$em
$notificationManager read-only
private
NotificationManager
$notificationManager
$probes read-only
private
array<string|int, mixed>
$probes
Methods
__construct()
public
__construct(array<string|int, NotificationTargetProbeInterface> $probes, EntityManagerInterface $em, NotificationManager $notificationManager) : mixed
Parameters
- $probes : array<string|int, NotificationTargetProbeInterface>
- $em : EntityManagerInterface
- $notificationManager : NotificationManager
detachOrphans()
Checks the newest {@see CHECK_LIMIT} notifications with a target; returns the number detached.
public
detachOrphans() : int
Return values
intfindOrphanIds()
Notification ids whose target is confirmed missing.
public
findOrphanIds(array<int, array{notificationId: int, typeKey: string, itemId: int}> $notifications) : array<int, int>
Parameters
- $notifications : array<int, array{notificationId: int, typeKey: string, itemId: int}>
Return values
array<int, int>findExistingIds()
private
findExistingIds(string $typeKey, array<int, int> $ids) : array<int, int>|null
Parameters
- $typeKey : string
- $ids : array<int, int>
Return values
array<int, int>|null —null = no probe owns the type, or the check failed
loadNewest()
Newest notifications that still point to a target, with their type key — scalar read, no hydration. Already detached rows (`id` NULL) are skipped, so they do not eat the limit.
private
loadNewest() : array<int, array{notificationId: int, typeKey: string, itemId: int}>