Skip to content

Conversation

@larskemper
Copy link
Member

@larskemper larskemper commented Jan 21, 2026

Overall refactor of the migration validation behavior. Should lead to much less needed fixes and more accurate / actually fixable errors.

sorry, for the big pr, but refactor was needed 😅. Reach out if I can explain anything and please give me your ideas for better validation!

Changes (file by file)

  • src/Controller/DataProviderController.php, src/Controller/HistoryController.php, src/Controller/PremappingController.php, src/Controller/StatusController.php:
    Introduced all common route definition constants to unify with platform

  • [Added] src/Controller/ErrorResolutionController.php:
    Added two new routes:

    • /error-resolution/validate: provides validation for user created fixes to make sure the fix matches original migration validation
    • /error-resolution/example-field-structure: in case we can not determine the field type in the administration this route provides an example field structure of json association fields, so the user can follow this structure to create a valid fix.
  • src/Migration/ErrorResolution/MigrationErrorResolutionService.php: added missing early return if no fixes could be loaded to avoid event spam and unnecessary code execution

  • [Added] src/Migration/ErrorResolution/MigrationFieldExampleGenerator.php: the generator used for the example field structure route. It takes in a field and tries to build a accurate example structure for the user

  • [!!!] src/Migration/ErrorResolution/MigrationFix.php: see the comment

  • src/Migration/Mapping/MappingService.php: removed the mapping lookup. We decided to not validate of a given association is actually existing and just validate the given data

  • src/Migration/Service/MediaFileProcessorService.php: add where clause to only fetch unprocessed media

  • src/Migration/Service/MigrationDataWriter.php: change to ->count() to return number of entities that are processed in the current batch, not the absolute total

  • [!!!] src/Migration/Validation/MigrationEntityValidationService.php:

    • Deleted association mapping lookup
    • New definition of required field: not system managed (createdAt, ...), actually required in db, no default value
    • Validated nested converted data
    • More naive way of validating association field, cause the before validation by serializer needed context that we actually can't provide in the migration. Now more like a structural validation of association data.
    • Move Exceptions to validation domain for better separation
  • [!!!] src/Migration/Validation/MigrationFieldValidationService.php:

    • same as entity validator: more naive validation, support of nested fields
    • splited from entity validator to provide standalone field validation for the error resolution controller
  • src/Migration/Validation/MigrationValidationResult.php: added log key generation to avoid multiple log for the same invalidation (avoid multiple fixes for same issue)

  • src/Profile/Shopware/Converter/OrderConverter.php: bug fix (custom field was set to null before, instead of just not adding it to the converted data)

  • src/Resources/app/administration/src/module/swag-migration/component/swag-migration-error-resolution/swag-migration-error-resolution-field/swag-migration-error-resolution-field-scalar/index.ts: added display of error messages that are provides by the validation route

  • src/Resources/app/administration/src/module/swag-migration/component/swag-migration-error-resolution/swag-migration-error-resolution-field/swag-migration-error-resolution-field/index.ts: added auto fetch of example field structure in case the admin could not determine a specific field type

  • src/Resources/app/administration/src/module/swag-migration/service/swag-migration-error-resolution.service.ts: support of nested field paths and overall simplification

@larskemper larskemper self-assigned this Jan 21, 2026
@larskemper larskemper changed the title feat: add validation of resolution values refactor: migration validation Jan 21, 2026
@larskemper larskemper force-pushed the feat/add-validation-of-resolution-values branch from 80b2502 to 534d4d1 Compare January 21, 2026 15:52
}

// key points to a list, apply to all items in the list
if (isset($data[$key]) && \is_array($data[$key]) && \array_is_list($data[$key])) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example case for this:
entity number_range with converted data like this:

[
    'id' => 'uuid'
    // ...
    'numberRangeSalesChannels' => [
        [
            // ...
             'typeId' => null // (invalid, has to be set)
        ],
        [
            'typeId' => null
        ]
    ]
]

with this solution the fix would be applied for all sub child of numberRangeSalesChannels.
To support individual fixes for sub childs we would have to rebuild quite a lot of the error resolution to a path syntax like parent.sub.[index].property which we currently are not supporting and would cause problems in the ui (because we group by fieldname).

What do you think? Leave it like this or maybe not support it at all for the moment (would mean these entities can not be migrated, like before anyway)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a realistic scenario with the number ranges?

Im fine with this solution but we should make it clear that this will happen to the user with a warning or tooltip

Copy link
Member Author

@larskemper larskemper Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it just feels not completely right, but it seemed quite common. The problem is that if we don't to this adjustment we ask the user to provide a fix that we actually are not supporting to fix. So we would need new extra logic to prevent them to appear "fixable"

@larskemper larskemper requested a review from a team January 22, 2026 08:33
@larskemper larskemper requested review from DennisGarding, MalteJanz, ennasus4sun, jozsefdamokos and vintagesucks and removed request for a team January 22, 2026 08:33
@larskemper larskemper marked this pull request as ready for review January 22, 2026 08:40
/**
* @param array<int|string, array<int|string, mixed>> $data
*
* @throws Exception
Copy link
Contributor

@DennisGarding DennisGarding Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@throws Exception can be removed. In the function is no throw

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchAllAssociative() inside of loadFixes() can throw

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but normaly annotate exceptions if its realy thrown in the mehtod. So fetchAllAssociative need the @throw tag

/**
* Loads fixes from the database and populates them in the context.
*
* @throws Exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

*/
public function apply(array &$item): void
{
/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep this I think

* @param array<string|int, mixed> $data
* @param array<int, string> $remainingPath
*/
private function applyToPath(array &$data, array $remainingPath, mixed $value): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to call the function param simply $path and set $remainingPath inside the function

/**
* Validates that all required fields are present in the converted data.
*
* @throws \Exception|Exception
Copy link
Member

@jozsefdamokos jozsefdamokos Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? Same question for all methods in this class 😉

Copy link
Member Author

@larskemper larskemper Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i mean technically definitely not, but documenting the most ide's will highlight the usage of the method "this can throw..." and that's not unrelevant for the validation, cause all thrown errors should be catched to create logs / fixes. But I can also live with removing them 😄

Copy link
Member

@vintagesucks vintagesucks Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, this line resulted in a "Why both \Exception and Exception?" question 😄

nevermind 😅

use Doctrine\DBAL\Exception;

* @param array<string, mixed>|null $convertedEntity
* @param array<string, mixed> $sourceData
*
* @throws \Exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

* Resolves a potentially nested field path (e.g., "prices.shippingMethodId") to its target.
* Traverses through association fields to find the final entity definition and field.
*
* @throws \Exception
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? Same question for the other methods where @throws is used

Copy link
Member

@vintagesucks vintagesucks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀🚀🚀

return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::VALIDATION_INVALID_ID,
'The id "{{ entityId }}" for entity "{{ entityName }}" is not a valid Uuid',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'The id "{{ entityId }}" for entity "{{ entityName }}" is not a valid Uuid',
'The id "{{ entityId }}" for entity "{{ entityName }}" is not a valid UUID',

}

public function getCode(): string
{
return 'SWAG_MIGRATION_VALIDATION_UNEXPECTED_FIELD';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snippets are still present

class MigrationEntityValidationService implements ResetInterface
{
/**
* System managed that managed by Shopware and should not be validated as required fields
Copy link
Member

@vintagesucks vintagesucks Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* System managed that managed by Shopware and should not be validated as required fields
* System managed fields are managed by Shopware and should not be validated as required fields

or

Suggested change
* System managed that managed by Shopware and should not be validated as required fields
* System managed fields that are managed by Shopware and should not be validated as required fields

return value;
} catch {
this.error = {
detail: this.$tc('swag-migration.index.error-resolution.errors.invalidJsonInput'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snippets are still present

: $field->getReferenceDefinition();

foreach ($value as $nestedEntityData) {
$this->validateNestedEntityData(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not use the same method validateFieldValues here? From the docblocks it seems to me that these two methods do a similar job.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some tests are missing for validating association structures

@jozsefdamokos
Copy link
Member

Looks good! Great job!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants