[SonataImportBundle] Skip-cell marker to preserve existing values on partial imports#339
Merged
Conversation
added 4 commits
May 1, 2026 11:08
…lues on partial imports Add a configurable sentinel value (default "_SKIP_") that, when present in a CSV cell, makes Importer::assignValue() short-circuit so the existing value on the entity is preserved for that (row, column) pair. The check runs before any type coercion (date, etc.) so the marker is honoured on all column types — including dates, booleans and translation columns — and applies uniformly to every ColumnExtractorInterface implementation without each one having to reimplement it. The marker is exposed as draw_sonata_import.skip_value (cannot be empty) and surfaced in the Sonata Admin file-upload help text so content editors can see how to opt out of overwriting a column. Empty cells are not treated as skip — they still clear the field, which preserves the prior "set to empty string" behaviour. The marker only makes sense on update; on insertWhenNotFound=true the field simply stays at its default value. Refs DEV-3653 (downstream pnp-api ticket).
ImporterTest covers:
- default and custom skip markers via getSkipValue() / isSkipValue()
- assignValue() bypasses extractors and date coercion when the marker
is present
- assignValue() runs extractors normally for regular values
- empty string is not treated as skip (regression)
- non-exact matches (whitespace, case, padding, integer 0/-1) are
rejected so the marker stays unambiguous
ConfigurationTest covers the new draw_sonata_import.skip_value node:
default value, custom value, and the cannot-be-empty constraint.
Adds a 'Preserving existing values on partial imports' section that explains the use case (partial-locale CSV updates), shows the YAML configuration of skip_value, and clarifies that empty cells still clear the field while the marker is the explicit "keep existing value" signal.
- Rename data provider to match test method name convention (provideIsSkipValueRejectsNonExactMatchesCases) and place it directly after the test method. - Return iterable instead of array from the data provider. - Use static::createStub() rather than $this->createStub() to match the Draw/class_static_call rule.
PHPStan level 5 cannot see properties declared on an anonymous class when accessed through the BaseColumnExtractor return type. Promote the call-tracking double to a named fixture (CallTrackingColumnExtractor) with explicit $callCount and $lastValue properties, instantiated directly in the tests so PHPStan resolves the concrete type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Sonata import (
/admin/draw/sonataimport/import/create) writes every mapped CSV cell to the entity. When a content editor wants to update only one locale of a translatable field (or one of several scalar columns), they currently have to repeat every other locale/column — otherwise the importer overwrites the stored value with whatever is in the cell (often a placeholder liken/aor an empty string).This PR adds a per-cell skip sentinel: when a cell equals the configured marker (default
_SKIP_), assignment for that(row, column)pair is short-circuited and the existing value on the entity is preserved.For row
id=42, the English and French titles are updated and the Polish title is left exactly as it is in the database.