From 62c4ba641a9eaff0c2a71f6d0f2e6ff5ea306365 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Mon, 3 Aug 2026 17:17:41 +0200 Subject: [PATCH 01/12] ci: add extension-verifier --- .github/workflows/php.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 9be452e..9e5c9a5 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -32,3 +32,20 @@ jobs: with: extension_name: ${{ github.event.repository.name }} platform_branch: trunk + + extension_verifier: + name: Extension Verifier (${{ matrix.check-against }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + check-against: [lowest, highest] + steps: + - name: Checkout SwagMigrationMagento + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Run verifier + uses: shopware/github-actions/extension-verifier@main + with: + action: check + check-against: ${{ matrix.check-against }} From 241accc1c998d191ad72f999a22385d461d92a46 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Mon, 3 Aug 2026 17:23:38 +0200 Subject: [PATCH 02/12] fix: remove phpstan config --- .github/workflows/php.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 9e5c9a5..125f291 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -44,6 +44,9 @@ jobs: - name: Checkout SwagMigrationMagento uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Remove PHPStan config + run: rm phpstan.neon.dist + - name: Run verifier uses: shopware/github-actions/extension-verifier@main with: From 76549b2cf1cfde3e0a1b91c6e72878b65a3b7571 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 14:04:33 +0200 Subject: [PATCH 03/12] chore: resolve extension verifier findings --- .github/workflows/php.yml | 3 ++ .shopware-extension.yml | 8 +++ phpstan-baseline.neon | 20 -------- phpstan.neon.dist | 2 - .../Mapping/MagentoMappingService.php | 5 -- .../Magento/Converter/CustomerConverter.php | 2 +- .../Converter/ManufacturerConverter.php | 8 ++- .../Magento/Converter/OrderConverter.php | 2 +- .../Magento/Converter/ProductConverter.php | 1 + .../Converter/SalesChannelConverter.php | 1 + .../Gateway/Local/Reader/ProductReader.php | 2 +- .../Local/Reader/SalesChannelReader.php | 6 +-- .../Magento/Media/LocalMediaProcessor.php | 42 ++++++++-------- .../Magento/Premapping/AdminStoreReader.php | 4 +- .../Magento2SalesChannelConverter.php | 2 + .../Gateway/Local/Magento2LocalGateway.php | 8 --- .../Local/Reader/Magento2SeoUrlReader.php | 6 +-- .../AbstractReaderTableIntrospectionTest.php | 49 +++++++++++++++++++ .../Gateway/Local/Reader/DummyReader.php | 12 +++++ 19 files changed, 107 insertions(+), 76 deletions(-) create mode 100644 .shopware-extension.yml create mode 100644 tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 125f291..dbe6008 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -47,6 +47,9 @@ jobs: - name: Remove PHPStan config run: rm phpstan.neon.dist + - name: Allow composer to resolve advisory-affected releases + run: composer config policy.advisories.block false + - name: Run verifier uses: shopware/github-actions/extension-verifier@main with: diff --git a/.shopware-extension.yml b/.shopware-extension.yml new file mode 100644 index 0000000..813e3d4 --- /dev/null +++ b/.shopware-extension.yml @@ -0,0 +1,8 @@ +compatibility_date: '2026-08-03' + +validation: + ignore: + # replacement AbstractNumberRangeValueGenerator needs Shopware 6.7.12.0+ + - message: 'NumberRangeValueGeneratorInterface' + # https://github.com/shopware/shopware/issues/13072 + - message: 'XmlFileLoader' diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index b781d55..3dbf087 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -180,11 +180,6 @@ parameters: count: 1 path: src/Profile/Magento/Converter/MagentoConverter.php - - - message: "#^Offset 'translations' on array\\{id\\: mixed, translations\\: array\\} in isset\\(\\) always exists and is not nullable\\.$#" - count: 1 - path: src/Profile/Magento/Converter/ManufacturerConverter.php - - message: "#^Method Swag\\\\MigrationMagento\\\\Profile\\\\Magento\\\\Converter\\\\NewsletterRecipientConverter\\:\\:getSalesChannelMapping\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#" count: 1 @@ -1245,11 +1240,6 @@ parameters: count: 1 path: src/Profile/Magento/Gateway/Local/Reader/SalesChannelReader.php - - - message: "#^Offset 'allowedCountries' on array\\{defaultCountry\\: mixed\\} in isset\\(\\) does not exist\\.$#" - count: 1 - path: src/Profile/Magento/Gateway/Local/Reader/SalesChannelReader.php - - message: "#^Method Swag\\\\MigrationMagento\\\\Profile\\\\Magento\\\\Gateway\\\\Local\\\\Reader\\\\SeoUrlReader\\:\\:fetchSeoUrls\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 @@ -1295,11 +1285,6 @@ parameters: count: 1 path: src/Profile/Magento/Gateway/TableReaderInterface.php - - - message: "#^Call to an undefined method Swag\\\\MigrationMagento\\\\Profile\\\\Magento\\\\Gateway\\\\MagentoGatewayInterface\\:\\:readStores\\(\\)\\.$#" - count: 1 - path: src/Profile/Magento/Premapping/AdminStoreReader.php - - message: "#^Property Swag\\\\MigrationMagento\\\\Profile\\\\Magento\\\\Premapping\\\\PaymentMethodReader\\:\\:\\$preselectionDictionary type has no value type specified in iterable type array\\.$#" count: 1 @@ -1580,11 +1565,6 @@ parameters: count: 1 path: src/Profile/Magento2/Gateway/Local/Reader/Magento2SalesChannelReader.php - - - message: "#^Dead catch \\- Error is never thrown in the try block\\.$#" - count: 1 - path: src/Profile/Magento2/Gateway/Local/Reader/Magento2SeoUrlReader.php - - message: "#^Method Swag\\\\MigrationMagento\\\\Profile\\\\Magento2\\\\Gateway\\\\Local\\\\Reader\\\\Magento2SeoUrlReader\\:\\:fetchSeoUrls\\(\\) return type has no value type specified in iterable type array\\.$#" count: 1 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0714c3f..65312a5 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -34,8 +34,6 @@ parameters: # TODO: XML config format will be migrated step by step, so we ignore errors for now, see https://github.com/shopware/shopware/issues/13072 - message: '#deprecated class Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader#' - # TODO: MagentoMappingServiceInterface extends MappingServiceInterface with additional methods, but parent Converter class types $mappingService as MappingServiceInterface - - message: '#Call to an undefined method SwagMigrationAssistant\\Migration\\Mapping\\MappingServiceInterface::(createListItemMapping|getUuidList)\(\)#' # TODO remove this for 6.8 compatibility - message: '#Access to constant on deprecated class Shopware\\Core\\Checkout\\Payment\\Cart\\PaymentHandler\\DebitPayment#' diff --git a/src/Migration/Mapping/MagentoMappingService.php b/src/Migration/Mapping/MagentoMappingService.php index 333d362..bec7341 100644 --- a/src/Migration/Mapping/MagentoMappingService.php +++ b/src/Migration/Mapping/MagentoMappingService.php @@ -20,7 +20,6 @@ use Shopware\Core\System\Tax\TaxCollection; use SwagMigrationAssistant\Migration\Mapping\MappingService; use SwagMigrationAssistant\Migration\Mapping\SwagMigrationMappingCollection; -use SwagMigrationAssistant\Migration\Mapping\SwagMigrationMappingEntity; #[Package('fundamentals@after-sales')] class MagentoMappingService extends MappingService implements MagentoMappingServiceInterface @@ -98,10 +97,6 @@ public function getUuidList(string $connectionId, string $entityName, string $id $uuidList = []; foreach ($result as $entity) { - if (!$entity instanceof SwagMigrationMappingEntity) { - continue; - } - $entityId = $entity->getEntityId(); if ($entityId === null) { diff --git a/src/Profile/Magento/Converter/CustomerConverter.php b/src/Profile/Magento/Converter/CustomerConverter.php index da8bc24..b8ebfc7 100644 --- a/src/Profile/Magento/Converter/CustomerConverter.php +++ b/src/Profile/Magento/Converter/CustomerConverter.php @@ -182,7 +182,7 @@ public function convert(array $data, Context $context, MigrationContextInterface $context ); - if (\is_array($mapping) && \array_key_exists('entityId', $mapping)) { + if (\is_array($mapping)) { $converted['groupId'] = $mapping['entityId']; unset($data['group_id']); } diff --git a/src/Profile/Magento/Converter/ManufacturerConverter.php b/src/Profile/Magento/Converter/ManufacturerConverter.php index d05c407..9b2971e 100644 --- a/src/Profile/Magento/Converter/ManufacturerConverter.php +++ b/src/Profile/Magento/Converter/ManufacturerConverter.php @@ -75,12 +75,10 @@ public function convert(array $data, Context $context, MigrationContextInterface $context ); - if (isset($converted['translations'])) { - foreach ($converted['translations'] as &$translation) { - $translation['manufacturerId'] = $converted['id']; - } - unset($translation); + foreach ($converted['translations'] as &$translation) { + $translation['manufacturerId'] = $converted['id']; } + unset($translation); } unset($data['translations']); diff --git a/src/Profile/Magento/Converter/OrderConverter.php b/src/Profile/Magento/Converter/OrderConverter.php index 4c47904..daeb1bc 100644 --- a/src/Profile/Magento/Converter/OrderConverter.php +++ b/src/Profile/Magento/Converter/OrderConverter.php @@ -753,7 +753,7 @@ protected function convertOrderCustomer(array &$converted, array &$data): bool $this->context ); - if ($mapping !== null && isset($mapping['entityId'], $mapping['id'])) { + if ($mapping !== null && isset($mapping['entityId'])) { $this->mappingIds[] = $mapping['id']; $this->salutationUuid = $mapping['entityId']; } diff --git a/src/Profile/Magento/Converter/ProductConverter.php b/src/Profile/Magento/Converter/ProductConverter.php index 5bd5281..28781ba 100644 --- a/src/Profile/Magento/Converter/ProductConverter.php +++ b/src/Profile/Magento/Converter/ProductConverter.php @@ -785,6 +785,7 @@ private function setDefaultStoreVisibility(int $status, string $productId, array return; } + \assert($this->mappingService instanceof MagentoMappingServiceInterface); $uuids = $this->mappingService->getUuidList( $this->connectionId, MagentoDefaultEntities::STORE_DEFAULT, diff --git a/src/Profile/Magento/Converter/SalesChannelConverter.php b/src/Profile/Magento/Converter/SalesChannelConverter.php index 8c3c96b..ed58f55 100644 --- a/src/Profile/Magento/Converter/SalesChannelConverter.php +++ b/src/Profile/Magento/Converter/SalesChannelConverter.php @@ -132,6 +132,7 @@ public function convert(array $data, Context $context, MigrationContextInterface $this->mappingIds[] = $mapping['id']; } + \assert($this->mappingService instanceof MagentoMappingServiceInterface); $this->mappingService->createListItemMapping( $this->connectionId, MagentoDefaultEntities::STORE_DEFAULT, diff --git a/src/Profile/Magento/Gateway/Local/Reader/ProductReader.php b/src/Profile/Magento/Gateway/Local/Reader/ProductReader.php index 83c81ec..9f49ea4 100644 --- a/src/Profile/Magento/Gateway/Local/Reader/ProductReader.php +++ b/src/Profile/Magento/Gateway/Local/Reader/ProductReader.php @@ -268,7 +268,7 @@ protected function appendAssociatedData(array $fetchedProducts, array $ids): arr $media = $this->fetchProductMedia($ids); $prices = $this->fetchProductPrices($ids); $configuratorSettings = $this->fetchConfiguratorSettings(); - unset($this->combinedProductIds); + $this->combinedProductIds = null; $visibility = $this->fetchVisibility($ids); $locales = $this->fetchLocales(); diff --git a/src/Profile/Magento/Gateway/Local/Reader/SalesChannelReader.php b/src/Profile/Magento/Gateway/Local/Reader/SalesChannelReader.php index c2b0b76..cf786c4 100644 --- a/src/Profile/Magento/Gateway/Local/Reader/SalesChannelReader.php +++ b/src/Profile/Magento/Gateway/Local/Reader/SalesChannelReader.php @@ -373,11 +373,7 @@ protected function fetchStoreCountryConfig(array $storeIds): array $storeCountryConfig = []; foreach ($configurations as $key => $storeConfig) { foreach ($storeConfig as $config) { - if ($config['path'] === 'general/country/allow') { - if (isset($storeCountryConfig[$key]['allowedCountries'])) { - $storeCountryConfig[$key]['allowedCountries'] = \explode(',', (string) $config['value']); - } - } else { + if ($config['path'] !== 'general/country/allow') { $storeCountryConfig[$key]['defaultCountry'] = $config['value']; } } diff --git a/src/Profile/Magento/Media/LocalMediaProcessor.php b/src/Profile/Magento/Media/LocalMediaProcessor.php index 32bb11d..a95d285 100644 --- a/src/Profile/Magento/Media/LocalMediaProcessor.php +++ b/src/Profile/Magento/Media/LocalMediaProcessor.php @@ -232,9 +232,9 @@ private function copyMediaFiles( $sourcePath = $this->getInstallationRoot($migrationContext) . $mappedWorkload[$mediaId]->getAdditionalData()['path']; $fileExtension = \pathinfo($sourcePath, \PATHINFO_EXTENSION); - $filePath = \tempnam(\sys_get_temp_dir(), 'SwagMigrationMagento-'); + $tempFilePath = \tempnam(\sys_get_temp_dir(), 'SwagMigrationMagento-'); - if ($filePath === false) { + if ($tempFilePath === false) { $failureUuids[] = (string) $mediaId; $mappedWorkload[$mediaId]->setState(MediaProcessWorkloadStruct::ERROR_STATE); @@ -256,16 +256,16 @@ private function copyMediaFiles( continue; } - if (\copy($sourcePath, $filePath)) { + if (\copy($sourcePath, $tempFilePath)) { try { - $fileSize = \filesize($filePath); + $fileSize = \filesize($tempFilePath); if ($fileSize === false) { - throw MigrationMagentoException::mediaFileSizeError($filePath); + throw MigrationMagentoException::mediaFileSizeError($tempFilePath); } $mappedWorkload[$mediaId]->setState(MediaProcessWorkloadStruct::FINISH_STATE); $this->persistFileToMedia( - $filePath, + $tempFilePath, $mediaId, $mediaFile['file_name'], $fileSize, @@ -284,7 +284,7 @@ private function copyMediaFiles( ->withException($e) ->withConvertedData( [ - 'file_path' => $filePath, + 'file_path' => $tempFilePath, 'source_path' => $sourcePath, 'media' => $media, ] @@ -294,7 +294,7 @@ private function copyMediaFiles( ); } - \unlink($filePath); + \unlink($tempFilePath); } else { $mappedWorkload[$mediaId]->setState(MediaProcessWorkloadStruct::ERROR_STATE); @@ -304,7 +304,7 @@ private function copyMediaFiles( ->withSourceData($mediaFile) ->withConvertedData( [ - 'file_path' => $filePath, + 'file_path' => $tempFilePath, 'source_path' => $sourcePath, 'media' => $media, ] @@ -329,7 +329,7 @@ private function copyMediaFiles( * @param list $failedMedia */ private function persistFileToMedia( - string $filePath, + string $tempFilePath, string $mediaId, string $fileName, int $fileSize, @@ -338,7 +338,7 @@ private function persistFileToMedia( array &$failedMedia, Context $context, ): void { - $mimeType = \mime_content_type($filePath); + $mimeType = \mime_content_type($tempFilePath); if ($mimeType === false) { $failedMedia[] = $mediaId; @@ -350,7 +350,7 @@ private function persistFileToMedia( ->withFieldName('mimeType') ->withConvertedData( [ - 'file_path' => $filePath, + 'file_path' => $tempFilePath, 'media_id' => $mediaId, 'file_name' => $fileName, 'file_size' => $fileSize, @@ -364,7 +364,7 @@ private function persistFileToMedia( return; } - $mediaFile = new MediaFile($filePath, $mimeType, $fileExtension, $fileSize); + $mediaFile = new MediaFile($tempFilePath, $mimeType, $fileExtension, $fileSize); $fileName = (string) \preg_replace('/[^a-z0-9_-]+/', '-', \mb_strtolower($fileName)); try { @@ -464,8 +464,8 @@ static function (MediaProcessWorkloadStruct $work) use ($uuid) { /** @var Response $response */ $response = $result['value']; $fileExtension = \pathinfo($additionalData['uri'], \PATHINFO_EXTENSION); - $filePath = \tempnam(\sys_get_temp_dir(), 'SwagMigrationMagento-'); - if ($filePath === false) { + $tempFilePath = \tempnam(\sys_get_temp_dir(), 'SwagMigrationMagento-'); + if ($tempFilePath === false) { $failureUuids[] = $uuid; $mappedWorkload[$uuid]->setState(MediaProcessWorkloadStruct::ERROR_STATE); @@ -493,7 +493,7 @@ static function (MediaProcessWorkloadStruct $work) use ($uuid) { ], ]); - $fileHandle = \fopen($filePath, 'ab', false, $streamContext); + $fileHandle = \fopen($tempFilePath, 'ab', false, $streamContext); if ($fileHandle === false) { $failureUuids[] = $uuid; $mappedWorkload[$uuid]->setState(MediaProcessWorkloadStruct::ERROR_STATE); @@ -503,7 +503,7 @@ static function (MediaProcessWorkloadStruct $work) use ($uuid) { ->withEntityName(MediaDefinition::ENTITY_NAME) ->withSourceData([ 'media_id' => $uuid, - 'source_path' => $filePath, + 'source_path' => $tempFilePath, 'media' => $media, ]) ->withEntityId($uuid) @@ -513,13 +513,13 @@ static function (MediaProcessWorkloadStruct $work) use ($uuid) { continue; } \fwrite($fileHandle, $response->getBody()->getContents()); - $fileSize = (int) \filesize($filePath); + $fileSize = (int) \filesize($tempFilePath); \fclose($fileHandle); if ($mappedWorkload[$uuid]->getState() === MediaProcessWorkloadStruct::FINISH_STATE) { try { $this->persistFileToMedia( - $filePath, + $tempFilePath, $uuid, $additionalData['file_name'], $fileSize, @@ -528,7 +528,7 @@ static function (MediaProcessWorkloadStruct $work) use ($uuid) { $failureUuids, $context ); - \unlink($filePath); + \unlink($tempFilePath); $finishedUuids[] = $uuid; } catch (\Exception $e) { $failureUuids[] = $uuid; @@ -540,7 +540,7 @@ static function (MediaProcessWorkloadStruct $work) use ($uuid) { ->withException($e) ->withConvertedData( [ - 'file_path' => $filePath, + 'file_path' => $tempFilePath, 'uri' => $additionalData['uri'], 'media_id' => $uuid, 'media' => $media, diff --git a/src/Profile/Magento/Premapping/AdminStoreReader.php b/src/Profile/Magento/Premapping/AdminStoreReader.php index 8aaa362..0493c00 100644 --- a/src/Profile/Magento/Premapping/AdminStoreReader.php +++ b/src/Profile/Magento/Premapping/AdminStoreReader.php @@ -11,8 +11,8 @@ use Shopware\Core\Framework\Log\Package; use Swag\MigrationMagento\Profile\Magento\DataSelection\CustomerAndOrderDataSelection; use Swag\MigrationMagento\Profile\Magento\DataSelection\ProductReviewDataSelection; -use Swag\MigrationMagento\Profile\Magento\Gateway\MagentoGatewayInterface; use Swag\MigrationMagento\Profile\Magento\MagentoProfileInterface; +use Swag\MigrationMagento\Profile\Magento2\Gateway\Local\Magento2LocalGateway; use SwagMigrationAssistant\Migration\Gateway\GatewayRegistryInterface; use SwagMigrationAssistant\Migration\MigrationContextInterface; use SwagMigrationAssistant\Migration\Premapping\AbstractPremappingReader; @@ -91,7 +91,7 @@ private function getMapping(): array */ private function getChoices(MigrationContextInterface $migrationContext): array { - /** @var MagentoGatewayInterface $gateway */ + /** @var Magento2LocalGateway $gateway */ $gateway = $this->gatewayRegistry->getGateway($migrationContext); $stores = $gateway->readStores($migrationContext); diff --git a/src/Profile/Magento2/Converter/Magento2SalesChannelConverter.php b/src/Profile/Magento2/Converter/Magento2SalesChannelConverter.php index 450e7bb..ebcb761 100644 --- a/src/Profile/Magento2/Converter/Magento2SalesChannelConverter.php +++ b/src/Profile/Magento2/Converter/Magento2SalesChannelConverter.php @@ -12,6 +12,7 @@ use Shopware\Core\Framework\Context; use Shopware\Core\Framework\Log\Package; use Shopware\Core\System\SalesChannel\SalesChannelDefinition; +use Swag\MigrationMagento\Migration\Mapping\MagentoMappingServiceInterface; use Swag\MigrationMagento\Profile\Magento\Converter\SalesChannelConverter; use Swag\MigrationMagento\Profile\Magento\DataSelection\DefaultEntities as MagentoDefaultEntities; use Swag\MigrationMagento\Profile\Magento\Premapping\PaymentMethodReader; @@ -140,6 +141,7 @@ protected function setStores(array &$data, array &$converted): void $this->mappingIds[] = $mapping['id']; } + \assert($this->mappingService instanceof MagentoMappingServiceInterface); $this->mappingService->createListItemMapping( $this->connectionId, MagentoDefaultEntities::STORE_DEFAULT, diff --git a/src/Profile/Magento2/Gateway/Local/Magento2LocalGateway.php b/src/Profile/Magento2/Gateway/Local/Magento2LocalGateway.php index d12612f..78de80d 100644 --- a/src/Profile/Magento2/Gateway/Local/Magento2LocalGateway.php +++ b/src/Profile/Magento2/Gateway/Local/Magento2LocalGateway.php @@ -196,18 +196,10 @@ public function readPayments(MigrationContextInterface $migrationContext): array unset($orderPayments[$key]); } - if (isset($result[$key])) { - continue; - } - $result[] = $payment; } foreach ($orderPayments as $key => $payment) { - if (isset($result[$key])) { - continue; - } - $result[] = $payment; } diff --git a/src/Profile/Magento2/Gateway/Local/Reader/Magento2SeoUrlReader.php b/src/Profile/Magento2/Gateway/Local/Reader/Magento2SeoUrlReader.php index 933aa9a..5fef832 100644 --- a/src/Profile/Magento2/Gateway/Local/Reader/Magento2SeoUrlReader.php +++ b/src/Profile/Magento2/Gateway/Local/Reader/Magento2SeoUrlReader.php @@ -95,11 +95,7 @@ protected function setProductData(array &$seoUrl): void return; } - try { - $json = \json_decode($seoUrl['metadata'], true); - } catch (\Error) { - return; - } + $json = \json_decode($seoUrl['metadata'], true); if (isset($json['category_id'])) { $seoUrl['category_id'] = $json['category_id']; diff --git a/tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php b/tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php new file mode 100644 index 0000000..9741faa --- /dev/null +++ b/tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php @@ -0,0 +1,49 @@ + + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Swag\MigrationMagento\Test\Profile\Magento\Gateway\Local\Reader; + +use Doctrine\DBAL\Connection; +use PHPUnit\Framework\TestCase; +use Shopware\Core\Framework\Log\Package; +use Shopware\Core\Framework\Test\TestCaseBase\KernelTestBehaviour; +use Swag\MigrationMagento\Profile\Magento\Gateway\Connection\ConnectionFactory; + +/** + * Executes the schema introspection of addTableSelection() against a live database, + * so a CI run on the lowest supported platform fails when the used DBAL API does not + * exist in the DBAL version that platform ships. + * + * @internal + */ +#[Package('fundamentals@after-sales')] +class AbstractReaderTableIntrospectionTest extends TestCase +{ + use KernelTestBehaviour; + + public function testAddTableSelectionSelectsEveryColumnOfTheIntrospectedTable(): void + { + $connection = $this->getContainer()->get(Connection::class); + static::assertInstanceOf(Connection::class, $connection); + + $reader = new DummyReader(new ConnectionFactory()); + $reader->setDatabaseConnection($connection); + + $query = $connection->createQueryBuilder(); + $query->from('language', 'lang'); + $reader->testAddTableSelection($query, 'language', 'lang'); + + $sql = $query->getSQL(); + static::assertStringContainsString('`lang`.`id` AS `lang.id`', $sql); + static::assertStringContainsString('`lang`.`name` AS `lang.name`', $sql); + + $rows = $query->executeQuery()->fetchAllAssociative(); + static::assertNotEmpty($rows); + static::assertArrayHasKey('lang.id', $rows[0]); + static::assertArrayHasKey('lang.name', $rows[0]); + } +} diff --git a/tests/Profile/Magento/Gateway/Local/Reader/DummyReader.php b/tests/Profile/Magento/Gateway/Local/Reader/DummyReader.php index 8d8c7de..d732990 100644 --- a/tests/Profile/Magento/Gateway/Local/Reader/DummyReader.php +++ b/tests/Profile/Magento/Gateway/Local/Reader/DummyReader.php @@ -7,6 +7,8 @@ namespace Swag\MigrationMagento\Test\Profile\Magento\Gateway\Local\Reader; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Query\QueryBuilder; use Shopware\Core\Framework\Log\Package; use Swag\MigrationMagento\Profile\Magento\Gateway\Local\Reader\AbstractReader; use SwagMigrationAssistant\Migration\MigrationContextInterface; @@ -36,4 +38,14 @@ public function testUtf8ize(array $array): array { return $this->utf8ize($array); } + + public function setDatabaseConnection(Connection $connection): void + { + $this->connection = $connection; + } + + public function testAddTableSelection(QueryBuilder $query, string $table, string $tableAlias): void + { + $this->addTableSelection($query, $table, $tableAlias); + } } From 4b6d18dc5361b3671fc68cc3bde570ab67b61dd3 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 14:33:27 +0200 Subject: [PATCH 04/12] fix: avoid `introspectTableByUnquotedName` --- phpstan.neon.dist | 4 ++++ src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 65312a5..41974fe 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -34,6 +34,10 @@ parameters: # TODO: XML config format will be migrated step by step, so we ignore errors for now, see https://github.com/shopware/shopware/issues/13072 - message: '#deprecated class Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader#' + # TODO remove once the minimum supported Shopware version ships DBAL 4.4 (>= 6.7.8.0) + - message: '#Call to deprecated method introspectTable\(\) of class Doctrine\\DBAL\\Schema\\AbstractSchemaManager#' + paths: + - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php # TODO remove this for 6.8 compatibility - message: '#Access to constant on deprecated class Shopware\\Core\\Checkout\\Payment\\Cart\\PaymentHandler\\DebitPayment#' diff --git a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php index 578eec7..5a86c19 100644 --- a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php +++ b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php @@ -88,7 +88,8 @@ protected function addTableSelection(QueryBuilder $query, string $table, string return; } - $table = $this->connection->createSchemaManager()->introspectTableByUnquotedName($table); + // replacement introspectTableByUnquotedName() needs DBAL 4.4+ (Shopware 6.7.8.0+) + $table = $this->connection->createSchemaManager()->introspectTable($table); foreach ($table->getColumns() as $column) { $columnName = $column->getObjectName()->toString(); From 8de245a9e4437a13de3918fc66a67472fd744813 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 14:42:13 +0200 Subject: [PATCH 05/12] fix: avoid `getObjectName` --- .shopware-extension.yml | 2 ++ CHANGELOG.md | 4 ++++ CHANGELOG_de-DE.md | 4 ++++ composer.json | 2 +- phpstan.neon.dist | 3 +++ src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php | 3 ++- 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.shopware-extension.yml b/.shopware-extension.yml index 813e3d4..95e3cd4 100644 --- a/.shopware-extension.yml +++ b/.shopware-extension.yml @@ -6,3 +6,5 @@ validation: - message: 'NumberRangeValueGeneratorInterface' # https://github.com/shopware/shopware/issues/13072 - message: 'XmlFileLoader' + # replacement getObjectName() needs DBAL 4.3+ (Shopware 6.7.0/6.7.1 can lock DBAL 4.2) + - message: 'getName() of deprecated class Doctrine\DBAL\Schema\AbstractAsset' diff --git a/CHANGELOG.md b/CHANGELOG.md index 9792823..3fc1c7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 13.0.1 + +- Fixed a fatal error that broke reading data via the local gateway on Shopware versions below 6.7.8.0. + # 13.0.0 - Compatibility with Migration Assistant version 18. diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md index 764fbc3..fb8eb93 100644 --- a/CHANGELOG_de-DE.md +++ b/CHANGELOG_de-DE.md @@ -1,3 +1,7 @@ +# 13.0.1 + +- Ein fataler Fehler wurde behoben, der das Lesen von Daten über das lokale Gateway auf Shopware-Versionen unter 6.7.8.0 verhindert hat. + # 13.0.0 - Kompatibilität mit Migration Assistant Version 18. diff --git a/composer.json b/composer.json index 00cca16..20af725 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "swag/migration-magento", "description": "Magento profiles for Shopware Migration Assistant", - "version": "13.0.0", + "version": "13.0.1", "type": "shopware-platform-plugin", "license": "MIT", "authors": [ diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 41974fe..ab1e618 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -38,6 +38,9 @@ parameters: - message: '#Call to deprecated method introspectTable\(\) of class Doctrine\\DBAL\\Schema\\AbstractSchemaManager#' paths: - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php + - message: '#Call to deprecated method getName\(\) of class Doctrine\\DBAL\\Schema\\AbstractAsset#' + paths: + - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php # TODO remove this for 6.8 compatibility - message: '#Access to constant on deprecated class Shopware\\Core\\Checkout\\Payment\\Cart\\PaymentHandler\\DebitPayment#' diff --git a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php index 5a86c19..1de1d04 100644 --- a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php +++ b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php @@ -92,7 +92,8 @@ protected function addTableSelection(QueryBuilder $query, string $table, string $table = $this->connection->createSchemaManager()->introspectTable($table); foreach ($table->getColumns() as $column) { - $columnName = $column->getObjectName()->toString(); + // replacement getObjectName() needs DBAL 4.3+ (Shopware 6.7.0/6.7.1 can lock DBAL 4.2) + $columnName = $column->getName(); $columnName = trim($columnName, '"`'); From 9d3cc1115bda856e3a23cd2d406283564a639dea Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 14:59:12 +0200 Subject: [PATCH 06/12] ci: drop highest verifier --- .github/workflows/php.yml | 8 ++------ .shopware-extension.yml | 10 ---------- 2 files changed, 2 insertions(+), 16 deletions(-) delete mode 100644 .shopware-extension.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index dbe6008..6e9427a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -34,12 +34,8 @@ jobs: platform_branch: trunk extension_verifier: - name: Extension Verifier (${{ matrix.check-against }}) + name: Extension Verifier (lowest) runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - check-against: [lowest, highest] steps: - name: Checkout SwagMigrationMagento uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -54,4 +50,4 @@ jobs: uses: shopware/github-actions/extension-verifier@main with: action: check - check-against: ${{ matrix.check-against }} + check-against: lowest diff --git a/.shopware-extension.yml b/.shopware-extension.yml deleted file mode 100644 index 95e3cd4..0000000 --- a/.shopware-extension.yml +++ /dev/null @@ -1,10 +0,0 @@ -compatibility_date: '2026-08-03' - -validation: - ignore: - # replacement AbstractNumberRangeValueGenerator needs Shopware 6.7.12.0+ - - message: 'NumberRangeValueGeneratorInterface' - # https://github.com/shopware/shopware/issues/13072 - - message: 'XmlFileLoader' - # replacement getObjectName() needs DBAL 4.3+ (Shopware 6.7.0/6.7.1 can lock DBAL 4.2) - - message: 'getName() of deprecated class Doctrine\DBAL\Schema\AbstractAsset' From c6f29b0c55d782a3c602d7b14b52533dfdd5726b Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 15:01:21 +0200 Subject: [PATCH 07/12] test: reintroduce issues --- phpstan.neon.dist | 8 -------- .../Magento/Gateway/Local/Reader/AbstractReader.php | 6 ++---- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ab1e618..02a8935 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -34,14 +34,6 @@ parameters: # TODO: XML config format will be migrated step by step, so we ignore errors for now, see https://github.com/shopware/shopware/issues/13072 - message: '#deprecated class Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader#' - # TODO remove once the minimum supported Shopware version ships DBAL 4.4 (>= 6.7.8.0) - - message: '#Call to deprecated method introspectTable\(\) of class Doctrine\\DBAL\\Schema\\AbstractSchemaManager#' - paths: - - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php - - message: '#Call to deprecated method getName\(\) of class Doctrine\\DBAL\\Schema\\AbstractAsset#' - paths: - - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php - # TODO remove this for 6.8 compatibility - message: '#Access to constant on deprecated class Shopware\\Core\\Checkout\\Payment\\Cart\\PaymentHandler\\DebitPayment#' paths: diff --git a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php index 1de1d04..578eec7 100644 --- a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php +++ b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php @@ -88,12 +88,10 @@ protected function addTableSelection(QueryBuilder $query, string $table, string return; } - // replacement introspectTableByUnquotedName() needs DBAL 4.4+ (Shopware 6.7.8.0+) - $table = $this->connection->createSchemaManager()->introspectTable($table); + $table = $this->connection->createSchemaManager()->introspectTableByUnquotedName($table); foreach ($table->getColumns() as $column) { - // replacement getObjectName() needs DBAL 4.3+ (Shopware 6.7.0/6.7.1 can lock DBAL 4.2) - $columnName = $column->getName(); + $columnName = $column->getObjectName()->toString(); $columnName = trim($columnName, '"`'); From f3a64487f52898e43102740f6304fed1acda0081 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 15:23:12 +0200 Subject: [PATCH 08/12] Revert "test: reintroduce issues" This reverts commit c6f29b0c55d782a3c602d7b14b52533dfdd5726b. --- phpstan.neon.dist | 8 ++++++++ .../Magento/Gateway/Local/Reader/AbstractReader.php | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 02a8935..ab1e618 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -34,6 +34,14 @@ parameters: # TODO: XML config format will be migrated step by step, so we ignore errors for now, see https://github.com/shopware/shopware/issues/13072 - message: '#deprecated class Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader#' + # TODO remove once the minimum supported Shopware version ships DBAL 4.4 (>= 6.7.8.0) + - message: '#Call to deprecated method introspectTable\(\) of class Doctrine\\DBAL\\Schema\\AbstractSchemaManager#' + paths: + - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php + - message: '#Call to deprecated method getName\(\) of class Doctrine\\DBAL\\Schema\\AbstractAsset#' + paths: + - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php + # TODO remove this for 6.8 compatibility - message: '#Access to constant on deprecated class Shopware\\Core\\Checkout\\Payment\\Cart\\PaymentHandler\\DebitPayment#' paths: diff --git a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php index 578eec7..1de1d04 100644 --- a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php +++ b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php @@ -88,10 +88,12 @@ protected function addTableSelection(QueryBuilder $query, string $table, string return; } - $table = $this->connection->createSchemaManager()->introspectTableByUnquotedName($table); + // replacement introspectTableByUnquotedName() needs DBAL 4.4+ (Shopware 6.7.8.0+) + $table = $this->connection->createSchemaManager()->introspectTable($table); foreach ($table->getColumns() as $column) { - $columnName = $column->getObjectName()->toString(); + // replacement getObjectName() needs DBAL 4.3+ (Shopware 6.7.0/6.7.1 can lock DBAL 4.2) + $columnName = $column->getName(); $columnName = trim($columnName, '"`'); From f34e02f0d1baf668f8ede98b61883584f3087fb5 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 16:12:08 +0200 Subject: [PATCH 09/12] chore: fix test docblock --- .../Local/Reader/AbstractReaderTableIntrospectionTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php b/tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php index 9741faa..d488b7d 100644 --- a/tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php +++ b/tests/Profile/Magento/Gateway/Local/Reader/AbstractReaderTableIntrospectionTest.php @@ -14,9 +14,7 @@ use Swag\MigrationMagento\Profile\Magento\Gateway\Connection\ConnectionFactory; /** - * Executes the schema introspection of addTableSelection() against a live database, - * so a CI run on the lowest supported platform fails when the used DBAL API does not - * exist in the DBAL version that platform ships. + * Executes the schema introspection against a real database to catch DBAL API drift at runtime. * * @internal */ From 52a912a51b4d6c5910ba8ba8e2fcf98222bb53e9 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 4 Aug 2026 16:18:26 +0200 Subject: [PATCH 10/12] chore: `drop setAccessible` --- tests/Profile/Magento/Media/LocalMediaProcessorTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Profile/Magento/Media/LocalMediaProcessorTest.php b/tests/Profile/Magento/Media/LocalMediaProcessorTest.php index 410ada3..bfaed44 100644 --- a/tests/Profile/Magento/Media/LocalMediaProcessorTest.php +++ b/tests/Profile/Magento/Media/LocalMediaProcessorTest.php @@ -68,7 +68,6 @@ public function testCopyMediaFiles(): void $mediaProcessor = $this->createLocaleMediaProcessor($mediaFiles); $reflectionMethod = (new \ReflectionClass(LocalMediaProcessor::class))->getMethod('copyMediaFiles'); - $reflectionMethod->setAccessible(true); $result = $reflectionMethod->invokeArgs($mediaProcessor, [$mediaFiles, $mappedWorkload, $migrationContext, Context::createDefaultContext()]); foreach ($result as $workload) { From 01a3a2ac3a886ca312cfef13bafd1ced7f06817d Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Tue, 11 Aug 2026 15:38:47 +0200 Subject: [PATCH 11/12] chore: reword deprecation comment --- src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php index 1de1d04..a51e8fc 100644 --- a/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php +++ b/src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php @@ -88,11 +88,11 @@ protected function addTableSelection(QueryBuilder $query, string $table, string return; } - // replacement introspectTableByUnquotedName() needs DBAL 4.4+ (Shopware 6.7.8.0+) + // introspectTable() is deprecated, but introspectTableByUnquotedName() needs DBAL 4.4+ (Shopware 6.7.8.0+) $table = $this->connection->createSchemaManager()->introspectTable($table); foreach ($table->getColumns() as $column) { - // replacement getObjectName() needs DBAL 4.3+ (Shopware 6.7.0/6.7.1 can lock DBAL 4.2) + // getName() is deprecated, but getObjectName() needs DBAL 4.3+ (Shopware 6.7.0/6.7.1 can lock DBAL 4.2) $columnName = $column->getName(); $columnName = trim($columnName, '"`'); From 4be332edd82f0bb929eb790c0f67561377f8d498 Mon Sep 17 00:00:00 2001 From: Nikolas Evers Date: Fri, 14 Aug 2026 11:52:18 +0200 Subject: [PATCH 12/12] chore: replace `ReturnTypeNarrowing` with a deprecation annotation --- phpstan.neon.dist | 7 +++++++ src/Profile/Magento/Media/LocalMediaProcessor.php | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ab1e618..28debbe 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -42,6 +42,13 @@ parameters: paths: - src/Profile/Magento/Gateway/Local/Reader/AbstractReader.php + # TODO remove once the minimum supported Shopware version ships the `BCChange` attributes + - identifier: shopware.bcPlanningDeprecation + - identifier: shopware.deprecatedMethod + paths: + - src/Profile/Magento/Media/LocalMediaProcessor.php + - message: '#Call to deprecated method doNormalDownloadRequest\(\) of class Swag\\MigrationMagento\\Profile\\Magento\\Media\\LocalMediaProcessor#' + # TODO remove this for 6.8 compatibility - message: '#Access to constant on deprecated class Shopware\\Core\\Checkout\\Payment\\Cart\\PaymentHandler\\DebitPayment#' paths: diff --git a/src/Profile/Magento/Media/LocalMediaProcessor.php b/src/Profile/Magento/Media/LocalMediaProcessor.php index f499625..a64c6d8 100644 --- a/src/Profile/Magento/Media/LocalMediaProcessor.php +++ b/src/Profile/Magento/Media/LocalMediaProcessor.php @@ -20,7 +20,6 @@ use Shopware\Core\Content\Media\MediaException; use Shopware\Core\Framework\Context; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; -use Shopware\Core\Framework\Deprecation\BCChange\ReturnTypeNarrowing; use Shopware\Core\Framework\Log\Package; use Shopware\Core\Framework\Uuid\Uuid; use Swag\MigrationMagento\Exception\MigrationMagentoException; @@ -156,7 +155,9 @@ protected function doMediaDownloadRequests(array $media, array &$mappedWorkload, return $promises; } - #[ReturnTypeNarrowing(version: 'v14.0.0', newType: Promise\PromiseInterface::class)] + /** + * @deprecated tag:v14.0.0 - reason:return-type-change - Will return Promise\PromiseInterface + */ protected function doNormalDownloadRequest(MediaProcessWorkloadStruct $workload, Client $client): ?Promise\PromiseInterface { $additionalData = $workload->getAdditionalData();