-
Notifications
You must be signed in to change notification settings - Fork 26
feat: Compatible with product bundle #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,15 @@ | |
|
|
||
| namespace SwagMigrationAssistant\Profile\Shopware6\Converter; | ||
|
|
||
| use Doctrine\DBAL\Connection; | ||
| use Shopware\Core\Content\Product\ProductDefinition; | ||
| use Shopware\Core\Defaults; | ||
| use Shopware\Core\Framework\Log\Package; | ||
| use SwagMigrationAssistant\Migration\Converter\ConvertStruct; | ||
| use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; | ||
| use SwagMigrationAssistant\Migration\Logging\LoggingServiceInterface; | ||
| use SwagMigrationAssistant\Migration\Mapping\MappingServiceInterface; | ||
| use SwagMigrationAssistant\Migration\Media\MediaFileServiceInterface; | ||
| use SwagMigrationAssistant\Migration\MigrationContextInterface; | ||
| use SwagMigrationAssistant\Profile\Shopware6\DataSelection\DataSet\ProductDataSet; | ||
| use SwagMigrationAssistant\Profile\Shopware6\Shopware6MajorProfile; | ||
|
|
@@ -20,6 +25,17 @@ class ProductConverter extends ShopwareMediaConverter | |
| { | ||
| private ?string $sourceDefaultCurrencyUuid; | ||
|
|
||
| private ?bool $hasTypeColumn = null; | ||
|
|
||
| public function __construct( | ||
| MappingServiceInterface $mappingService, | ||
| LoggingServiceInterface $loggingService, | ||
| MediaFileServiceInterface $mediaFileService, | ||
| private readonly Connection $connection, | ||
| ) { | ||
| parent::__construct($mappingService, $loggingService, $mediaFileService); | ||
| } | ||
|
|
||
| public function supports(MigrationContextInterface $migrationContext): bool | ||
| { | ||
| return $migrationContext->getProfile()->getName() === Shopware6MajorProfile::PROFILE_NAME | ||
|
|
@@ -58,6 +74,16 @@ protected function convertData(array $data): ConvertStruct | |
| { | ||
| $converted = $data; | ||
|
|
||
| // Check if product already exists (was previously migrated) before creating mapping | ||
| // This is needed because the 'type' field has Immutable flag and can only be set on create | ||
| $existingMapping = $this->mappingService->getMapping( | ||
| $this->connectionId, | ||
| DefaultEntities::PRODUCT, | ||
| $data['id'], | ||
| $this->context | ||
| ); | ||
| $isUpdate = $existingMapping !== null; | ||
|
Comment on lines
+77
to
+85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At first look, this is a clever attempt to determine if this entity will run into an Yes on the first migration run this will tell you if the product "was previously attempted to migrate" and thus give the the wanted result. But if the migration for this entity somehow fails:
it would mean the next migration run (retry) would still consider this entity as an "update", because the mapping already exists, which produces the wrong outcome in this case. We have to think further about this, to not introduce yet another N+1 Query performance issue 😬 |
||
|
|
||
| $this->mainMapping = $this->getOrCreateMappingMainCompleteFacade( | ||
| DefaultEntities::PRODUCT, | ||
| $data['id'], | ||
|
|
@@ -205,6 +231,8 @@ protected function convertData(array $data): ConvertStruct | |
| ); | ||
| } | ||
|
|
||
| $this->convertStatesToType($converted, $isUpdate); | ||
|
|
||
| return new ConvertStruct($converted, null, $this->mainMapping['id'] ?? null); | ||
| } | ||
|
|
||
|
|
@@ -235,4 +263,46 @@ private function checkDefaultCurrency(array &$source, string $key): void | |
| $source[$key][] = $defaultPrice; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param array<string, mixed> $converted | ||
| */ | ||
| private function convertStatesToType(array &$converted, bool $isUpdate): void | ||
| { | ||
| if (!$this->hasTypeColumn()) { | ||
| return; | ||
| } | ||
|
|
||
| if ($isUpdate) { | ||
| unset($converted['type']); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| if (isset($converted['type'])) { | ||
| return; | ||
| } | ||
|
|
||
| if (isset($converted['states']) && \is_array($converted['states'])) { | ||
| $converted['type'] = \in_array('is-download', $converted['states'], true) | ||
| ? ProductDefinition::TYPE_DIGITAL | ||
| : ProductDefinition::TYPE_PHYSICAL; | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| $converted['type'] = ProductDefinition::TYPE_PHYSICAL; | ||
| } | ||
|
|
||
| private function hasTypeColumn(): bool | ||
| { | ||
| if ($this->hasTypeColumn !== null) { | ||
| return $this->hasTypeColumn; | ||
| } | ||
|
|
||
| $columns = $this->connection->createSchemaManager()->listTableColumns('product'); | ||
| $this->hasTypeColumn = isset($columns['type']); | ||
|
|
||
| return $this->hasTypeColumn; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,4 +56,5 @@ | |
| 'id' => 'd7e9ceac19a948abad07667419424b13', | ||
| ], | ||
| ], | ||
| 'type' => 'physical', | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,4 +22,5 @@ | |
| 'id' => 'bfaf0c7366e6454fb7516ab47435b01a', | ||
| ], | ||
| ], | ||
| 'type' => 'physical', | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,4 +55,5 @@ | |
| 'id' => 'd7e9ceac19a948abad07667419424b13', | ||
| ], | ||
| ], | ||
| 'type' => 'physical', | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,4 +57,5 @@ | |
| 'id' => 'd7e9ceac19a948abad07667419424b13', | ||
| ], | ||
| ], | ||
| 'type' => 'physical', | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -209,4 +209,5 @@ | |
| 'id' => 'd7e9ceac19a948abad07667419424b13', | ||
| ], | ||
| ], | ||
| 'type' => 'physical', | ||
| ]; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for not raising this concern earlier:
Do you really think it's a good idea to have such a ImmutableFlag ? (Was introduced in this PR )
Because I already see the extra work it creates for us here, every entity that uses that flag needs an extra special treatment in the migration and if someone adds that flag to other fields in the future (or introduces such fields), we will run into issues again.
It basically makes "upserts" (insert / update) really difficult to do for such entities, because you want to create them if they don't exists yet, or update them otherwise, so you need the field to be defined but also not defined at the same time... 🤯
I also can't imagine that it's nice to work with such an API, e.g. imagine:
I think this was possible in the past (correct me if I'm wrong), but now you need to remove certain fields from your payload to send the update request, which is really annoying.
Of course in our administration codebase this is a non-issue, because there we use some extra logic / magic to build a changeset diff and only send that back to the server, but do you really expect every other API client to do the same or treat it differently like we do here in the migration? 😬
I would suggest either:
Immutablefield stays the same (is unchanged), as it should be in these cases like here in the "upsert" case for migration. Otherwise it would be a valid error if the migration tries to change such a valueI'm open for other suggestions as well, but I think we should have a better answer for the "upsert" case than just deal with it by throwing extra code at it. The Migration-Assistant makes heave use of "upsert" and basically writes all it's data that way:
SwagMigrationAssistant/src/Migration/Writer/AbstractWriter.php
Line 60 in 5a9afd8
cc: @patzick @keulinho what's your opinion on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That do makes sense, I was also thinking about the update should allow the payload with immutable key if the value doesn't change, but at the query time, we haven't aware of the current value in the database, fetching it before wouldn't be ideal, I will have a look further at this problem and your suggestion and provide an improvement for the flag.
For an external API consumer, I think ideally they don't want to send the whole entities; a subset of what changes is more practical and lightweight. But there might be cases where they want to resend the entire payload and catch the immutable error, as in your mentioned scenario.
Another simple option is to bypass the immutable check for system context scope; it will work for examples like MIG, but not if they update the entities via the admin api.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be possible, I added a draft PR here: shopware/shopware#14422