From bad2c7b882ef484e8b4fe466f70d2b7baf308fff Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 11:43:22 +0200 Subject: [PATCH 01/28] fix: include exception file and line in logs --- .../Log/Builder/MigrationLogBuilder.php | 38 ++++++++++++++++++- .../Processor/HttpDownloadServiceBase.php | 15 +++----- .../Processor/MediaProcessingProcessor.php | 6 +-- src/Migration/Run/RunService.php | 3 +- .../Service/MigrationDataConverter.php | 6 +-- .../Service/MigrationDataFetcher.php | 3 +- src/Migration/Service/MigrationDataWriter.php | 11 ++---- .../Subscriber/MessageQueueSubscriber.php | 6 +-- .../MigrationEntityValidationService.php | 6 +-- .../MainVariantRelationConverter.php | 5 ++- .../Shopware/Converter/ProductConverter.php | 3 +- .../Converter/TranslationConverter.php | 9 +---- .../Shopware/Media/LocalMediaProcessor.php | 3 +- .../Media/LocalOrderDocumentProcessor.php | 6 +-- .../Media/LocalProductDownloadProcessor.php | 3 +- .../HttpOrderDocumentGenerationService.php | 10 +++-- 16 files changed, 71 insertions(+), 62 deletions(-) diff --git a/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php b/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php index b54a0674e..2af57a11a 100644 --- a/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php +++ b/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php @@ -40,6 +40,7 @@ public function __construct( protected ?array $convertedData = null, protected ?string $exceptionMessage = null, protected ?array $exceptionTrace = null, + protected ?\Throwable $exception = null, ) { } @@ -100,6 +101,13 @@ public function withConvertedData(array $convertedData): self return $this; } + public function withException(\Throwable $exception): self + { + $this->exception = $exception; + + return $this; + } + public function withExceptionMessage(string $exceptionMessage): self { $this->exceptionMessage = $exceptionMessage; @@ -138,8 +146,8 @@ public function build(string $logClass): AbstractMigrationLogEntry $this->fieldSourcePath, $this->sourceData, $this->convertedData, - $this->exceptionMessage, - $this->exceptionTrace, + $this->getExceptionMessage(), + $this->getExceptionTrace(), ); } @@ -155,4 +163,30 @@ private function getRevisedId(?string $id): ?string return null; } + + private function getExceptionMessage(): ?string + { + if ($this->exceptionMessage !== null) { + return $this->exceptionMessage; + } + + if ($this->exception !== null) { + return $this->exception->getMessage() . ' in ' . $this->exception->getFile() . ':' . $this->exception->getLine(); + } + + return null; + } + + private function getExceptionTrace(): ?array + { + if ($this->exceptionTrace !== null) { + return $this->exceptionTrace; + } + + if ($this->exception !== null) { + return $this->exception->getTrace(); + } + + return null; + } } diff --git a/src/Migration/Media/Processor/HttpDownloadServiceBase.php b/src/Migration/Media/Processor/HttpDownloadServiceBase.php index 179861642..2e6ea39cc 100644 --- a/src/Migration/Media/Processor/HttpDownloadServiceBase.php +++ b/src/Migration/Media/Processor/HttpDownloadServiceBase.php @@ -80,8 +80,7 @@ public function process(MigrationContextInterface $migrationContext, Context $co $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName(MediaDefinition::ENTITY_NAME) ->build(RunExceptionLog::class) ); @@ -188,8 +187,7 @@ function (MediaProcessWorkloadStruct $work) use ($uuid) { $work->setState(MediaProcessWorkloadStruct::ERROR_STATE); $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($e->getMessage()) - ->withExceptionTrace($e->getTrace()) + ->withException($e) ->withEntityName(MediaDefinition::ENTITY_NAME) ->withEntityId($uuid) ->build(RunExceptionLog::class) @@ -287,8 +285,7 @@ private function doNormalDownloadRequest(MigrationContextInterface $migrationCon // this should never happen because of Promises, but just in case something is wrong with request construction $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName(MediaDefinition::ENTITY_NAME) ->build(RunExceptionLog::class) ); @@ -311,8 +308,7 @@ private function persistFileToMedia(string $filePath, string $uuid, string $name $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName(MediaDefinition::ENTITY_NAME) ->build(RunExceptionLog::class) ); @@ -347,8 +343,7 @@ private function persistFileToMedia(string $filePath, string $uuid, string $name } else { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($mediaException->getMessage()) - ->withExceptionTrace($mediaException->getTrace()) + ->withException($mediaException) ->withEntityName(MediaDefinition::ENTITY_NAME) ->build(RunExceptionLog::class) ); diff --git a/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php b/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php index fc6bc2587..16ce56703 100644 --- a/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php +++ b/src/Migration/MessageQueue/Handler/Processor/MediaProcessingProcessor.php @@ -140,8 +140,7 @@ public function process( if ($e->getErrorCode() === MigrationException::NO_CONNECTION_FOUND) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($e->getMessage()) - ->withExceptionTrace($e->getTrace()) + ->withException($e) ->withEntityName($currentDataSet::getEntity()) ->build(FetchProcessorMissingLog::class) ); @@ -151,8 +150,7 @@ public function process( } catch (\Throwable $e) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($e->getMessage()) - ->withExceptionTrace($e->getTrace()) + ->withException($e) ->withEntityName($currentDataSet::getEntity()) ->build(RunExceptionLog::class) ); diff --git a/src/Migration/Run/RunService.php b/src/Migration/Run/RunService.php index e7711dfa2..22847dfb9 100644 --- a/src/Migration/Run/RunService.php +++ b/src/Migration/Run/RunService.php @@ -287,8 +287,7 @@ public function assignThemeToSalesChannel(string $runUuid, Context $context): vo $connection->getProfileName(), $connection->getGatewayName(), )) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName(ThemeDefinition::ENTITY_NAME) ->withEntityId($defaultThemeId) ->build(WriteThemeCompilingFailedLog::class) diff --git a/src/Migration/Service/MigrationDataConverter.php b/src/Migration/Service/MigrationDataConverter.php index 92a56366e..1985f7223 100644 --- a/src/Migration/Service/MigrationDataConverter.php +++ b/src/Migration/Service/MigrationDataConverter.php @@ -74,8 +74,7 @@ public function convert(array $data, MigrationContextInterface $migrationContext } catch (\Throwable $exception) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName($dataSet::getEntity()) ->build(RunExceptionLog::class) ); @@ -128,8 +127,7 @@ private function convertData( } catch (\Throwable $exception) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName($dataSet::getEntity()) ->withSourceData($item) ->build(RunExceptionLog::class) diff --git a/src/Migration/Service/MigrationDataFetcher.php b/src/Migration/Service/MigrationDataFetcher.php index fbc1e2679..08b20ee57 100644 --- a/src/Migration/Service/MigrationDataFetcher.php +++ b/src/Migration/Service/MigrationDataFetcher.php @@ -37,8 +37,7 @@ public function fetchData(MigrationContextInterface $migrationContext, Context $ } catch (\Throwable $exception) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName($dataSet::getEntity()) ->build(RunExceptionLog::class) ); diff --git a/src/Migration/Service/MigrationDataWriter.php b/src/Migration/Service/MigrationDataWriter.php index a00f76444..5366c0c85 100644 --- a/src/Migration/Service/MigrationDataWriter.php +++ b/src/Migration/Service/MigrationDataWriter.php @@ -119,8 +119,7 @@ public function writeData(MigrationContextInterface $migrationContext, Context $ } catch (MigrationException $writerNotFoundException) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($writerNotFoundException->getMessage()) - ->withExceptionTrace($writerNotFoundException->getTrace()) + ->withException($writerNotFoundException) ->withConvertedData([$converted]) ->withEntityName($dataSet::getEntity()) ->build(RunExceptionLog::class) @@ -202,8 +201,7 @@ private function handleWriteException( $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName($entityName) ->withConvertedData($entity) ->withEntityId($entity['id'] ?? null) @@ -261,12 +259,11 @@ private function writePerEntity( } catch (\Throwable $exception) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityName($entityName) ->withConvertedData([$entity]) ->withEntityId($entity['id'] ?? null) - ->build(RunExceptionLog::class) + ->build(WriteExceptionLog::class) ); $updateWrittenData[$dataId]['written'] = false; diff --git a/src/Migration/Subscriber/MessageQueueSubscriber.php b/src/Migration/Subscriber/MessageQueueSubscriber.php index 492bf0bab..3e801f0a9 100644 --- a/src/Migration/Subscriber/MessageQueueSubscriber.php +++ b/src/Migration/Subscriber/MessageQueueSubscriber.php @@ -95,8 +95,7 @@ public function onWorkerMessageFailed(WorkerMessageFailedEvent $event): void $connection?->getProfileName() ?? 'unknown', $connection?->getGatewayName() ?? 'unknown' )) - ->withExceptionMessage($event->getThrowable()->getMessage()) - ->withExceptionTrace($event->getThrowable()->getTrace()) + ->withException($event->getThrowable()) ->build(RunMessageQueueExceptionLog::class) ); @@ -114,8 +113,7 @@ public function onWorkerMessageFailed(WorkerMessageFailedEvent $event): void $connection?->getProfileName() ?? 'unknown', $connection?->getGatewayName() ?? 'unknown' )) - ->withExceptionMessage($event->getThrowable()->getMessage()) - ->withExceptionTrace($event->getThrowable()->getTrace()) + ->withException($event->getThrowable()) ->build(RunAbortedLog::class) ); diff --git a/src/Migration/Validation/MigrationEntityValidationService.php b/src/Migration/Validation/MigrationEntityValidationService.php index 0bf50b7cc..a33d6b42d 100644 --- a/src/Migration/Validation/MigrationEntityValidationService.php +++ b/src/Migration/Validation/MigrationEntityValidationService.php @@ -528,8 +528,7 @@ private function addValidationExceptionLog( ->withFieldName($fieldName) ->withConvertedData($validationContext->getConvertedData()) ->withSourceData($validationContext->getSourceData()) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityId($entityId) ->build($logClass) ); @@ -545,8 +544,7 @@ private function addExceptionLog(MigrationValidationContext $validationContext, ->withEntityName($validationContext->getEntityDefinition()->getEntityName()) ->withSourceData($validationContext->getSourceData()) ->withConvertedData($convertedData) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->withEntityId($entityId) ->build(MigrationValidationExceptionLog::class) ); diff --git a/src/Profile/Shopware/Converter/MainVariantRelationConverter.php b/src/Profile/Shopware/Converter/MainVariantRelationConverter.php index 76e822e68..25f6eb2fb 100644 --- a/src/Profile/Shopware/Converter/MainVariantRelationConverter.php +++ b/src/Profile/Shopware/Converter/MainVariantRelationConverter.php @@ -35,11 +35,12 @@ public function convert(array $data, Context $context, MigrationContextInterface $this->connectionId = $connection->getId(); if (!isset($data['id'], $data['ordernumber'])) { + $exception = new \Exception('MainVariantRelation requires ID and order number, to be converted successful'); + $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) ->withSourceData($data) - ->withExceptionMessage('MainVariantRelation requires ID and order number, to be converted successful') - ->withExceptionTrace(\debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2)) + ->withException($exception) ->build(ConvertMainVariantRelationFailedLog::class) ); diff --git a/src/Profile/Shopware/Converter/ProductConverter.php b/src/Profile/Shopware/Converter/ProductConverter.php index e9d534751..55c0c2b02 100644 --- a/src/Profile/Shopware/Converter/ProductConverter.php +++ b/src/Profile/Shopware/Converter/ProductConverter.php @@ -916,8 +916,7 @@ private function getEsdFiles(array $esdFiles, string $oldVariantId, array $conve ->withFieldName('path') ->withFieldSourcePath('path') ->withSourceData($esdFile) - ->withExceptionMessage($e->getMessage()) - ->withExceptionTrace($e->getTrace()) + ->withException($e) ->build(ConvertChildEntityFailedLog::class) ); diff --git a/src/Profile/Shopware/Converter/TranslationConverter.php b/src/Profile/Shopware/Converter/TranslationConverter.php index 06a49a582..d3f88b2ef 100644 --- a/src/Profile/Shopware/Converter/TranslationConverter.php +++ b/src/Profile/Shopware/Converter/TranslationConverter.php @@ -961,24 +961,17 @@ protected function addAttribute(string $entityName, string $key, string $value, protected function unserializeTranslation(array $data, string $entity): ?array { $objectDataSerialized = $data['objectdata']; - $exception = null; try { /** @phpstan-ignore shopware.unserializeUsage */ $objectData = \unserialize($objectDataSerialized, ['allowed_classes' => false]); } catch (\Throwable $e) { - $objectData = null; - $exception = $e; - } - - if (!\is_array($objectData)) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($this->migrationContext) ->withEntityName($entity) ->withFieldSourcePath('objectdata') ->withSourceData($data) - ->withExceptionMessage($exception?->getMessage() ?? 'Unserialization failed') - ->withExceptionTrace($exception?->getTrace() ?? []) + ->withException($e) ->build(ConvertUnserializedDataInvalidLog::class) ); diff --git a/src/Profile/Shopware/Media/LocalMediaProcessor.php b/src/Profile/Shopware/Media/LocalMediaProcessor.php index 8c5a8a806..1c36cccb2 100644 --- a/src/Profile/Shopware/Media/LocalMediaProcessor.php +++ b/src/Profile/Shopware/Media/LocalMediaProcessor.php @@ -198,8 +198,7 @@ private function copyMediaFiles( 'source_path' => $sourcePath, 'media' => $mediaFile, ]) - ->withExceptionMessage($e->getMessage()) - ->withExceptionTrace($e->getTrace()) + ->withException($e) ->withEntityId($mediaId) ->build(RunExceptionLog::class) ); diff --git a/src/Profile/Shopware/Media/LocalOrderDocumentProcessor.php b/src/Profile/Shopware/Media/LocalOrderDocumentProcessor.php index 04b03eee1..1ba81fa89 100644 --- a/src/Profile/Shopware/Media/LocalOrderDocumentProcessor.php +++ b/src/Profile/Shopware/Media/LocalOrderDocumentProcessor.php @@ -129,14 +129,14 @@ private function copyMediaFiles( $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($e->getMessage()) - ->withExceptionTrace($e->getTrace()) + ->withException($e) + ->withEntityName(MediaDefinition::ENTITY_NAME) + ->withEntityId($mediaId) ->withSourceData([ 'media_id' => $mediaId, 'source_path' => $sourcePath, 'media' => $mappedWorkload[$mediaId], ]) - ->withEntityId($mediaId) ->build(RunExceptionLog::class) ); } diff --git a/src/Profile/Shopware/Media/LocalProductDownloadProcessor.php b/src/Profile/Shopware/Media/LocalProductDownloadProcessor.php index b39751b6c..89632cdd3 100644 --- a/src/Profile/Shopware/Media/LocalProductDownloadProcessor.php +++ b/src/Profile/Shopware/Media/LocalProductDownloadProcessor.php @@ -138,8 +138,7 @@ private function copyMediaFiles( 'source_path' => $sourcePath, 'media' => $mappedWorkload[$mediaId], ]) - ->withExceptionMessage($e->getMessage()) - ->withExceptionTrace($e->getTrace()) + ->withException($e) ->withEntityId($mediaId) ->build(RunExceptionLog::class) ); diff --git a/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php b/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php index 8a2bc5867..c7bdf0e6a 100644 --- a/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php +++ b/src/Profile/Shopware6/Media/HttpOrderDocumentGenerationService.php @@ -13,6 +13,7 @@ use GuzzleHttp\Promise\Utils; use GuzzleHttp\Psr7\Response; use Shopware\Core\Checkout\Document\DocumentCollection; +use Shopware\Core\Content\Media\MediaDefinition; use Shopware\Core\Content\Media\MediaService; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; @@ -88,8 +89,7 @@ public function process( $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($exception->getMessage()) - ->withExceptionTrace($exception->getTrace()) + ->withException($exception) ->build(RunExceptionLog::class) ); @@ -286,10 +286,12 @@ private function handleFailedRequest( $failureUuids[] = $uuid; $mappedWorkload->setState(MediaProcessWorkloadStruct::ERROR_STATE); + $exception = $clientException ?? new \Exception('Unknown error occurred'); + $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) - ->withExceptionMessage($clientException?->getMessage() ?? 'Unknown error occurred') - ->withExceptionTrace($clientException?->getTrace() ?? []) + ->withException($exception) + ->withEntityName(MediaDefinition::ENTITY_NAME) ->withSourceData($additionalData) ->withEntityId($uuid) ->build(MediaFileMissingLog::class) From 7e173691605b6eb52404fbda4d4323bf580756e9 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 11:43:53 +0200 Subject: [PATCH 02/28] fix exception in converter registry --- src/Migration/Converter/ConverterRegistry.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Migration/Converter/ConverterRegistry.php b/src/Migration/Converter/ConverterRegistry.php index 77fbe39a3..0ce03f502 100644 --- a/src/Migration/Converter/ConverterRegistry.php +++ b/src/Migration/Converter/ConverterRegistry.php @@ -32,6 +32,11 @@ public function getConverter(MigrationContextInterface $migrationContext): Conve } } - throw MigrationException::converterNotFound($migrationContext->getProfile()->getName()); + $dataSet = $migrationContext->getDataSet(); + if ($dataSet === null) { + throw MigrationException::migrationContextPropertyMissing('DataSet'); + } + + throw MigrationException::converterNotFound($dataSet::getEntity()); } } From 9f7119adee7fbc1376a13d5260ed3a12e8d78c29 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 11:44:15 +0200 Subject: [PATCH 03/28] make OrderWriter magento proof --- src/Migration/Writer/OrderWriter.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Migration/Writer/OrderWriter.php b/src/Migration/Writer/OrderWriter.php index 3062b6b26..338cf9870 100644 --- a/src/Migration/Writer/OrderWriter.php +++ b/src/Migration/Writer/OrderWriter.php @@ -33,6 +33,10 @@ public function supports(): string public function writeData(array $data, Context $context): array { foreach ($data as &$item) { + if (!isset($item['transactions']) || !\is_array($item['transactions'])) { + continue; + } + foreach ($item['transactions'] as &$transaction) { $transaction['amount'] = $this->structNormalizer->denormalize($transaction['amount']); } From ca586a19210fa2b6d161f3cc058318192d6ea4ad Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 11:44:35 +0200 Subject: [PATCH 04/28] remove magento 1.9 profile + add 2.4 profile --- .../swag-migration-wizard-page-profile-installation.html.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard-page-profile-installation/swag-migration-wizard-page-profile-installation.html.twig b/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard-page-profile-installation/swag-migration-wizard-page-profile-installation.html.twig index d7ac8d301..56bccd579 100644 --- a/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard-page-profile-installation/swag-migration-wizard-page-profile-installation.html.twig +++ b/src/Resources/app/administration/src/module/swag-migration/page/wizard/swag-migration-wizard-page-profile-installation/swag-migration-wizard-page-profile-installation.html.twig @@ -112,11 +112,11 @@ {% block swag_migration_wizard_page_profile_installation_magento_available_version_list %}
- 1.9 2.0 2.1 2.2 - 2.3 + 2.3 + 2.4
{% endblock %} From f941808b8dc090606c97bd39d345824a8ecb500c Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 15:20:38 +0200 Subject: [PATCH 05/28] fix/add tests --- .../Log/Builder/MigrationLogBuilder.php | 3 + .../Services/MigrationDataWriterTest.php | 3 +- .../MigrationEntityValidationServiceTest.php | 2 +- .../Logging/Log/MigrationLogTest.php | 93 +++++++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php b/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php index 2af57a11a..22fdfbb55 100644 --- a/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php +++ b/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php @@ -177,6 +177,9 @@ private function getExceptionMessage(): ?string return null; } + /** + * @return array|null + */ private function getExceptionTrace(): ?array { if ($this->exceptionTrace !== null) { diff --git a/tests/Migration/Services/MigrationDataWriterTest.php b/tests/Migration/Services/MigrationDataWriterTest.php index 00d2523c4..db11cc5d5 100644 --- a/tests/Migration/Services/MigrationDataWriterTest.php +++ b/tests/Migration/Services/MigrationDataWriterTest.php @@ -55,6 +55,7 @@ use SwagMigrationAssistant\Migration\Gateway\Reader\ReaderRegistry; use SwagMigrationAssistant\Migration\History\LogGroupingService; use SwagMigrationAssistant\Migration\Logging\Log\RunExceptionLog; +use SwagMigrationAssistant\Migration\Logging\Log\WriteExceptionLog; use SwagMigrationAssistant\Migration\Logging\LoggingService; use SwagMigrationAssistant\Migration\Logging\LoggingServiceInterface; use SwagMigrationAssistant\Migration\Logging\SwagMigrationLoggingCollection; @@ -382,7 +383,7 @@ public function testHandleWriteException(): void $log = $this->loggingRepo->search(new Criteria(), $this->context)->getEntities()->first(); static::assertNotNull($log); - static::assertSame(RunExceptionLog::getCode(), $log->getCode()); + static::assertSame(WriteExceptionLog::getCode(), $log->getCode()); } #[DataProvider('requiredProperties')] diff --git a/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php b/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php index 42cb722f3..019a76f2f 100644 --- a/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php +++ b/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php @@ -317,7 +317,7 @@ public function testShouldLogWhenEntityHasInvalidOrMissingId(array $convertedDat $exceptionLog = array_values($logs)[0]; static::assertInstanceOf(MigrationValidationExceptionLog::class, $exceptionLog); - static::assertSame($expectedExceptionMessage, $exceptionLog->getExceptionMessage()); + static::assertStringContainsString($expectedExceptionMessage, $exceptionLog->getExceptionMessage()); } /** diff --git a/tests/unit/Migration/Logging/Log/MigrationLogTest.php b/tests/unit/Migration/Logging/Log/MigrationLogTest.php index acd1721f7..8293037c4 100644 --- a/tests/unit/Migration/Logging/Log/MigrationLogTest.php +++ b/tests/unit/Migration/Logging/Log/MigrationLogTest.php @@ -330,4 +330,97 @@ public static function logProvider(): \Generator 'userFixable' => false, ]; } + + /** + * @param array|null $exceptionTrace + * @param array $expectedExceptionTrace + */ + #[DataProvider('exceptionProvider')] + public function testLogEntryExceptions( + ?\Throwable $exception, + ?string $exceptionMessage, + ?array $exceptionTrace, + string $expectedExceptionMessage, + ?array $expectedExceptionTrace, + ): void { + $connection = new SwagMigrationConnectionEntity(); + $connection->setProfileName(Shopware54Profile::PROFILE_NAME); + $connection->setGatewayName(DummyLocalGateway::GATEWAY_NAME); + + $context = new MigrationContext( + $connection, + new Shopware54Profile(), + new DummyLocalGateway(), + null, + Uuid::randomHex(), + ); + + $builder = MigrationLogBuilder::fromMigrationContext($context); + + if ($exception !== null) { + $builder = $builder->withException($exception); + } + + if ($exceptionMessage !== null) { + $builder = $builder->withExceptionMessage($exceptionMessage); + } + + if ($exceptionTrace !== null) { + $builder = $builder->withExceptionTrace($exceptionTrace); + } + + $logEntry = $builder->build(RunExceptionLog::class); + + static::assertInstanceOf(RunExceptionLog::class, $logEntry); + static::assertSame($expectedExceptionMessage, $logEntry->getExceptionMessage()); + static::assertSame($expectedExceptionTrace, $logEntry->getExceptionTrace()); + } + + public static function exceptionProvider(): \Generator + { + $exception = new \RuntimeException('Runtime exception occurred'); + $file = $exception->getFile(); + $line = $exception->getLine(); + $trace = $exception->getTrace(); + + yield 'with exception' => [ + 'exception' => $exception, + 'exceptionMessage' => null, + 'exceptionTrace' => null, + 'expectedExceptionMessage' => 'Runtime exception occurred' . ' in ' . $file . ':' . $line, + 'expectedExceptionTrace' => $trace, + ]; + + yield 'with exception message and trace' => [ + 'exception' => null, + 'exceptionMessage' => 'Test2', + 'exceptionTrace' => ['test' => 'trace'], + 'expectedExceptionMessage' => 'Test2', + 'expectedExceptionTrace' => ['test' => 'trace'], + ]; + + yield 'with exception and message' => [ + 'exception' => $exception, + 'exceptionMessage' => 'Test4', + 'exceptionTrace' => null, + 'expectedExceptionMessage' => 'Test4', + 'expectedExceptionTrace' => $trace, + ]; + + yield 'with exception and trace' => [ + 'exception' => $exception, + 'exceptionMessage' => null, + 'exceptionTrace' => ['test' => 'trace2'], + 'expectedExceptionMessage' => 'Runtime exception occurred' . ' in ' . $file . ':' . $line, + 'expectedExceptionTrace' => ['test' => 'trace2'], + ]; + + yield 'with exception, message and trace' => [ + 'exception' => $exception, + 'exceptionMessage' => 'Test5', + 'exceptionTrace' => ['test' => 'trace3'], + 'expectedExceptionMessage' => 'Test5', + 'expectedExceptionTrace' => ['test' => 'trace3'], + ]; + } } From e783be67549c51d2993058a3b9234326fd0a4948 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 15:23:48 +0200 Subject: [PATCH 06/28] ecs-fix --- tests/unit/Migration/Logging/Log/MigrationLogTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/Migration/Logging/Log/MigrationLogTest.php b/tests/unit/Migration/Logging/Log/MigrationLogTest.php index 8293037c4..28ec2aafd 100644 --- a/tests/unit/Migration/Logging/Log/MigrationLogTest.php +++ b/tests/unit/Migration/Logging/Log/MigrationLogTest.php @@ -387,7 +387,7 @@ public static function exceptionProvider(): \Generator 'exception' => $exception, 'exceptionMessage' => null, 'exceptionTrace' => null, - 'expectedExceptionMessage' => 'Runtime exception occurred' . ' in ' . $file . ':' . $line, + 'expectedExceptionMessage' => 'Runtime exception occurred in ' . $file . ':' . $line, 'expectedExceptionTrace' => $trace, ]; @@ -411,7 +411,7 @@ public static function exceptionProvider(): \Generator 'exception' => $exception, 'exceptionMessage' => null, 'exceptionTrace' => ['test' => 'trace2'], - 'expectedExceptionMessage' => 'Runtime exception occurred' . ' in ' . $file . ':' . $line, + 'expectedExceptionMessage' => 'Runtime exception occurred in ' . $file . ':' . $line, 'expectedExceptionTrace' => ['test' => 'trace2'], ]; From d15c34ead5c0c3309a4bfd010c47ce64ea411296 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 15:34:25 +0200 Subject: [PATCH 07/28] fix TranslationConverter --- src/Profile/Shopware/Converter/TranslationConverter.php | 4 ++++ .../Validation/MigrationEntityValidationServiceTest.php | 1 + 2 files changed, 5 insertions(+) diff --git a/src/Profile/Shopware/Converter/TranslationConverter.php b/src/Profile/Shopware/Converter/TranslationConverter.php index d3f88b2ef..0d27d4c7f 100644 --- a/src/Profile/Shopware/Converter/TranslationConverter.php +++ b/src/Profile/Shopware/Converter/TranslationConverter.php @@ -965,6 +965,10 @@ protected function unserializeTranslation(array $data, string $entity): ?array try { /** @phpstan-ignore shopware.unserializeUsage */ $objectData = \unserialize($objectDataSerialized, ['allowed_classes' => false]); + + if (!\is_array($objectData)) { + throw new \UnexpectedValueException('Unserialized data is not an array'); + } } catch (\Throwable $e) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($this->migrationContext) diff --git a/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php b/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php index 019a76f2f..01df02e77 100644 --- a/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php +++ b/tests/integration/Migration/Validation/MigrationEntityValidationServiceTest.php @@ -317,6 +317,7 @@ public function testShouldLogWhenEntityHasInvalidOrMissingId(array $convertedDat $exceptionLog = array_values($logs)[0]; static::assertInstanceOf(MigrationValidationExceptionLog::class, $exceptionLog); + static::assertNotNull($exceptionLog->getExceptionMessage()); static::assertStringContainsString($expectedExceptionMessage, $exceptionLog->getExceptionMessage()); } From 9565af5ce22035e8c6349574e2ce139f2dbfc27e Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 15:45:43 +0200 Subject: [PATCH 08/28] fix phpstan --- src/Profile/Shopware/Converter/TranslationConverter.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Profile/Shopware/Converter/TranslationConverter.php b/src/Profile/Shopware/Converter/TranslationConverter.php index 0d27d4c7f..88405c926 100644 --- a/src/Profile/Shopware/Converter/TranslationConverter.php +++ b/src/Profile/Shopware/Converter/TranslationConverter.php @@ -961,21 +961,26 @@ protected function addAttribute(string $entityName, string $key, string $value, protected function unserializeTranslation(array $data, string $entity): ?array { $objectDataSerialized = $data['objectdata']; + $exception = null; try { /** @phpstan-ignore shopware.unserializeUsage */ $objectData = \unserialize($objectDataSerialized, ['allowed_classes' => false]); if (!\is_array($objectData)) { - throw new \UnexpectedValueException('Unserialized data is not an array'); + $exception = new \UnexpectedValueException('Unserialized data is not an array'); } } catch (\Throwable $e) { + $exception = $e; + } + + if ($exception !== null) { $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($this->migrationContext) ->withEntityName($entity) ->withFieldSourcePath('objectdata') ->withSourceData($data) - ->withException($e) + ->withException($exception) ->build(ConvertUnserializedDataInvalidLog::class) ); From def4e6d6f6a9f83319fa9e4c9087656594ad38a7 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 15:57:34 +0200 Subject: [PATCH 09/28] fix phpstan --- .../Converter/TranslationConverter.php | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Profile/Shopware/Converter/TranslationConverter.php b/src/Profile/Shopware/Converter/TranslationConverter.php index 88405c926..7eb4c0c3c 100644 --- a/src/Profile/Shopware/Converter/TranslationConverter.php +++ b/src/Profile/Shopware/Converter/TranslationConverter.php @@ -961,33 +961,30 @@ protected function addAttribute(string $entityName, string $key, string $value, protected function unserializeTranslation(array $data, string $entity): ?array { $objectDataSerialized = $data['objectdata']; - $exception = null; try { /** @phpstan-ignore shopware.unserializeUsage */ $objectData = \unserialize($objectDataSerialized, ['allowed_classes' => false]); - if (!\is_array($objectData)) { - $exception = new \UnexpectedValueException('Unserialized data is not an array'); + if (\is_array($objectData)) { + return $objectData; } + + $error = new \UnexpectedValueException('Unserialized data is not an array'); } catch (\Throwable $e) { - $exception = $e; + $error = $e; } - if ($exception !== null) { - $this->loggingService->log( - MigrationLogBuilder::fromMigrationContext($this->migrationContext) - ->withEntityName($entity) - ->withFieldSourcePath('objectdata') - ->withSourceData($data) - ->withException($exception) - ->build(ConvertUnserializedDataInvalidLog::class) - ); - - return null; - } + $this->loggingService->log( + MigrationLogBuilder::fromMigrationContext($this->migrationContext) + ->withEntityName($entity) + ->withFieldSourcePath('objectdata') + ->withSourceData($data) + ->withException($error) + ->build(ConvertUnserializedDataInvalidLog::class) + ); - return $objectData; + return null; } /** From b4e4cd145195cb930016ce52df41202a506f2020 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 16:21:38 +0200 Subject: [PATCH 10/28] fix acceptance test --- .../MigrationTest.spec.ts/linux/migration-log-sw5.txt | 6 +++--- tests/acceptance/tests/MigrationTest.spec.ts | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt index c317d98c7..8493cc2ba 100644 --- a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt +++ b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt @@ -735,7 +735,7 @@ Profile name: shopware55 Gateway name: local Created at: [timestamp] Entity: product -Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in /home/runner/work/SwagMigrationAssistant/SwagMigrationAssistant/custom/plugins/SwagMigrationAssistant/src/Profile/Shopware/Converter/ProductConverter.php on line 383 +Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter.php on line 383 Source data (JSON): { "id": "273", @@ -824,7 +824,7 @@ Gateway name: local Created at: [timestamp] Entity: product_translation Source path: objectdata -Exception message: Unserialization failed +Exception message: Unserialized data is not an array in [path]src/Profiled/Shopware/Converter/TranslationConverter.php:973 Source data (JSON): { "id": "170", @@ -848,7 +848,7 @@ Gateway name: local Created at: [timestamp] Entity: product_translation Source path: objectdata -Exception message: Unserialization failed +Exception message: Unserialized data is not an array in [path]src/Profiled/Shopware/Converter/TranslationConverter.php:973 Source data (JSON): { "id": "172", diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index 2d5a63eb9..ead88d3d1 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -279,6 +279,12 @@ test.describe('Migration Tests @migration @visual', () => { 'Exception trace (JSON):\n[trace]\n', ); + // remove exception paths + logString = logString.replaceAll( + /^Exception message:.*? in \/.*?\/src\//gm, + (match) => match.replace(/ in \/.*?\/src\//, ' in [path]src/'), + ); + expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); }); From 82199f93ba733da4cfa9f914c8afa26fb59ed677 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 16:33:41 +0200 Subject: [PATCH 11/28] npm format fix --- .../MigrationTest.spec.ts/linux/migration-log-sw5.txt | 10 +++++++--- tests/acceptance/tests/MigrationTest.spec.ts | 5 ++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt index 8493cc2ba..e0dc2661c 100644 --- a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt +++ b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt @@ -735,7 +735,7 @@ Profile name: shopware55 Gateway name: local Created at: [timestamp] Entity: product -Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter.php on line 383 +Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter.php:1256 Source data (JSON): { "id": "273", @@ -824,7 +824,7 @@ Gateway name: local Created at: [timestamp] Entity: product_translation Source path: objectdata -Exception message: Unserialized data is not an array in [path]src/Profiled/Shopware/Converter/TranslationConverter.php:973 +Exception message: Unserialized data is not an array in [path]src/Profile/Shopware/Converter/TranslationConverter.php:973 Source data (JSON): { "id": "170", @@ -838,6 +838,8 @@ Source data (JSON): "ordernumber": null, "objectlanguage": "2" } +Exception trace (JSON): +[trace] ----- Log Entry #10 ----- ID: [uuid] @@ -848,7 +850,7 @@ Gateway name: local Created at: [timestamp] Entity: product_translation Source path: objectdata -Exception message: Unserialized data is not an array in [path]src/Profiled/Shopware/Converter/TranslationConverter.php:973 +Exception message: Unserialized data is not an array in [path]src/Profile/Shopware/Converter/TranslationConverter.php:973 Source data (JSON): { "id": "172", @@ -862,6 +864,8 @@ Source data (JSON): "ordernumber": null, "objectlanguage": "2" } +Exception trace (JSON): +[trace] ----- Log Entry #11 ----- ID: [uuid] diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index ead88d3d1..af2bda339 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,9 +280,8 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - logString = logString.replaceAll( - /^Exception message:.*? in \/.*?\/src\//gm, - (match) => match.replace(/ in \/.*?\/src\//, ' in [path]src/'), + logString = logString.replaceAll(/^Exception message:.*? in \/.*?\/src\//gm, (match) => + match.replace(/ in \/.*?\/src\//, ' in [path]src/'), ); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); From dabf4a0f5a42008bb68e83f36af57067745d90ce Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 16:46:37 +0200 Subject: [PATCH 12/28] fix acceptance test --- .../snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt index e0dc2661c..d560887cf 100644 --- a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt +++ b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt @@ -735,7 +735,7 @@ Profile name: shopware55 Gateway name: local Created at: [timestamp] Entity: product -Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter.php:1256 +Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter on line 383 Source data (JSON): { "id": "273", From 7ec36b6e6fdc43a67e6405d5d7ec2135becc6827 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 16:59:06 +0200 Subject: [PATCH 13/28] try again acceptance tests --- .../snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt index d560887cf..fc9451cba 100644 --- a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt +++ b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt @@ -735,7 +735,7 @@ Profile name: shopware55 Gateway name: local Created at: [timestamp] Entity: product -Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter on line 383 +Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter.php on line 383 Source data (JSON): { "id": "273", From e0b5550c58bf3f8dec6221dfeaaaf101a070aa7b Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 17:01:41 +0200 Subject: [PATCH 14/28] change replace pattern --- tests/acceptance/tests/MigrationTest.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index af2bda339..01758ebc5 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,8 +280,9 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - logString = logString.replaceAll(/^Exception message:.*? in \/.*?\/src\//gm, (match) => - match.replace(/ in \/.*?\/src\//, ' in [path]src/'), + logString = logString.replaceAll( + /^(Exception message:.*? in )\/.*?\/src\//gm, + '$1[path]src/', ); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); From 197f708ffab4c1470fbf02c5bf0af4b2c6fbd514 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Thu, 5 Mar 2026 17:26:36 +0200 Subject: [PATCH 15/28] try again --- tests/acceptance/tests/MigrationTest.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index 01758ebc5..3beff3049 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,10 +280,7 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - logString = logString.replaceAll( - /^(Exception message:.*? in )\/.*?\/src\//gm, - '$1[path]src/', - ); + logString = logString.replaceAll(/(^Exception message:.*? in )\/[^ ]*?\/src\//gm, '$1[path]src/'); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); From f491569350cfd609f91eba6477601cb7743730bf Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Fri, 6 Mar 2026 12:59:35 +0200 Subject: [PATCH 16/28] resolve threads --- src/Exception/MigrationException.php | 11 +++++++++++ .../Converter/MainVariantRelationConverter.php | 3 ++- .../MigrationTest.spec.ts/linux/migration-log-sw5.txt | 6 +++--- tests/acceptance/tests/MigrationTest.spec.ts | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Exception/MigrationException.php b/src/Exception/MigrationException.php index c477e289b..755ab62cb 100644 --- a/src/Exception/MigrationException.php +++ b/src/Exception/MigrationException.php @@ -105,6 +105,8 @@ class MigrationException extends HttpException public const LOCAL_DATABASE_CONNECTION_ERROR = 'SWAG_MIGRATION__LOCAL_DATABASE_CONNECTION_ERROR'; + final public const MAIN_VARIANT_RELATION_MISSING_ID_AND_ORDER_NUMBER = 'SWAG_MIGRATION__MAIN_VARIANT_RELATION_MISSING_ID_AND_ORDER_NUMBER'; + public static function associationEntityRequiredMissing(string $entity, string $missingEntity): self { return new self( @@ -556,4 +558,13 @@ public static function connectionValidationFailed(string $code, string $message) $message, ); } + + public static function mainVariantRelationMissingIdAndOrderNumber(): self + { + return new self( + Response::HTTP_INTERNAL_SERVER_ERROR, + self::MAIN_VARIANT_RELATION_MISSING_ID_AND_ORDER_NUMBER, + 'MainVariantRelation requires ID and order number.', + ); + } } diff --git a/src/Profile/Shopware/Converter/MainVariantRelationConverter.php b/src/Profile/Shopware/Converter/MainVariantRelationConverter.php index 25f6eb2fb..fb0a71e3f 100644 --- a/src/Profile/Shopware/Converter/MainVariantRelationConverter.php +++ b/src/Profile/Shopware/Converter/MainVariantRelationConverter.php @@ -9,6 +9,7 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\Log\Package; +use SwagMigrationAssistant\Exception\MigrationException; use SwagMigrationAssistant\Migration\Converter\ConvertStruct; use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities; use SwagMigrationAssistant\Migration\Logging\Log\Builder\MigrationLogBuilder; @@ -35,7 +36,7 @@ public function convert(array $data, Context $context, MigrationContextInterface $this->connectionId = $connection->getId(); if (!isset($data['id'], $data['ordernumber'])) { - $exception = new \Exception('MainVariantRelation requires ID and order number, to be converted successful'); + $exception = MigrationException::mainVariantRelationMissingIdAndOrderNumber(); $this->loggingService->log( MigrationLogBuilder::fromMigrationContext($migrationContext) diff --git a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt index fc9451cba..339bf3bd1 100644 --- a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt +++ b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt @@ -735,7 +735,7 @@ Profile name: shopware55 Gateway name: local Created at: [timestamp] Entity: product -Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path]src/Profile/Shopware/Converter/ProductConverter.php on line 383 +Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path] Source data (JSON): { "id": "273", @@ -824,7 +824,7 @@ Gateway name: local Created at: [timestamp] Entity: product_translation Source path: objectdata -Exception message: Unserialized data is not an array in [path]src/Profile/Shopware/Converter/TranslationConverter.php:973 +Exception message: Unserialized data is not an array in [path] Source data (JSON): { "id": "170", @@ -850,7 +850,7 @@ Gateway name: local Created at: [timestamp] Entity: product_translation Source path: objectdata -Exception message: Unserialized data is not an array in [path]src/Profile/Shopware/Converter/TranslationConverter.php:973 +Exception message: Unserialized data is not an array in [path] Source data (JSON): { "id": "172", diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index 3beff3049..5bee34d12 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,7 +280,7 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - logString = logString.replaceAll(/(^Exception message:.*? in )\/[^ ]*?\/src\//gm, '$1[path]src/'); + logString = logString.replaceAll(/(^Exception message:.* in ).*/gm, '$1[path]'); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); From d26face7186638cd65e13217a7f44372af6877c2 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Fri, 6 Mar 2026 13:14:37 +0200 Subject: [PATCH 17/28] fix exception --- src/Profile/Shopware/Converter/ProductConverter.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Profile/Shopware/Converter/ProductConverter.php b/src/Profile/Shopware/Converter/ProductConverter.php index 55c0c2b02..7453b63ad 100644 --- a/src/Profile/Shopware/Converter/ProductConverter.php +++ b/src/Profile/Shopware/Converter/ProductConverter.php @@ -380,7 +380,9 @@ private function getProductData(array &$data, array $converted): array unset($data['unit'], $data['detail']['unitID']); } - $converted['price'] = $this->getPrice($data['prices'][0], $converted['tax']['taxRate']); + if (\is_array($data['prices'][0])) { + $converted['price'] = $this->getPrice($data['prices'][0], $converted['tax']['taxRate']); + } if (empty($converted['price'])) { $this->loggingService->log( @@ -394,7 +396,9 @@ private function getProductData(array &$data, array $converted): array ); } - $converted['prices'] = $this->getPrices($data['prices'], $converted); + if (\is_array($data['prices'])) { + $converted['prices'] = $this->getPrices($data['prices'], $converted); + } unset($data['prices']); if (isset($data['assets'])) { From 0c4611c35fefec3eeecc78246f8acd23b4a29533 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 09:56:36 +0100 Subject: [PATCH 18/28] Revert "fix exception" This reverts commit d26face7186638cd65e13217a7f44372af6877c2. --- src/Profile/Shopware/Converter/ProductConverter.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Profile/Shopware/Converter/ProductConverter.php b/src/Profile/Shopware/Converter/ProductConverter.php index 7453b63ad..55c0c2b02 100644 --- a/src/Profile/Shopware/Converter/ProductConverter.php +++ b/src/Profile/Shopware/Converter/ProductConverter.php @@ -380,9 +380,7 @@ private function getProductData(array &$data, array $converted): array unset($data['unit'], $data['detail']['unitID']); } - if (\is_array($data['prices'][0])) { - $converted['price'] = $this->getPrice($data['prices'][0], $converted['tax']['taxRate']); - } + $converted['price'] = $this->getPrice($data['prices'][0], $converted['tax']['taxRate']); if (empty($converted['price'])) { $this->loggingService->log( @@ -396,9 +394,7 @@ private function getProductData(array &$data, array $converted): array ); } - if (\is_array($data['prices'])) { - $converted['prices'] = $this->getPrices($data['prices'], $converted); - } + $converted['prices'] = $this->getPrices($data['prices'], $converted); unset($data['prices']); if (isset($data['assets'])) { From 49c0f5ee9a79ba3eb13bfbc0c44de061c8ea83a7 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 10:39:59 +0100 Subject: [PATCH 19/28] fix test --- tests/acceptance/tests/MigrationTest.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index 5bee34d12..ec75e726b 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,7 +280,7 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - logString = logString.replaceAll(/(^Exception message:.* in ).*/gm, '$1[path]'); + logString = logString.replaceAll(/(^Exception message:.*\bin\b )(?!.*\bin\b ).*/gm, '$1[path]'); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); From f9a092d939cf52ea1ee66515403acf46a5706ad1 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 10:59:22 +0100 Subject: [PATCH 20/28] add changelog --- CHANGELOG.md | 5 +++++ CHANGELOG_de-DE.md | 5 +++++ composer.json | 2 +- tests/acceptance/tests/MigrationTest.spec.ts | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9029353ca..7ebd4f07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 16.1.0 + +- Fixed log entries to have the file path and line number in the exception message +- Remove support for Magento 1.9 + # 16.0.0 - #14517 - Fixed duplicate key error when re-migrating products with SEO main categories after checksum reset diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md index 34f3643b6..b5ec443da 100644 --- a/CHANGELOG_de-DE.md +++ b/CHANGELOG_de-DE.md @@ -1,3 +1,8 @@ +# 16.1.0 + +- Log-Einträge wurden korrigiert, sodass der Dateipfad und die Zeilennummer nun in der Ausnahmemeldung enthalten sind. +- Die Unterstützung für Magento 1.9 wurde entfernt. + # 16.0.0 - #14517 - Fehler bei doppeltem Schlüssel beim erneuten Migrieren von Produkten mit SEO-Hauptkategorien nach dem Zurücksetzen der Prüfsummen behoben diff --git a/composer.json b/composer.json index 15c948d1e..ae815ffc5 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "swag/migration-assistant", "description": "Migration plugin for shopware/platform", - "version": "16.0.0", + "version": "16.1.0", "type": "shopware-platform-plugin", "license": "MIT", "authors": [ diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index ec75e726b..b6c5eb4e7 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,7 +280,7 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - logString = logString.replaceAll(/(^Exception message:.*\bin\b )(?!.*\bin\b ).*/gm, '$1[path]'); + logString = logString.replaceAll(/^(Exception message:.*) in .*$/gm, '$1 in [path]'); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); From eb8f319c67f8cefebd1de5f53badee4dc85b85f6 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 11:09:52 +0100 Subject: [PATCH 21/28] test --- .../snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt index 339bf3bd1..d579f033e 100644 --- a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt +++ b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt @@ -735,7 +735,7 @@ Profile name: shopware55 Gateway name: local Created at: [timestamp] Entity: product -Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path] +Exception message: Source data (JSON): { "id": "273", From 91ecbe22870b715cd2b515bd8e79c441195d85e6 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 11:23:25 +0100 Subject: [PATCH 22/28] temp --- tests/acceptance/tests/MigrationTest.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index b6c5eb4e7..ab05937fc 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,7 +280,7 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - logString = logString.replaceAll(/^(Exception message:.*) in .*$/gm, '$1 in [path]'); + // logString = logString.replaceAll(/^(Exception message:.*) in .*$/gm, '$1 in [path]'); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); From 9664f97ea13c63353c308c9972e1f7b7f435e3e9 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 11:45:01 +0100 Subject: [PATCH 23/28] fix acceptance tests --- tests/acceptance/tests/MigrationTest.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index ab05937fc..98fcac23f 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,7 +280,9 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths - // logString = logString.replaceAll(/^(Exception message:.*) in .*$/gm, '$1 in [path]'); + logString = logString.replaceAll(/(^Exception message:.*\bin\b )(?!.*\bin\b ).*/gm, '$1[path]'); + // some exceptions can also contain paths in the message itself (with the pattern "called in ..."), so we need to replace them as well + logString = logString.replaceAll(/^.*(?= called in [^]* called in )/, '[path]'); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); From e90a6256b143ac92f51e391da709d31f088b6b6e Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 12:01:54 +0100 Subject: [PATCH 24/28] add some explanation --- tests/acceptance/tests/MigrationTest.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/acceptance/tests/MigrationTest.spec.ts b/tests/acceptance/tests/MigrationTest.spec.ts index 98fcac23f..5b27c1d38 100644 --- a/tests/acceptance/tests/MigrationTest.spec.ts +++ b/tests/acceptance/tests/MigrationTest.spec.ts @@ -280,9 +280,12 @@ test.describe('Migration Tests @migration @visual', () => { ); // remove exception paths + // (the regex replaces everything after the last occurrence of "in" in the exception message) logString = logString.replaceAll(/(^Exception message:.*\bin\b )(?!.*\bin\b ).*/gm, '$1[path]'); - // some exceptions can also contain paths in the message itself (with the pattern "called in ..."), so we need to replace them as well - logString = logString.replaceAll(/^.*(?= called in [^]* called in )/, '[path]'); + // some exceptions can also contain paths in the message itself (with the pattern "called in ...") + // so we need to replace them as well + // (the regex replaces everything after the last occurrence of "called in" in the message) + logString = logString.replaceAll(/^(Exception message:.*called in ).*$/gm, '$1[path]'); expect(logString).toMatchSnapshot('migration-log-sw5.txt'); }); From 1cd00a0e5aad4cd5ffc8680685177972025cd0f2 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 12:11:27 +0100 Subject: [PATCH 25/28] fix log file --- .../snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt index d579f033e..339bf3bd1 100644 --- a/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt +++ b/tests/acceptance/snapshots/MigrationTest.spec.ts/linux/migration-log-sw5.txt @@ -735,7 +735,7 @@ Profile name: shopware55 Gateway name: local Created at: [timestamp] Entity: product -Exception message: +Exception message: SwagMigrationAssistant\Profile\Shopware\Converter\ProductConverter::getPrice(): Argument #1 ($priceData) must be of type array, null given, called in [path] Source data (JSON): { "id": "273", From c6a5502ae682a5c3dc4177213c15727c1365d29b Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 13:40:11 +0100 Subject: [PATCH 26/28] fix traces --- .../Log/Builder/MigrationLogBuilder.php | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php b/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php index d465607c2..9bf8f6e2a 100644 --- a/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php +++ b/src/Migration/Logging/Log/Builder/MigrationLogBuilder.php @@ -136,13 +136,6 @@ public function build(string $logClass): AbstractMigrationLogEntry { \assert(\class_exists($logClass) && \is_subclass_of($logClass, AbstractMigrationLogEntry::class)); - if ($this->exceptionTrace !== null) { - // remove args from trace, as they quickly become too large - foreach ($this->exceptionTrace as &$trace) { - unset($trace['args']); - } - } - return new $logClass( $this->runId, $this->profileName, @@ -189,14 +182,14 @@ private function getExceptionMessage(): ?string */ private function getExceptionTrace(): ?array { - if ($this->exceptionTrace !== null) { - return $this->exceptionTrace; - } + $trace = $this->exceptionTrace ?? $this->exception?->getTrace(); - if ($this->exception !== null) { - return $this->exception->getTrace(); + if ($trace !== null) { + foreach ($trace as &$traceEntry) { + unset($traceEntry['args']); + } } - return null; + return $trace; } } From 40ae0fa76fd1243c6ca09ff24e81a9e24c83b1fb Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 13:46:18 +0100 Subject: [PATCH 27/28] fix tests --- .../unit/Migration/Logging/Log/MigrationLogTest.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/unit/Migration/Logging/Log/MigrationLogTest.php b/tests/unit/Migration/Logging/Log/MigrationLogTest.php index d5f9906ba..6b992a81d 100644 --- a/tests/unit/Migration/Logging/Log/MigrationLogTest.php +++ b/tests/unit/Migration/Logging/Log/MigrationLogTest.php @@ -390,6 +390,11 @@ public static function exceptionProvider(): \Generator $line = $exception->getLine(); $trace = $exception->getTrace(); + // unset args from trace for comparison, as they are removed in the log entry + foreach ($trace as &$traceEntry) { + unset($traceEntry['args']); + } + yield 'with exception' => [ 'exception' => $exception, 'exceptionMessage' => null, @@ -401,9 +406,9 @@ public static function exceptionProvider(): \Generator yield 'with exception message and trace' => [ 'exception' => null, 'exceptionMessage' => 'Test2', - 'exceptionTrace' => ['test' => 'trace'], + 'exceptionTrace' => [['test' => 'trace']], 'expectedExceptionMessage' => 'Test2', - 'expectedExceptionTrace' => ['test' => 'trace'], + 'expectedExceptionTrace' => [['test' => 'trace']], ]; yield 'with exception and message' => [ @@ -417,7 +422,7 @@ public static function exceptionProvider(): \Generator yield 'with exception and trace' => [ 'exception' => $exception, 'exceptionMessage' => null, - 'exceptionTrace' => ['test' => 'trace2'], + 'exceptionTrace' => [['test' => 'trace2']], 'expectedExceptionMessage' => 'Runtime exception occurred in ' . $file . ':' . $line, 'expectedExceptionTrace' => ['test' => 'trace2'], ]; @@ -425,7 +430,7 @@ public static function exceptionProvider(): \Generator yield 'with exception, message and trace' => [ 'exception' => $exception, 'exceptionMessage' => 'Test5', - 'exceptionTrace' => ['test' => 'trace3'], + 'exceptionTrace' => [['test' => 'trace3']], 'expectedExceptionMessage' => 'Test5', 'expectedExceptionTrace' => ['test' => 'trace3'], ]; From 3245ef98a2887a82e0a7813536605f3f1ae32cc2 Mon Sep 17 00:00:00 2001 From: Jozsef Damokos Date: Mon, 9 Mar 2026 13:51:57 +0100 Subject: [PATCH 28/28] fix remaining tests --- tests/unit/Migration/Logging/Log/MigrationLogTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/Migration/Logging/Log/MigrationLogTest.php b/tests/unit/Migration/Logging/Log/MigrationLogTest.php index 6b992a81d..bda9dc842 100644 --- a/tests/unit/Migration/Logging/Log/MigrationLogTest.php +++ b/tests/unit/Migration/Logging/Log/MigrationLogTest.php @@ -424,7 +424,7 @@ public static function exceptionProvider(): \Generator 'exceptionMessage' => null, 'exceptionTrace' => [['test' => 'trace2']], 'expectedExceptionMessage' => 'Runtime exception occurred in ' . $file . ':' . $line, - 'expectedExceptionTrace' => ['test' => 'trace2'], + 'expectedExceptionTrace' => [['test' => 'trace2']], ]; yield 'with exception, message and trace' => [ @@ -432,7 +432,7 @@ public static function exceptionProvider(): \Generator 'exceptionMessage' => 'Test5', 'exceptionTrace' => [['test' => 'trace3']], 'expectedExceptionMessage' => 'Test5', - 'expectedExceptionTrace' => ['test' => 'trace3'], + 'expectedExceptionTrace' => [['test' => 'trace3']], ]; } }