Skip to content

Creating a New Migration Profile

Creating a New Migration Profile

If you want to migrate your data from a different source system than Shopware, create a new migration profile for the Migration Assistant. But if you want to convert your plugin data from a Shopware system to Shopware 6, look at this article on Extending a Shopware Migration Profile.

Setup

First of all, it is required that you already have installed the Migration Assistant plugin in Shopware 6 and have created a demo source system database with a product table. To create the table, use this SQL statement:

sql
CREATE TABLE product
(
  id int PRIMARY KEY NOT NULL AUTO_INCREMENT,
  product_number varchar(255) NOT NULL,
  price float NOT NULL,
  stock int NOT NULL,
  product_name varchar(255) NOT NULL,
  tax float NOT NULL
);

This table should simulate a simple third-party source system, which should be migrated in the following steps.

Creating a profile

In the first step, you have to create a new profile for your source system:

php
<?php declare(strict_types=1);

namespace SwagMigrationOwnProfileExample;

use SwagMigrationAssistant\Migration\Profile\ProfileInterface;

class OwnProfile implements ProfileInterface
{
    public const PROFILE_NAME = 'ownProfile';

    public const SOURCE_SYSTEM_NAME = 'MySourceSystem';

    public const SOURCE_SYSTEM_VERSION = '1.0';

    public const AUTHOR_NAME = 'shopware AG';

    public const ICON_PATH = '/swagmigrationassistant/static/img/migration-assistant-plugin.svg';

    public function getName(): string
    {
        return self::PROFILE_NAME;
    }

    public function getSourceSystemName(): string
    {
        return self::SOURCE_SYSTEM_NAME;
    }

    public function getVersion(): string
    {
        return self::SOURCE_SYSTEM_VERSION;
    }

    public function getAuthorName(): string
    {
        return self::AUTHOR_NAME;
    }

    public function getIconPath(): string
    {
        return self::ICON_PATH;
    }
}

The profile itself does not contain any logic and is used to bundle the executing classes. To use this profile, you have to register and tag it in the service.xml with shopware.migration.profile:

html
<service id="SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile">
    <tag name="shopware.migration.profile"/>
</service>

Creating a gateway

Next, you have to create a new gateway which supports your profile:

php
<?php declare(strict_types=1);

namespace SwagMigrationOwnProfileExample\Profile\OwnProfile\Gateway;

use Shopware\Core\Framework\Context;
use SwagMigrationAssistant\Migration\EnvironmentInformation;
use SwagMigrationAssistant\Migration\Gateway\GatewayInterface;
use SwagMigrationAssistant\Migration\Gateway\Reader\ReaderRegistry;
use SwagMigrationAssistant\Migration\MigrationContextInterface;
use SwagMigrationAssistant\Migration\RequestStatusStruct;
use SwagMigrationAssistant\Profile\Shopware\Exception\DatabaseConnectionException;
use SwagMigrationAssistant\Profile\Shopware\Gateway\Connection\ConnectionFactoryInterface;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile;

class OwnLocaleGateway implements GatewayInterface
{
    public const GATEWAY_NAME = 'local';

    private ConnectionFactoryInterface $connectionFactory;

    private ReaderRegistry $readerRegistry;

    public function __construct(
        ReaderRegistry $readerRegistry,
        ConnectionFactoryInterface $connectionFactory
    ) {
        $this->readerRegistry = $readerRegistry;
        $this->connectionFactory = $connectionFactory;
    }

    public function getName(): string
    {
        return self::GATEWAY_NAME;
    }

    public function supports(MigrationContextInterface $migrationContext): bool
    {
        return $migrationContext->getProfile() instanceof OwnProfile;
    }

    public function getSnippetName(): string
    {
        return 'swag-migration.wizard.pages.connectionCreate.gateways.shopwareLocal';
    }

    /**
     * Reads the given entity type from via context from its connection and returns the data
     */
    public function read(MigrationContextInterface $migrationContext): array
    {
        // TODO: Implement read() method.
        return [];
    }

    public function readEnvironmentInformation(
        MigrationContextInterface $migrationContext,
        Context $context
    ): EnvironmentInformation {
        $connection = $this->connectionFactory->createDatabaseConnection($migrationContext);
        $profile = $migrationContext->getProfile();

        try {
            $connection->connect();
        } catch (\Exception $e) {
            $error = new DatabaseConnectionException();

            return new EnvironmentInformation(
                $profile->getSourceSystemName(),
                $profile->getVersion(),
                '-',
                [],
                [],
                new RequestStatusStruct($error->getErrorCode(), $error->getMessage())
            );
        }
        $connection->close();

        $totals = $this->readTotals($migrationContext, $context);

        return new EnvironmentInformation(
            $profile->getSourceSystemName(),
            $profile->getVersion(),
            'Example Host Name',
            $totals,
            [],
            new RequestStatusStruct(),
            false
        );
    }

    public function readTotals(MigrationContextInterface $migrationContext, Context $context): array
    {
        $readers = $this->readerRegistry->getReaderForTotal($migrationContext);

        $totals = [];
        foreach ($readers as $reader) {
            $total = $reader->readTotal($migrationContext);

            if ($total === null) {
                continue;
            }

            $totals[$total->getEntityName()] = $total;
        }

        return $totals;
    }
}

As you have seen above, the gateway uses the ConnectionFactory to test the connection to the source system. You can also implement your own way to check this, but using this factory is the simplest way for a gateway to connect to a local database. Like the profile, you have to register the new gateway in the service.xml and tag it with shopware.migration.gateway:

html
<service id="SwagMigrationOwnProfileExample\Profile\OwnProfile\Gateway\OwnLocaleGateway">
    <argument type="service" id="SwagMigrationAssistant\Migration\Gateway\Reader\ReaderRegistry"/>
    <argument type="service" id="SwagMigrationAssistant\Profile\Shopware\Gateway\Connection\ConnectionFactory"/>
    <tag name="shopware.migration.gateway"/>
</service>

Creating a credentials page

If you want to try your current progress in the Administration, you can select the profile and gateway in the migration wizard. If you try to go to the next page, there will be an error message because no credentials page was found. To create a new credentials page, you have to add an index.js for your new component into Resources/app/administration/src/own-profile/profile:

javascript
import { Component } from 'src/core/shopware';
import template from './swag-migration-profile-ownProfile-local-credential-form.html.twig';

Component.register('swag-migration-profile-ownProfile-local-credential-form', {
    template,

    props: {
        credentials: {
            type: Object,
            default() {
                return {};
            }
        }
    },

    data() {
        return {
            inputCredentials: {
                dbHost: '',
                dbPort: '3306',
                dbUser: '',
                dbPassword: '',
                dbName: ''
            }
        };
    },

    watch: {
        credentials: {
            immediate: true,
            handler(newCredentials) {
                if (newCredentials === null) {
                    this.emitCredentials(this.inputCredentials);
                    return;
                }

                this.inputCredentials = newCredentials;
                this.emitOnChildRouteReadyChanged(
                    this.areCredentialsValid(this.inputCredentials)
                );
            }
        },

        inputCredentials: {
            deep: true,
            handler(newInputCredentials) {
                this.emitCredentials(newInputCredentials);
            }
        }
    },

    methods: {
        areCredentialsValid(newInputCredentials) {
            return (newInputCredentials.dbHost !== '' &&
                newInputCredentials.dbPort !== '' &&
                newInputCredentials.dbName !== '' &&
                newInputCredentials.dbUser !== '' &&
                newInputCredentials.dbPassword !== ''
            );
        },

        emitOnChildRouteReadyChanged(isReady) {
            this.$emit('onChildRouteReadyChanged', isReady);
        },

        emitCredentials(newInputCredentials) {
            this.$emit('onCredentialsChanged', newInputCredentials);
            this.emitOnChildRouteReadyChanged(
                this.areCredentialsValid(newInputCredentials)
            );
        },

        onKeyPressEnter() {
            this.$emit('onTriggerPrimaryClick');
        }
    }
});

As you can see above, currently, the template does not exist and you have to create this file: swag-migration-profile-ownProfile-local-credential-form.html.twig

html
{% block own_profile_page_credentials %}
    <div class="swag-migration-wizard swag-migration-wizard-page-credentials"
         @keypress.enter="onKeyPressEnter">
        {% block own_profile_page_credentials_content %}
            <div class="swag-migration-wizard__content">
                {% block own_profile_page_credentials_information %}
                    <div class="swag-migration-wizard__content-information">
                        {% block own_profile_page_credentials_local_hint %}
                            {{ $tc('swag-migration.wizard.pages.credentials.shopware55.local.contentInformation') }}
                        {% endblock %}
                    </div>
                {% endblock %}

                {% block own_profile_page_credentials_credentials %}
                    <div class="swag-migration-wizard__form">
                        {% block own_profile_page_credentials_local_db_host_port_group %}
                            <sw-container columns="1fr 80px"
                                          gap="16px">
                                {% block own_profile_page_credentials_local_dbhost_field %}
                                    <sw-text-field v-autofocus
                                                   name="sw-field--dbHost"
                                                   :label="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbHostLabel')"
                                                   :placeholder="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbHostPlaceholder')"
                                                   v-model="inputCredentials.dbHost">
                                    </sw-text-field>
                                {% endblock %}

                                {% block own_profile_page_credentials_local_dbport_field %}
                                    <sw-field name="sw-field--dbPort"
                                              :label="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbPortLabel')"
                                              v-model="inputCredentials.dbPort">
                                    </sw-field>
                                {% endblock %}
                            </sw-container>
                        {% endblock %}

                        {% block own_profile_page_credentials_local_dbuser_field %}
                            <sw-field name="sw-field--dbUser"
                                      :label="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbUserLabel')"
                                      :placeholder="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbUserPlaceholder')"
                                      v-model="inputCredentials.dbUser">
                            </sw-field>
                        {% endblock %}

                        {% block own_profile_page_credentials_local_dbpassword_field %}
                            <sw-field name="sw-field--dbPassword"
                                      type="password"
                                      :label="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbPasswordLabel')"
                                      :placeholder="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbPasswordPlaceholder')"
                                      v-model="inputCredentials.dbPassword">
                            </sw-field>
                        {% endblock %}

                        {% block own_profile_page_credentials_local_dbname_field %}
                            <sw-field name="sw-field--dbName"
                                      :label="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbNameLabel')"
                                      :placeholder="$tc('swag-migration.wizard.pages.credentials.shopware55.local.dbNamePlaceholder')"
                                      v-model="inputCredentials.dbName">
                            </sw-field>
                        {% endblock %}
                    </div>
                {% endblock %}
            </div>
        {% endblock %}
    </div>
{% endblock %}

Note that the component name isn't random and consists of:

  1. The prefix: swag-migration-profile-
  2. The name of the profile
  3. The name of the gateway
  4. The suffix: -credential-form

To see your credentials page, you have to register this component in your main.js:

javascript
import './own-profile/profile';

Creating a dataSet and dataSelection

Now the credentials page is loaded in the Administration and the connection check will succeed. But there is no data selection if you open the data selection table. To add an entry to this table, you have to create a ProductDataSet first:

php
<?php declare(strict_types=1);

namespace SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\DataSet;

use SwagMigrationAssistant\Migration\DataSelection\DataSet\DataSet;
use SwagMigrationAssistant\Migration\MigrationContextInterface;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile;

class ProductDataSet extends DataSet
{
    /**
     * Returns the entity identifier of this DataSet
     */
    public static function getEntity(): string
    {
        return 'product';
    }

    /**
     * Supports only an OwnProfile
     */
    public function supports(MigrationContextInterface $migrationContext): bool
    {
        return $migrationContext->getProfile() instanceof OwnProfile;
    }
}

Now you have to use this ProductDataSet in the new ProductDataSelection:

php
<?php declare(strict_types=1);

namespace SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection;

use SwagMigrationAssistant\Migration\DataSelection\DataSelectionInterface;
use SwagMigrationAssistant\Migration\DataSelection\DataSelectionStruct;
use SwagMigrationAssistant\Migration\MigrationContextInterface;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\DataSet\ProductDataSet;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile;

class ProductDataSelection implements DataSelectionInterface
{
    /**
     * Identifier of this DataSelection
     */
    public const IDENTIFIER = 'products';

    /**
     * Supports only an OwnProfile
     */
    public function supports(MigrationContextInterface $migrationContext): bool
    {
        return $migrationContext->getProfile() instanceof OwnProfile;
    }

    public function getData(): DataSelectionStruct
    {
        return new DataSelectionStruct(
            self::IDENTIFIER,
            $this->getDataSets(),
            $this->getDataSetsRequiredForCount(),
            /*
             * Snippet of the original ProductDataSelection, if you
             * want to use your own title, you have to create a new snippet
             */
            'swag-migration.index.selectDataCard.dataSelection.products',
            100
        );
    }

    /**
     * Returns all DataSets, which should be migrated with this DataSelection
     */
    public function getDataSets(): array
    {
        return [
            new ProductDataSet()
        ];
    }

    public function getDataSetsRequiredForCount(): array
    {
        return $this->getDataSets();
    }
}

INFO

The order in the getDataSets array is important as it determines the order in which the entities are processed. Because of that, the manufacturers, for example, have to be positioned before the products so that the products can use those later on.

To see the created ProductDataSelection in the Administration, you have to register it both in the services.xml and tag them with shopware.migration.data_selection and shopware.migration.data_set:

html
<service id="SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\ProductDataSelection">
    <tag name="shopware.migration.data_selection"/>
</service>

<service id="SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\DataSet\ProductDataSet">
    <tag name="shopware.migration.data_set"/>
</service>

Creating a product gateway reader

Currently, you can see the DataSelection in the Administration, but if you select it and start a migration, no product will be migrated. That is because the gateway read function isn't implemented yet. But before you can implement this function, you have to create a new ProductReader first:

php
<?php declare(strict_types=1);

namespace SwagMigrationOwnProfileExample\Profile\OwnProfile\Gateway\Reader;

use Doctrine\DBAL\Driver\ResultStatement;
use SwagMigrationAssistant\Migration\MigrationContextInterface;
use SwagMigrationAssistant\Migration\TotalStruct;
use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\Reader\AbstractReader;
use SwagMigrationAssistant\Profile\Shopware\Gateway\Local\ShopwareLocalGateway;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\DataSet\ProductDataSet;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile;

class ProductReader extends AbstractReader
{
    /**
     * Supports only an OwnProfile and the ProductDataSet
     */
    public function supports(MigrationContextInterface $migrationContext): bool
    {
        return $migrationContext->getProfile() instanceof OwnProfile
            && $migrationContext->getDataSet()::getEntity() === ProductDataSet::getEntity();
    }

    /**
     * Supports only an OwnProfile and the ProductDataSet for totals
     */
    public function supportsTotal(MigrationContextInterface $migrationContext): bool
    {
        return $migrationContext->getProfile() instanceof OwnProfile
            && $migrationContext->getGateway()->getName() === ShopwareLocalGateway::GATEWAY_NAME;
    }

    /**
     * Creates a database connection and sets the connection class variable
     */
    protected function setConnection(MigrationContextInterface $migrationContext): void
    {
        $this->connection = $this->connectionFactory->createDatabaseConnection($migrationContext);
    }

    public function readTotal(MigrationContextInterface $migrationContext): ?TotalStruct
    {
        $this->setConnection($migrationContext);

        $query = $this->connection->createQueryBuilder()
            ->select('COUNT(*)')
            ->from('product')
            ->execute();

        $total = 0;
        if ($query instanceof ResultStatement) {
            $total = (int) $query->fetchColumn();
        }

        return new TotalStruct(ProductDataSet::getEntity(), $total);
    }

    /**
     * Fetches all entities out of the product table with the given limit
     */
    public function read(MigrationContextInterface $migrationContext, array $params = []): array
    {
        $this->setConnection($migrationContext);

        $query = $this->connection->createQueryBuilder();
        $query->from('product');
        $query->addSelect('*');

        $query->setFirstResult($migrationContext->getOffset());
        $query->setMaxResults($migrationContext->getLimit());

        return $query->execute()->fetchAll(\PDO::FETCH_ASSOC);
    }
}

Then you have to register this in services.xml and tag it with shopware.migration.reader:

html
<service id="SwagMigrationOwnProfileExample\Profile\OwnProfile\Gateway\Reader\ProductReader"
    parent="SwagMigrationAssistant\Profile\Shopware\Gateway\Local\Reader\AbstractReader">
    <argument type="service" id="SwagMigrationAssistant\Profile\Shopware\Gateway\Connection\ConnectionFactory"/>
    <tag name="shopware.migration.reader"/>
</service>

Once the ProductReader is created and registered, you can use it in the read method of the OwnLocaleGateway:

php
<?php declare(strict_types=1);

namespace SwagMigrationOwnProfileExample\Profile\OwnProfile\Gateway;

use Shopware\Core\Framework\Context;
use SwagMigrationAssistant\Migration\EnvironmentInformation;
use SwagMigrationAssistant\Migration\Gateway\GatewayInterface;
use SwagMigrationAssistant\Migration\Gateway\Reader\ReaderRegistry;
use SwagMigrationAssistant\Migration\MigrationContextInterface;
use SwagMigrationAssistant\Migration\RequestStatusStruct;
use SwagMigrationAssistant\Profile\Shopware\Exception\DatabaseConnectionException;
use SwagMigrationAssistant\Profile\Shopware\Gateway\Connection\ConnectionFactoryInterface;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile;

class OwnLocaleGateway implements GatewayInterface
{
    public const GATEWAY_NAME = 'local';

    private ConnectionFactoryInterface $connectionFactory;

    private ReaderRegistry $readerRegistry;

    public function __construct(
        ReaderRegistry $readerRegistry,
        ConnectionFactoryInterface $connectionFactory
    ) {
        $this->readerRegistry = $readerRegistry;
        $this->connectionFactory = $connectionFactory;
    }

    public function getName(): string
    {
        return self::GATEWAY_NAME;
    }

    public function supports(MigrationContextInterface $migrationContext): bool
    {
        return $migrationContext->getProfile() instanceof OwnProfile;
    }

    public function getSnippetName(): string
    {
        return 'swag-migration.wizard.pages.connectionCreate.gateways.shopwareLocal';
    }

    public function read(MigrationContextInterface $migrationContext): array
    {
        $reader = $this->readerRegistry->getReader($migrationContext);

        return $reader->read($migrationContext);
    }

    public function readEnvironmentInformation(
        MigrationContextInterface $migrationContext,
        Context $context
    ): EnvironmentInformation {
        $connection = $this->connectionFactory->createDatabaseConnection($migrationContext);
        $profile = $migrationContext->getProfile();

        try {
            $connection->connect();
        } catch (\Exception $e) {
            $error = new DatabaseConnectionException();

            return new EnvironmentInformation(
                $profile->getSourceSystemName(),
                $profile->getVersion(),
                '-',
                [],
                [],
                new RequestStatusStruct($error->getErrorCode(), $error->getMessage())
            );
        }
        $connection->close();

        $totals = $this->readTotals($migrationContext, $context);

        return new EnvironmentInformation(
            $profile->getSourceSystemName(),
            $profile->getVersion(),
            'Example Host Name',
            $totals,
            [],
            new RequestStatusStruct(),
            false
        );
    }

    public function readTotals(MigrationContextInterface $migrationContext, Context $context): array
    {
        $readers = $this->readerRegistry->getReaderForTotal($migrationContext);

        $totals = [];
        foreach ($readers as $reader) {
            $total = $reader->readTotal($migrationContext);

            if ($total === null) {
                continue;
            }

            $totals[$total->getEntityName()] = $total;
        }

        return $totals;
    }
}

Creating a converter

By using the gateway reader, you fetch all products but don't use this data yet. In this step, you implement the logic of the converter:

php
<?php declare(strict_types=1);

namespace SwagMigrationOwnProfileExample\Profile\OwnProfile\Converter;

use Shopware\Core\Framework\Context;
use SwagMigrationAssistant\Migration\Converter\ConvertStruct;
use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities;
use SwagMigrationAssistant\Migration\MigrationContextInterface;
use SwagMigrationAssistant\Profile\Shopware\Converter\ShopwareConverter;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\DataSelection\DataSet\ProductDataSet;
use SwagMigrationOwnProfileExample\Profile\OwnProfile\OwnProfile;

class ProductConverter extends ShopwareConverter
{
    private string $connectionId;

    private Context $context;

    public function getSourceIdentifier(array $data): string
    {
        return $data['id'];
    }

    /**
     * Supports only an OwnProfile and the ProductDataSet
     */
    public function supports(MigrationContextInterface $migrationContext): bool
    {
        return $migrationContext->getProfile() instanceof OwnProfile &&
            $migrationContext->getDataSet()::getEntity() === ProductDataSet::getEntity();
    }

    /**
     * Writes the created mapping
     */
    public function writeMapping(Context $context): void
    {
        $this->mappingService->writeMapping($context);
    }

    public function convert(array $data, Context $context, MigrationContextInterface $migrationContext): ConvertStruct
    {
        $this->generateChecksum($data);
        $this->connectionId = $migrationContext->getConnection()->getId();
        $this->context = $context;

        /**
         * Gets the product uuid out of the mapping table or creates a new one
         */
        $this->mainMapping = $this->mappingService->getOrCreateMapping(
            $migrationContext->getConnection()->getId(),
            ProductDataSet::getEntity(),
            $data['id'],
            $context,
            $this->checksum
        );

        $converted['id'] = $this->mainMapping['entityUuid'];
        $this->convertValue($converted, 'productNumber', $data, 'product_number');
        $this->convertValue($converted, 'name', $data, 'product_name');
        $this->convertValue($converted, 'stock', $data, 'stock', self::TYPE_INTEGER);

        if (isset($data['tax'])) {
            $converted['tax'] = $this->getTax($data);
            $converted['price'] = $this->getPrice($data, $converted['tax']['taxRate']);
        }

        unset(
          $data['id'],
          $data['product_number'],
          $data['product_name'],
          $data['stock'],
          $data['tax'],
          $data['price']
        );

        if (empty($data)) {
            $data = null;
        }
        $this->updateMainMapping($migrationContext, $context);

        return new ConvertStruct($converted, $data, $this->mainMapping['id']);
    }

    private function getTax(array $data): array
    {
        $taxRate = (float) $data['tax'];

        /**
         * Gets the tax uuid by the given tax rate
         */
        $taxUuid = $this->mappingService->getTaxUuid($this->connectionId, $taxRate, $this->context);

        /**
         * If no tax rate is found, create a new one
         */
        if ($taxUuid === null) {
            $mapping = $this->mappingService->createMapping(
                $this->connectionId,
                DefaultEntities::TAX,
                $data['id']
            );
            $taxUuid = $mapping['entityUuid'];
        }

        return [
            'id' => $taxUuid,
            'taxRate' => $taxRate,
            'name' => 'Own profile tax rate (' . $taxRate . ')',
        ];
    }

    private function getPrice(array $data, float $taxRate): array
    {
        $gross = (float) $data['price'] * (1 + $taxRate / 100);

        /**
         * Gets the currency uuid by the given iso code
         */
        $currencyUuid = $this->mappingService->getCurrencyUuid(
            $this->connectionId,
            'EUR',
            $this->context
        );

        if ($currencyUuid === null) {
            return [];
        }

        $price = [];
        $price[] = [
            'currencyId' => $currencyUuid,
            'gross' => $gross,
            'net' => (float) $data['price'],
            'linked' => true,
        ];

        return $price;
    }
}

If you don't know which properties or requirements your entity has in Shopware 6, you may check the corresponding EntityDefinition. For this example, look at the ProductEntityDefinition to know how to convert the data exactly.

To use this converter, you must register it in the services.xml:

html
<service id="SwagMigrationOwnProfileExample\Profile\OwnProfile\Converter\ProductConverter">
    <argument type="service" id="SwagMigrationAssistant\Migration\Mapping\MappingService"/>
    <argument type="service" id="SwagMigrationAssistant\Migration\Logging\LoggingService"/>
    <tag name="shopware.migration.converter"/>
</service>

To write new entities, you have to create a new writer class, but for the product entity, you can use the ProductWriter:

php
<?php declare(strict_types=1);

namespace SwagMigrationAssistant\Migration\Writer;

use SwagMigrationAssistant\Migration\DataSelection\DefaultEntities;

class ProductWriter extends AbstractWriter
{
    public function supports(): string
    {
        return DefaultEntities::PRODUCT;
    }
}

This writer will be called automatically as the getEntityName method of your ProductDataSet is compared with the return value of the supports method of the writer in the WriterRegistry. These values are identical, and so the writer will be used to write your product entities.

Source

Check out this GitHub repository containing a full example source.