From 5445e99bfa1c9451f17db27c6ad20776f69acd40 Mon Sep 17 00:00:00 2001 From: Paula Date: Fri, 3 Jul 2026 12:32:23 +0200 Subject: [PATCH] fix: skip update logs for items whose identifier was cleared on removal Deleting a parent entity together with its LoggableChildInterface children dispatches an update log for the parent from the child's delete log. By then Doctrine has cleared the parent's generated identifier, so the current data tracker lookup ran with a null objectId, which Elasticsearch rejects with a 400. Skip the update when the item has no identifier: the parent's own delete log is already written and an edit log for a removed entity is meaningless. --- CHANGELOG.md | 1 + .../UpdateActivityLogHandler.php | 8 ++++ .../UpdateActivityLogHandlerTest.php | 44 +++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 tests/UnitTests/MessageHandler/UpdateActivityLogHandlerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4309824..f01ec3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `ElasticsearchService::getItemById()` always returned null because it compared the hit total against an integer while Elasticsearch returns an object +- Deleting an entity together with its `LoggableChildInterface` children crashed the update handler: the child's delete log triggers an update log for the parent, whose identifier Doctrine has already cleared; such updates are now skipped ## [1.2.0] - 2026-07-02 diff --git a/src/MessageHandler/UpdateActivityLogHandler.php b/src/MessageHandler/UpdateActivityLogHandler.php index f1bb030..ab5d02c 100644 --- a/src/MessageHandler/UpdateActivityLogHandler.php +++ b/src/MessageHandler/UpdateActivityLogHandler.php @@ -31,6 +31,14 @@ public function __invoke(UpdateActivityLogMessageInterface $message): void return; } + // the item was removed in the meantime (e.g. a LoggableChildInterface + // logging to a parent that was deleted in the same flush); Doctrine + // clears generated identifiers on removal, and an item without an + // identifier has no current data tracker to compare against + if (null === $updatedItem->getId()) { + return; + } + $currentDataTracker = $this->currentDataTrackerProvider->getCurrentDataTrackerByClassAndId($message->getClassName(), $updatedItem->getId()); if (!$currentDataTracker instanceof CurrentDataTrackerInterface) { diff --git a/tests/UnitTests/MessageHandler/UpdateActivityLogHandlerTest.php b/tests/UnitTests/MessageHandler/UpdateActivityLogHandlerTest.php new file mode 100644 index 0000000..151cea7 --- /dev/null +++ b/tests/UnitTests/MessageHandler/UpdateActivityLogHandlerTest.php @@ -0,0 +1,44 @@ +createMock(LoggableContextCollectionFactoryInterface::class); + $collectionFactory->method('create')->willReturn( + new LoggableContextCollection([$item::class => ['groups' => ['test_log']]]) + ); + + $currentDataTrackerProvider = $this->createMock(CurrentDataTrackerProviderInterface::class); + $currentDataTrackerProvider->expects(self::never())->method('getCurrentDataTrackerByClassAndId'); + + $activityLogProcessor = $this->createMock(ActivityLogProcessorInterface::class); + $activityLogProcessor->expects(self::never())->method('processUpdatedItem'); + + $handler = new UpdateActivityLogHandler( + $activityLogProcessor, + $currentDataTrackerProvider, + new LoggableContextFactory($collectionFactory) + ); + + $handler(new UpdateActivityLogMessage($item)); + } +}