Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/DependencyInjection/migration.xml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
<argument type="service" id="SwagMigrationAssistant\Migration\Logging\LoggingService"/>
<argument type="service" id="SwagMigrationAssistant\Migration\Data\SwagMigrationDataDefinition"/>
<argument type="service" id="swag_migration_mapping.repository"/>
<argument type="service" id="SwagMigrationAssistant\Migration\Writer\MigrationFix\MigrationFixApplier"/>
</service>

<service id="SwagMigrationAssistant\Migration\Service\MediaFileProcessorService" public="true">
Expand Down
4 changes: 4 additions & 0 deletions src/DependencyInjection/writer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,9 @@
<argument type="service" id="Shopware\Core\System\Country\CountryDefinition"/>
<tag name="shopware.migration.writer"/>
</service>

<service id="SwagMigrationAssistant\Migration\Writer\MigrationFix\MigrationFixApplier">
<argument type="service" id="Doctrine\DBAL\Connection"/>
</service>
</services>
</container>
12 changes: 12 additions & 0 deletions src/Exception/MigrationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class MigrationException extends HttpException

public const UNEXPECTED_NULL_VALUE = 'SWAG_MIGRATION__UNEXPECTED_NULL_VALUE';

public const MISSING_MIGRATION_FIX_KEY = 'SWAG_MIGRATION__MISSING_MIGRATION_FIX_KEY';

public static function associationEntityRequiredMissing(string $entity, string $missingEntity): self
{
return new AssociationEntityRequiredMissingException(
Expand Down Expand Up @@ -545,4 +547,14 @@ public static function unexpectedNullValue(string $fieldName): self
['fieldName' => $fieldName]
);
}

public static function couldNotConvertFix(string $missingKey): self
{
return new self(
Response::HTTP_INTERNAL_SERVER_ERROR,
self::MISSING_MIGRATION_FIX_KEY,
'Missing key "{{ missingKey }}" to construct MigrationFix.',
['missingKey' => $missingKey]
);
}
}
7 changes: 6 additions & 1 deletion src/Migration/Service/MigrationDataWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use SwagMigrationAssistant\Migration\Mapping\SwagMigrationMappingCollection;
use SwagMigrationAssistant\Migration\Media\MediaFileServiceInterface;
use SwagMigrationAssistant\Migration\MigrationContextInterface;
use SwagMigrationAssistant\Migration\Writer\MigrationFix\MigrationFixApplier;
use SwagMigrationAssistant\Migration\Writer\WriterRegistryInterface;

#[Package('fundamentals@after-sales')]
Expand All @@ -46,6 +47,7 @@ public function __construct(
private readonly LoggingServiceInterface $loggingService,
private readonly EntityDefinition $dataDefinition,
private readonly EntityRepository $mappingRepo,
private readonly MigrationFixApplier $fixApplier,
) {
// write / upsert entities only with this single context,
// otherwise the migration behaves differently when started in the administration
Expand Down Expand Up @@ -101,9 +103,12 @@ public function writeData(MigrationContextInterface $migrationContext, Context $
return 0;
}

$convertedValues = array_values($converted);
$this->fixApplier->apply($convertedValues, $migrationContext->getConnection()->getId());

try {
$currentWriter = $this->writerRegistry->getWriter($dataSet::getEntity());
$currentWriter->writeData(\array_values($converted), $this->writeContext);
$currentWriter->writeData($convertedValues, $this->writeContext);
} catch (WriterNotFoundException $writerNotFoundException) {
$this->loggingService->addLogEntry(
SwagMigrationLogBuilder::fromMigrationContext($migrationContext)
Expand Down
84 changes: 84 additions & 0 deletions src/Migration/Writer/MigrationFix/MigrationFix.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php declare(strict_types=1);
/*
* (c) shopware AG <[email protected]>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SwagMigrationAssistant\Migration\Writer\MigrationFix;

use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Uuid\Uuid;
use SwagMigrationAssistant\Exception\MigrationException;

#[Package('after-sales')]
class MigrationFix
{
private const PATH_SEPARATOR = '.';

public function __construct(
public readonly string $id,
public readonly string $value,
public readonly string $path,
) {
}

/**
* @param array<string, string> $data
*/
public static function fromDatabaseQuery(array $data): self
{
$expectedArrayKeys = ['id', 'value', 'path'];
foreach ($expectedArrayKeys as $expectedKey) {
if (!\array_key_exists($expectedKey, $data)) {
throw MigrationException::couldNotConvertFix($expectedKey);
}
}

return new self(
Uuid::fromBytesToHex($data['id']),
$data['value'],
$data['path'],
);
}

/**
* @param array<string|int, mixed> $item
*/
public function apply(array &$item): void
{
/*
* Explode the path to an array
* Path example: 'category.language.name'
* Results in an array like: ['category', 'language', 'name']
*/
$pathArray = explode(self::PATH_SEPARATOR, $this->path);

/*
* Set current item as pointer
* Item structure for example has no valid value for name and looks like:
* [
* 'someOtherKeys',
* ...
* category => [
* ...
* 'language' => [
* ...
* 'name' => null,
* ]
* ]
* ]
*/
$nestedPointer = &$item;

// Iterating over the path to follow them and set the nested pointer to the last key in pathArray
// In this example the result pointer is: $item['category']['language']['name']
foreach ($pathArray as $key) {
$nestedPointer = &$nestedPointer[$key];
}

// Now set the value to the pointer like: $item['category']['language']['name'] = 'new Value'
$nestedPointer = \json_decode($this->value, true, 512, \JSON_THROW_ON_ERROR);
unset($nestedPointer);
}
}
83 changes: 83 additions & 0 deletions src/Migration/Writer/MigrationFix/MigrationFixApplier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php declare(strict_types=1);
/*
* (c) shopware AG <[email protected]>
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SwagMigrationAssistant\Migration\Writer\MigrationFix;

use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Log\Package;
use Shopware\Core\Framework\Uuid\Uuid;

#[Package('after-sales')]
class MigrationFixApplier
{
public function __construct(
private readonly Connection $connection,
) {
}

/**
* @param array<int|string, array<int|string, mixed>> $data
*/
public function apply(array &$data, string $connectionId): void
{
$itemIds = \array_column($data, 'id');
$fixes = $this->getMappings($itemIds, $connectionId);

foreach ($data as &$item) {
$id = $item['id'];

if (!\array_key_exists($id, $fixes) || !\is_array($fixes[$id])) {
continue;
}

foreach ($fixes[$id] as $fix) {
$fix->apply($item);
}
}

unset($item);
}

/**
* @param array<int, string> $ids
*
* @return array<string, list<MigrationFix>>
*/
private function getMappings(array $ids, string $connectionId): array
{
$sql = <<<'SQL'
SELECT mapping.entity_uuid as entityId, fix.id, fix.value, fix.path FROM swag_migration_mapping as mapping
INNER JOIN swag_migration_fix as fix ON fix.main_mapping_id = mapping.id
WHERE mapping.entity_uuid IN (:ids)
AND mapping.connection_id = :connectionId
SQL;

$result = $this->connection->fetchAllAssociative(
$sql,
[
'ids' => Uuid::fromHexToBytesList($ids),
'connectionId' => Uuid::fromHexToBytes($connectionId),
],
[
'ids' => ArrayParameterType::STRING,
]
);

$return = [];
foreach ($result as $row) {
$entityUuid = Uuid::fromBytesToHex($row['entityId']);
if (!\array_key_exists($entityUuid, $return)) {
$return[$entityUuid] = [];
}

$return[$entityUuid][] = MigrationFix::fromDatabaseQuery($row);
}

return $return;
}
}
Loading
Loading