-
Notifications
You must be signed in to change notification settings - Fork 977
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'next-25734/sync-api-improvement' into 'trunk'
NEXT-25734 - Added option to provide a criteria inside the sync operations in... See merge request shopware/6/product/platform!10633
- Loading branch information
Showing
17 changed files
with
752 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Content\Product\Api; | ||
|
||
use Doctrine\DBAL\ArrayParameterType; | ||
use Doctrine\DBAL\Connection; | ||
use Shopware\Core\Defaults; | ||
use Shopware\Core\Framework\Api\Sync\AbstractFkResolver; | ||
use Shopware\Core\Framework\Api\Sync\FkReference; | ||
use Shopware\Core\Framework\Log\Package; | ||
use Shopware\Core\Framework\Uuid\Uuid; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
#[Package('core')] | ||
class ProductNumberFkResolver extends AbstractFkResolver | ||
{ | ||
public function __construct(private readonly Connection $connection) | ||
{ | ||
} | ||
|
||
public static function getName(): string | ||
{ | ||
return 'product.number'; | ||
} | ||
|
||
/** | ||
* @param array<FkReference> $map | ||
* | ||
* @return array<FkReference> | ||
*/ | ||
public function resolve(array $map): array | ||
{ | ||
$numbers = \array_map(fn ($id) => $id->value, $map); | ||
|
||
$numbers = \array_filter(\array_unique($numbers)); | ||
|
||
if (empty($numbers)) { | ||
return $map; | ||
} | ||
|
||
$hash = $this->connection->fetchAllKeyValue( | ||
'SELECT product_number, LOWER(HEX(id)) FROM product WHERE product_number IN (:numbers) AND version_id = :version', | ||
['numbers' => $numbers, 'version' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION)], | ||
['numbers' => ArrayParameterType::STRING] | ||
); | ||
|
||
foreach ($map as $reference) { | ||
$reference->resolved = $hash[$reference->value]; | ||
} | ||
|
||
return $map; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Framework\Api; | ||
|
||
use Shopware\Core\Framework\HttpException; | ||
use Shopware\Core\Framework\Log\Package; | ||
use Symfony\Component\HttpFoundation\Response; | ||
|
||
#[Package('core')] | ||
class ApiException extends HttpException | ||
{ | ||
public const API_INVALID_SYNC_CRITERIA_EXCEPTION = 'API_INVALID_SYNC_CRITERIA_EXCEPTION'; | ||
public const API_RESOLVER_NOT_FOUND_EXCEPTION = 'API_RESOLVER_NOT_FOUND_EXCEPTION'; | ||
public const API_INVALID_SYNC_OPERATION_EXCEPTION = 'FRAMEWORK__INVALID_SYNC_OPERATION'; | ||
|
||
public static function invalidSyncCriteriaException(string $operationKey): self | ||
{ | ||
return new self( | ||
Response::HTTP_BAD_REQUEST, | ||
self::API_INVALID_SYNC_CRITERIA_EXCEPTION, | ||
\sprintf('Sync operation %s, with action "delete", requires a criteria with at least one filter and can only be applied for mapping entities', $operationKey) | ||
); | ||
} | ||
|
||
public static function invalidSyncOperationException(string $message): self | ||
{ | ||
return new self( | ||
Response::HTTP_BAD_REQUEST, | ||
self::API_INVALID_SYNC_OPERATION_EXCEPTION, | ||
$message | ||
); | ||
} | ||
|
||
public static function resolverNotFoundException(string $key): self | ||
{ | ||
return new self( | ||
Response::HTTP_BAD_REQUEST, | ||
self::API_RESOLVER_NOT_FOUND_EXCEPTION, | ||
\sprintf('Foreign key resolver for key %s not found', $key) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Framework\Api\Sync; | ||
|
||
use Shopware\Core\Framework\Log\Package; | ||
|
||
#[Package('core')] | ||
abstract class AbstractFkResolver | ||
{ | ||
/** | ||
* Returns the unique name for the resolver which is used to identify for fk resolving hash map | ||
*/ | ||
abstract public static function getName(): string; | ||
|
||
/** | ||
* @param array<FkReference> $map | ||
* | ||
* @return array<FkReference> | ||
*/ | ||
abstract public function resolve(array $map): array; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Framework\Api\Sync; | ||
|
||
use Shopware\Core\Framework\Log\Package; | ||
|
||
/** | ||
* @final | ||
*/ | ||
#[Package('core')] | ||
class FkReference | ||
{ | ||
public ?string $resolved = null; | ||
|
||
public function __construct(public mixed $value) | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
<?php declare(strict_types=1); | ||
|
||
namespace Shopware\Core\Framework\Api\Sync; | ||
|
||
use Shopware\Core\Framework\Api\ApiException; | ||
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField; | ||
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToOneAssociationField; | ||
use Shopware\Core\Framework\Log\Package; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
#[Package('core')] | ||
class SyncFkResolver | ||
{ | ||
/** | ||
* @internal | ||
* | ||
* @param iterable<AbstractFkResolver> $resolvers | ||
*/ | ||
public function __construct( | ||
private readonly DefinitionInstanceRegistry $registry, | ||
private readonly iterable $resolvers | ||
) { | ||
} | ||
|
||
/** | ||
* @param array<int, array<string, mixed>> $payload | ||
* | ||
* @return array<int, array<string, mixed>> | ||
*/ | ||
public function resolve(string $entity, array $payload): array | ||
{ | ||
$map = $this->collect($entity, $payload); | ||
|
||
if (empty($map)) { | ||
return $payload; | ||
} | ||
|
||
foreach ($map as $key => &$values) { | ||
$values = $this->getResolver($key)->resolve($values); | ||
} | ||
|
||
\array_walk_recursive($payload, function (&$value): void { | ||
$value = $value instanceof FkReference ? $value->resolved : $value; | ||
}); | ||
|
||
return $payload; | ||
} | ||
|
||
/** | ||
* @param array<int, array<string, mixed>> $payload | ||
* | ||
* @return array<string, array<FkReference>> | ||
*/ | ||
private function collect(string $entity, array &$payload): array | ||
{ | ||
$definition = $this->registry->getByEntityName($entity); | ||
|
||
$map = []; | ||
foreach ($payload as &$row) { | ||
foreach ($row as $key => &$value) { | ||
if (\is_array($value) && isset($value['resolver']) && isset($value['value'])) { | ||
$definition = $this->registry->getByEntityName($entity); | ||
|
||
$field = $definition->getField($key); | ||
|
||
$ref = match (true) { | ||
$field instanceof FkField => $field->getReferenceDefinition()->getEntityName(), | ||
$field instanceof IdField => $entity, | ||
default => null | ||
}; | ||
|
||
if ($ref === null) { | ||
continue; | ||
} | ||
|
||
$resolver = (string) $value['resolver']; | ||
|
||
$row[$key] = $reference = new FkReference($value['value']); | ||
|
||
$map[$resolver][] = $reference; | ||
} | ||
|
||
if (\is_array($value)) { | ||
$field = $definition->getField($key); | ||
|
||
if (!$field instanceof AssociationField) { | ||
continue; | ||
} | ||
|
||
$nested = []; | ||
if ($field instanceof ManyToManyAssociationField || $field instanceof OneToManyAssociationField) { | ||
$ref = $field instanceof ManyToManyAssociationField ? $field->getToManyReferenceDefinition()->getEntityName() : $field->getReferenceDefinition()->getEntityName(); | ||
$nested = $this->collect($ref, $value); | ||
} elseif ($field instanceof ManyToOneAssociationField || $field instanceof OneToOneAssociationField) { | ||
$tmp = [$value]; | ||
$nested = $this->collect($field->getReferenceDefinition()->getEntityName(), $tmp); | ||
$value = \array_shift($tmp); | ||
} | ||
|
||
$map = $this->merge($map, $nested); | ||
} | ||
} | ||
} | ||
|
||
return $map; | ||
} | ||
|
||
/** | ||
* @param array<string, array<FkReference>> $map | ||
* @param array<string, array<FkReference>> $nested | ||
* | ||
* @return array<string, array<FkReference>> | ||
*/ | ||
private function merge(array $map, array $nested): array | ||
{ | ||
foreach ($nested as $resolver => $values) { | ||
foreach ($values as $value) { | ||
$map[$resolver][] = $value; | ||
} | ||
} | ||
|
||
return $map; | ||
} | ||
|
||
private function getResolver(string $key): AbstractFkResolver | ||
{ | ||
foreach ($this->resolvers as $resolver) { | ||
if ($resolver::getName() === $key) { | ||
return $resolver; | ||
} | ||
} | ||
|
||
throw ApiException::resolverNotFoundException($key); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.