From 166a8eb760aeb5c9a08f7f5231b987f9fd297864 Mon Sep 17 00:00:00 2001 From: Lorenzo Nicoletti <1744861+ellenico77@users.noreply.github.com> Date: Mon, 11 May 2026 11:23:58 +0200 Subject: [PATCH 1/3] Improve: allow PreSaveEvent listeners to skip default object persistence Add a skip-save flag to PreSaveEvent so listeners can decide programmatically whether an imported row should be persisted. ImportProcessingService now checks this flag after dispatching PreSaveEvent and returns early when save is skipped. Default behavior remains unchanged unless explicitly requested by a listener. --- src/Event/DataObject/PreSaveEvent.php | 11 +++++++++++ src/Processing/ImportProcessingService.php | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/src/Event/DataObject/PreSaveEvent.php b/src/Event/DataObject/PreSaveEvent.php index 1f24ce51..e28d3cf1 100644 --- a/src/Event/DataObject/PreSaveEvent.php +++ b/src/Event/DataObject/PreSaveEvent.php @@ -17,4 +17,15 @@ */ final class PreSaveEvent extends AbstractDataObjectImportEvent { + protected $skipSave = false; + + public function shouldSkipSave(): bool + { + return $this->skipSave; + } + + public function setSkipSave(bool $skipSave): void + { + $this->skipSave = $skipSave; + } } diff --git a/src/Processing/ImportProcessingService.php b/src/Processing/ImportProcessingService.php index ad5ed671..8f4e8201 100644 --- a/src/Processing/ImportProcessingService.php +++ b/src/Processing/ImportProcessingService.php @@ -237,6 +237,10 @@ private function processElement( $event = new PreSaveEvent($configName, $importDataRow, $element); $this->eventDispatcher->dispatch($event); + if ($event->shouldSkipSave()) { + return; + } + $this->checkKey($element); $element ->setUserModification($userOwner) From 401c084c7780ee60d8e11427f43031ebe534999d Mon Sep 17 00:00:00 2001 From: kingjia90 Date: Thu, 17 Sep 2026 12:29:27 +0200 Subject: [PATCH 2/3] Document skip-save, log skipped elements and type the flag - add upgrade note for 2026.3.0 and document the new opt-out in the events docs - write an info log entry when a listener skips the save, so skipped rows stay traceable in the import log - declare $skipSave as a typed private property (the class is final) --- doc/01_Installation/01_Upgrade.md | 9 +++++++++ doc/06_Extending/02_Events.md | 15 ++++++++++++++- src/Event/DataObject/PreSaveEvent.php | 2 +- src/Processing/ImportProcessingService.php | 5 +++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/doc/01_Installation/01_Upgrade.md b/doc/01_Installation/01_Upgrade.md index e52bea93..a8c65edb 100644 --- a/doc/01_Installation/01_Upgrade.md +++ b/doc/01_Installation/01_Upgrade.md @@ -5,6 +5,15 @@ description: Breaking changes and migration steps per release. # Upgrade Notes +## Upgrade to 2026.3.0 + +### Skipping Element Persistence From a `PreSaveEvent` Listener + +- `DataObject\PreSaveEvent` now carries a skip flag: a listener can call `setSkipSave(true)` to stop the import + from persisting the current element. The element is not saved, `DataObject\PostSaveEvent` is not dispatched, and + the skip is written to the import log. Processing continues with the next record. +- The flag defaults to `false`, so imports without such a listener behave exactly as before. No migration needed. + ## Upgrade to 2026.2.6 ### Frontend Build Ships as a Packaged Archive diff --git a/doc/06_Extending/02_Events.md b/doc/06_Extending/02_Events.md index 6804dec7..aa0ce0ce 100644 --- a/doc/06_Extending/02_Events.md +++ b/doc/06_Extending/02_Events.md @@ -18,7 +18,9 @@ Listening for events customizes import behaviour without replacing any component | `PostPreparationEvent` | After an import was prepared and the queue items were created. | The three `DataObject` events share a base class exposing the import configuration name, the raw source record, and the -data object. `ProcessElementExceptionEvent` adds the thrown exception, the error message, and the mapping configuration +data object. `DataObject\PreSaveEvent` additionally lets a listener skip the persistence of the current element with +`setSkipSave(true)`: the element is not saved, no `DataObject\PostSaveEvent` is dispatched, the skip is written to the +import log, and the import continues with the next record. `ProcessElementExceptionEvent` adds the thrown exception, the error message, and the mapping configuration that failed, when the failure can be attributed to one. `PostPreparationEvent` exposes the configuration name, the execution type, and whether the source file was interpreted. @@ -50,6 +52,17 @@ final class ImportListener } ``` +Skip the persistence of a record the import should not write: + +```php +public function __invoke(PreSaveEvent $event): void +{ + if (($event->getRawData()['status'] ?? null) === 'draft') { + $event->setSkipSave(true); + } +} +``` + ## Studio API Events The configuration panel is a Pimcore Studio plugin. Before one of its endpoints returns, the bundle dispatches a diff --git a/src/Event/DataObject/PreSaveEvent.php b/src/Event/DataObject/PreSaveEvent.php index e28d3cf1..afb9487d 100644 --- a/src/Event/DataObject/PreSaveEvent.php +++ b/src/Event/DataObject/PreSaveEvent.php @@ -17,7 +17,7 @@ */ final class PreSaveEvent extends AbstractDataObjectImportEvent { - protected $skipSave = false; + private bool $skipSave = false; public function shouldSkipSave(): bool { diff --git a/src/Processing/ImportProcessingService.php b/src/Processing/ImportProcessingService.php index aff0e644..214ac537 100644 --- a/src/Processing/ImportProcessingService.php +++ b/src/Processing/ImportProcessingService.php @@ -242,6 +242,11 @@ private function processElement( $this->eventDispatcher->dispatch($event); if ($event->shouldSkipSave()) { + $this->logInfo($configName, 'Saving of element skipped by PreSaveEvent listener.', [ + 'component' => PimcoreDataImporterBundle::LOGGER_COMPONENT_PREFIX . $configName, + 'relatedObject' => $element + ]); + return; } From f1666def6595b9b78d4409af860d47bc628c71bd Mon Sep 17 00:00:00 2001 From: kingjia90 Date: Thu, 17 Sep 2026 12:36:29 +0200 Subject: [PATCH 3/3] Reload a skipped element so its unsaved changes cannot leak into a later row Pimcore's loaders hand out runtime-cached instances, so the mapping changes applied to a skipped element would otherwise be inherited - and saved - by a later row resolving the same element. --- doc/01_Installation/01_Upgrade.md | 2 ++ doc/06_Extending/02_Events.md | 4 +++- src/Processing/ImportProcessingService.php | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/doc/01_Installation/01_Upgrade.md b/doc/01_Installation/01_Upgrade.md index a8c65edb..c9a46cb5 100644 --- a/doc/01_Installation/01_Upgrade.md +++ b/doc/01_Installation/01_Upgrade.md @@ -12,6 +12,8 @@ description: Breaking changes and migration steps per release. - `DataObject\PreSaveEvent` now carries a skip flag: a listener can call `setSkipSave(true)` to stop the import from persisting the current element. The element is not saved, `DataObject\PostSaveEvent` is not dispatched, and the skip is written to the import log. Processing continues with the next record. +- A skipped element that already exists is reloaded afterwards, so the changes the mapping applied to it in + memory cannot leak into a later row that resolves the same element. - The flag defaults to `false`, so imports without such a listener behave exactly as before. No migration needed. ## Upgrade to 2026.2.6 diff --git a/doc/06_Extending/02_Events.md b/doc/06_Extending/02_Events.md index aa0ce0ce..2d67f4b3 100644 --- a/doc/06_Extending/02_Events.md +++ b/doc/06_Extending/02_Events.md @@ -20,7 +20,9 @@ Listening for events customizes import behaviour without replacing any component The three `DataObject` events share a base class exposing the import configuration name, the raw source record, and the data object. `DataObject\PreSaveEvent` additionally lets a listener skip the persistence of the current element with `setSkipSave(true)`: the element is not saved, no `DataObject\PostSaveEvent` is dispatched, the skip is written to the -import log, and the import continues with the next record. `ProcessElementExceptionEvent` adds the thrown exception, the error message, and the mapping configuration +import log, and the import continues with the next record. An existing element is reloaded after the skip, so the +changes the mapping applied to it in memory are discarded rather than carried over to a later record resolving the +same element. `ProcessElementExceptionEvent` adds the thrown exception, the error message, and the mapping configuration that failed, when the failure can be attributed to one. `PostPreparationEvent` exposes the configuration name, the execution type, and whether the source file was interpreted. diff --git a/src/Processing/ImportProcessingService.php b/src/Processing/ImportProcessingService.php index 214ac537..c538707d 100644 --- a/src/Processing/ImportProcessingService.php +++ b/src/Processing/ImportProcessingService.php @@ -30,6 +30,7 @@ use Pimcore\Bundle\DataImporterBundle\Resolver\ResolverFactory; use Pimcore\Bundle\DataImporterBundle\Settings\ConfigurationPreparationService; use Pimcore\Model\Element\ElementInterface; +use Pimcore\Model\Element\Service as ElementService; use Pimcore\Model\Tool\TmpStore; use Pimcore\Model\Version; use Psr\Log\LoggerAwareTrait; @@ -246,6 +247,7 @@ private function processElement( 'component' => PimcoreDataImporterBundle::LOGGER_COMPONENT_PREFIX . $configName, 'relatedObject' => $element ]); + $this->discardUnsavedChanges($element); return; } @@ -325,6 +327,22 @@ private function processElement( } } + /** + * A skipped element keeps the changes the mapping applied to it in memory. Pimcore hands out + * runtime-cached instances, so a later row resolving the same element would otherwise inherit - + * and save - the values of the skipped row. Reloading it registers a clean instance instead. + */ + private function discardUnsavedChanges(ElementInterface $element): void + { + $type = ElementService::getElementType($element); + + if ($type === null || !$element->getId()) { + return; + } + + ElementService::getElementById($type, $element->getId(), ['force' => true]); + } + /** * Process transformations for an element *