diff --git a/app/code/Magento/CatalogExport/Event/Data/Entity.php b/app/code/Magento/CatalogExport/Event/Data/Entity.php
index 0d27bd082..6a82a09b8 100644
--- a/app/code/Magento/CatalogExport/Event/Data/Entity.php
+++ b/app/code/Magento/CatalogExport/Event/Data/Entity.php
@@ -14,7 +14,7 @@
class Entity
{
/**
- * @var int
+ * @var string
*/
private $entityId;
@@ -26,9 +26,9 @@ class Entity
/**
* Get entity id.
*
- * @return int
+ * @return string
*/
- public function getEntityId(): int
+ public function getEntityId(): string
{
return $this->entityId;
}
@@ -36,11 +36,11 @@ public function getEntityId(): int
/**
* Set entity id.
*
- * @param int $entityId
+ * @param string $entityId
*
* @return void
*/
- public function setEntityId(int $entityId): void
+ public function setEntityId(string $entityId): void
{
$this->entityId = $entityId;
}
diff --git a/app/code/Magento/CatalogMessageBroker/Model/FetchProductVariants.php b/app/code/Magento/CatalogMessageBroker/Model/FetchProductVariants.php
new file mode 100644
index 000000000..12cf583a3
--- /dev/null
+++ b/app/code/Magento/CatalogMessageBroker/Model/FetchProductVariants.php
@@ -0,0 +1,90 @@
+restClient = $restClient;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $entities): array
+ {
+ $data = $this->prepareRequestData($entities);
+ try {
+ $variants = $this->restClient->get(
+ self::EXPORT_API_GET_VARIANTS,
+ $data
+ );
+
+ } catch (\Throwable $e) {
+ $this->logger->error(
+ \sprintf(
+ 'Cannot load product variants via "%s" with ids "%s"',
+ self::EXPORT_API_GET_VARIANTS,
+ \implode(',', \array_map(function (Entity $entity) {
+ return $entity->getEntityId();
+ }, $entities))
+ ),
+ ['exception' => $e]
+ );
+ return [];
+ }
+
+ return $variants;
+ }
+
+ /**
+ * Prepare client request data
+ *
+ * @param Entity[] $entities
+ *
+ * @return array
+ */
+ private function prepareRequestData(array $entities): array
+ {
+ $variants = [];
+ foreach ($entities as $entity) {
+ $variants['ids'][] = $entity->getEntityId();
+ }
+ return $variants;
+ }
+}
diff --git a/app/code/Magento/CatalogMessageBroker/Model/FetchProductVariantsInterface.php b/app/code/Magento/CatalogMessageBroker/Model/FetchProductVariantsInterface.php
new file mode 100644
index 000000000..0f1124a90
--- /dev/null
+++ b/app/code/Magento/CatalogMessageBroker/Model/FetchProductVariantsInterface.php
@@ -0,0 +1,25 @@
+fetchCategories->execute($entities, $scope);
$attributesArray = $this->getAttributesArray($entities);
diff --git a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ConsumerEventInterface.php b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ConsumerEventInterface.php
index 923d54767..4630fbf41 100644
--- a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ConsumerEventInterface.php
+++ b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ConsumerEventInterface.php
@@ -18,9 +18,9 @@ interface ConsumerEventInterface
* Execute consumers by ids for specified scope
*
* @param Entity[] $entities
- * @param string $scope
+ * @param string|null $scope
*
* @return void
*/
- public function execute(array $entities, string $scope): void;
+ public function execute(array $entities, ?string $scope = null): void;
}
diff --git a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/DeleteProductsConsumer.php b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/DeleteProductsConsumer.php
index af3e8e446..7ada98df7 100644
--- a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/DeleteProductsConsumer.php
+++ b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/DeleteProductsConsumer.php
@@ -49,7 +49,7 @@ public function __construct(
/**
* @inheritdoc
*/
- public function execute(array $entities, string $scope): void
+ public function execute(array $entities, ?string $scope = null): void
{
$ids = [];
diff --git a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/PublishProductsConsumer.php b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/PublishProductsConsumer.php
index 5091edd0d..c1bd14d16 100644
--- a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/PublishProductsConsumer.php
+++ b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/Product/PublishProductsConsumer.php
@@ -73,7 +73,7 @@ public function __construct(
/**
* @inheritdoc
*/
- public function execute(array $entities, string $scope): void
+ public function execute(array $entities, ?string $scope = null): void
{
$productsData = $this->fetchProducts->execute($entities, $scope);
$attributesArray = $this->getAttributesArray($entities);
diff --git a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/DeleteProductVariantsConsumer.php b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/DeleteProductVariantsConsumer.php
new file mode 100644
index 000000000..7b041d511
--- /dev/null
+++ b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/DeleteProductVariantsConsumer.php
@@ -0,0 +1,72 @@
+deleteVariantsRequestInterfaceFactory = $deleteVariantsRequestInterfaceFactory;
+ $this->variantsServer = $variantsServer;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $entities, ?string $scope = null): void
+ {
+ $ids = [];
+ foreach ($entities as $entity) {
+ $ids[] = $entity->getEntityId();
+ }
+
+ $deleteVariantsRequest = $this->deleteVariantsRequestInterfaceFactory->create();
+ $deleteVariantsRequest->setId($ids);
+
+ try {
+ $importResult = $this->variantsServer->deleteProductVariants($deleteVariantsRequest);
+ if ($importResult->getStatus() === false) {
+ $this->logger->error(
+ sprintf('Product variants deletion has failed: "%s"', $importResult->getMessage())
+ );
+ }
+ } catch (\Throwable $e) {
+ $this->logger->critical(sprintf('Exception while deleting product variants: "%s"', $e));
+ }
+ }
+}
diff --git a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/ProductVariantsConsumer.php b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/ProductVariantsConsumer.php
new file mode 100644
index 000000000..d77e09b8a
--- /dev/null
+++ b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/ProductVariantsConsumer.php
@@ -0,0 +1,73 @@
+logger = $logger;
+ $this->consumerEventFactory = $consumerEventFactory;
+ }
+
+ /**
+ * Process message
+ *
+ * @param ChangedEntities $message
+ * @return void
+ */
+ public function processMessage(ChangedEntities $message): void
+ {
+ try {
+ $eventType = $message->getMeta() ? $message->getMeta()->getEventType() : null;
+ $entities = $message->getData() ? $message->getData()->getEntities() : null;
+
+ if (empty($entities)) {
+ throw new \InvalidArgumentException('Product variants data is missing in payload');
+ }
+
+ $variantsEvent = $this->consumerEventFactory->create($eventType);
+ $variantsEvent->execute($entities);
+ } catch (\Throwable $e) {
+ $this->logger->error(
+ \sprintf(
+ 'Unable to process collected product variants data. Event type: "%s", ids: "%s"',
+ $eventType ?? '',
+ \implode(',', \array_map(function (Entity $entity) {
+ return $entity->getEntityId();
+ }, $entities ?? []))
+ ),
+ ['exception' => $e]
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/PublishProductVariantsConsumer.php b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/PublishProductVariantsConsumer.php
new file mode 100644
index 000000000..4bf68dc96
--- /dev/null
+++ b/app/code/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariants/PublishProductVariantsConsumer.php
@@ -0,0 +1,119 @@
+logger = $logger;
+ $this->fetchProductVariants = $fetchProductVariants;
+ $this->variantService = $variantService;
+ $this->importVariantsRequestInterfaceFactory = $importVariantsRequestInterfaceFactory;
+ $this->productVariantImportMapper = $productVariantImportMapper;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(array $entities, ?string $scope = null): void
+ {
+ $variantsData = $this->fetchProductVariants->execute($entities);
+ $variantsImportData = [];
+ foreach ($variantsData as $variantData) {
+ $variantsImportData[] = $this->buildVariantImportObj($variantData);
+ }
+ if (!empty($variantsImportData)) {
+ $this->importVariants($variantsImportData);
+ }
+ }
+
+ /**
+ * Build product variant import object
+ *
+ * @param array $variantsData
+ * @return ProductVariantImportInterface
+ */
+ private function buildVariantImportObj(array $variantsData): ProductVariantImportInterface
+ {
+ return $this->productVariantImportMapper->setData(
+ [
+ 'id' => $variantsData['id'],
+ 'option_values' => $variantsData['option_values'],
+ ]
+ )->build();
+ }
+
+ /**
+ * Import variants
+ *
+ * @param ProductVariantImportInterface[] $variants
+ * @return void
+ */
+ private function importVariants(array $variants): void
+ {
+ $importVariantsRequest = $this->importVariantsRequestInterfaceFactory->create();
+ $importVariantsRequest->setVariants($variants);
+
+ try {
+ $importResult = $this->variantService->importProductVariants($importVariantsRequest);
+ if ($importResult->getStatus() === false) {
+ $this->logger->error(sprintf('Product variants import failed: "%s"', $importResult->getMessage()));
+ }
+ } catch (\Throwable $e) {
+ $this->logger->critical(sprintf('Exception while publishing product variants: "%s"', $e));
+ }
+ }
+}
diff --git a/app/code/Magento/CatalogMessageBroker/Model/ProductDataProcessor.php b/app/code/Magento/CatalogMessageBroker/Model/ProductDataProcessor.php
index 0dbe3a338..20b59f99d 100644
--- a/app/code/Magento/CatalogMessageBroker/Model/ProductDataProcessor.php
+++ b/app/code/Magento/CatalogMessageBroker/Model/ProductDataProcessor.php
@@ -42,7 +42,6 @@ class ProductDataProcessor
'updated_at' => 'updated_at',
'attributes' => 'dynamic_attributes',
'price_view' => 'price_view',
- // 'variants' => 'variants', // \Magento\CatalogStorefrontApi\Api\Data\VariantInterface[]
'category_ids' => 'categories',
'images' => 'images', //type: \Magento\CatalogStorefrontApi\Api\Data\ImageInterface[]
'videos' => 'videos', //type: \Magento\CatalogStorefrontApi\Api\Data\VideoInterface[]
diff --git a/app/code/Magento/CatalogMessageBroker/etc/communication.xml b/app/code/Magento/CatalogMessageBroker/etc/communication.xml
index 5acf3f9ac..ddc1f8466 100644
--- a/app/code/Magento/CatalogMessageBroker/etc/communication.xml
+++ b/app/code/Magento/CatalogMessageBroker/etc/communication.xml
@@ -8,5 +8,6 @@
+
diff --git a/app/code/Magento/CatalogMessageBroker/etc/di.xml b/app/code/Magento/CatalogMessageBroker/etc/di.xml
index deb30decf..ce2b4b23f 100644
--- a/app/code/Magento/CatalogMessageBroker/etc/di.xml
+++ b/app/code/Magento/CatalogMessageBroker/etc/di.xml
@@ -7,6 +7,7 @@
-->
+
@@ -35,6 +36,12 @@
-
Magento\CatalogMessageBroker\Model\MessageBus\Product\DeleteProductsConsumer
+ -
+ Magento\CatalogMessageBroker\Model\MessageBus\ProductVariants\PublishProductVariantsConsumer
+
+ -
+ Magento\CatalogMessageBroker\Model\MessageBus\ProductVariants\DeleteProductVariantsConsumer
+
diff --git a/app/code/Magento/CatalogMessageBroker/etc/queue_consumer.xml b/app/code/Magento/CatalogMessageBroker/etc/queue_consumer.xml
index 686181a95..14059af0d 100644
--- a/app/code/Magento/CatalogMessageBroker/etc/queue_consumer.xml
+++ b/app/code/Magento/CatalogMessageBroker/etc/queue_consumer.xml
@@ -7,5 +7,6 @@
-->
+
diff --git a/app/code/Magento/CatalogStorefront/DataProvider/ProductVariantsDataProvider.php b/app/code/Magento/CatalogStorefront/DataProvider/ProductVariantsDataProvider.php
new file mode 100644
index 000000000..6fff424fd
--- /dev/null
+++ b/app/code/Magento/CatalogStorefront/DataProvider/ProductVariantsDataProvider.php
@@ -0,0 +1,88 @@
+query = $query;
+ $this->storageState = $storageState;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Fetch product variants data from storage
+ *
+ * @param int $parentId
+ * @return array
+ * @throws RuntimeException
+ * @throws \Throwable
+ */
+ public function fetchByProductId(int $parentId): array
+ {
+ // TODO: Adapt to work without store code https://github.com/magento/catalog-storefront/issues/417
+ $storageName = $this->storageState->getCurrentDataSourceName(
+ [VariantService::EMPTY_STORE_CODE, ProductVariant::ENTITY_NAME]
+ );
+ try {
+ $entities = $this->query->searchFilteredEntries(
+ $storageName,
+ ProductVariant::ENTITY_NAME,
+ ['parent_id' => $parentId]
+ );
+ } catch (NotFoundException $notFoundException) {
+ $this->logger->error(
+ \sprintf(
+ 'Cannot find product variants for product id "%s"',
+ $parentId,
+ ),
+ ['exception' => $notFoundException]
+ );
+ return [];
+ } catch (\Throwable $e) {
+ $this->logger->error($e);
+ throw $e;
+ }
+ return $entities->toArray(false);
+ }
+}
diff --git a/app/code/Magento/CatalogStorefront/Model/CatalogRepository.php b/app/code/Magento/CatalogStorefront/Model/CatalogRepository.php
index 81bfe35a8..f05b2eb17 100644
--- a/app/code/Magento/CatalogStorefront/Model/CatalogRepository.php
+++ b/app/code/Magento/CatalogStorefront/Model/CatalogRepository.php
@@ -19,9 +19,10 @@
*/
class CatalogRepository
{
- protected const DELETE = 'delete';
- protected const SAVE = 'save';
- protected const UPDATE = 'update';
+ public const DELETE = 'delete';
+ public const DELETE_BY_QUERY = 'delete_by_query';
+ public const SAVE = 'save';
+ public const UPDATE = 'update';
/**
* @var CommandInterface
@@ -67,6 +68,7 @@ public function __construct(
* @param array $dataPerType
* @throws BulkException
* @throws CouldNotSaveException
+ * @throws \RuntimeException
*/
public function saveToStorage(array $dataPerType): void
{
@@ -74,6 +76,7 @@ public function saveToStorage(array $dataPerType): void
foreach ($dataPerStore as $storeCode => $data) {
$sourceName = $this->storageState->getCurrentDataSourceName([$storeCode, $entityType]);
$this->deleteEntities($data[self::DELETE] ?? [], $sourceName, $entityType);
+ $this->deleteEntitiesByQuery($data[self::DELETE_BY_QUERY] ?? [], $sourceName, $entityType);
$this->saveEntities($data[self::SAVE] ?? [], $sourceName, $entityType);
$this->updateEntities($data[self::UPDATE] ?? [], $sourceName, $entityType);
}
@@ -107,15 +110,45 @@ private function deleteEntities(array $data, string $sourceName, string $entityT
$this->storageWriteSource->bulkDelete($sourceName, $entityType, $data);
}
+ /**
+ * Delete bulk of entities by data, source name and entity type
+ *
+ * @param array $data
+ * @param string $sourceName
+ * @param string $entityType
+ * @return void
+ * @throws \RuntimeException
+ */
+ private function deleteEntitiesByQuery(array $data, string $sourceName, string $entityType): void
+ {
+ if (!$data) {
+ return;
+ }
+ if (!$this->storageSchemaManager->existsDataSource($sourceName)) {
+ $this->logger->debug(\sprintf(
+ 'Cannot delete entities "%s" by query: Index "%s" does not exist',
+ \implode(',', $data),
+ $sourceName
+ ));
+ return;
+ }
+
+ $this->logger->debug(
+ \sprintf('Delete from storage "%s" %s record(s)', $sourceName, count($data)),
+ ['verbose' => $data]
+ );
+ $this->storageWriteSource->deleteByQuery($sourceName, $entityType, $data);
+ }
+
/**
* Save bulk of entities by data, source name and entity type
*
* @param array $data
* @param string $sourceName
* @param string $entityType
- * @throws BulkException
- * @throws CouldNotSaveException
* @return void
+ * @throws CouldNotSaveException
+ * @throws BulkException
*/
private function saveEntities(array $data, string $sourceName, string $entityType): void
{
diff --git a/app/code/Magento/CatalogStorefront/Model/CatalogService.php b/app/code/Magento/CatalogStorefront/Model/CatalogService.php
index 9f8c743d7..870ab74cd 100644
--- a/app/code/Magento/CatalogStorefront/Model/CatalogService.php
+++ b/app/code/Magento/CatalogStorefront/Model/CatalogService.php
@@ -219,9 +219,9 @@ public function importProducts(ImportProductsRequestInterface $request): ImportP
return \in_array($code, $productData->getAttributes());
}, ARRAY_FILTER_USE_KEY);
- $productsInElasticFormat['product'][$storeCode]['update'][] = $product;
+ $productsInElasticFormat['product'][$storeCode][CatalogRepository::UPDATE][] = $product;
} else {
- $productsInElasticFormat['product'][$storeCode]['save'][] = $product;
+ $productsInElasticFormat['product'][$storeCode][CatalogRepository::SAVE][] = $product;
}
}
@@ -251,7 +251,7 @@ public function deleteProducts(DeleteProductsRequestInterface $request): DeleteP
$productsInElasticFormat = [
'product' => [
$storeCode => [
- 'delete' => $request->getProductIds()
+ CatalogRepository::DELETE => $request->getProductIds()
]
]
];
@@ -297,9 +297,9 @@ public function importCategories(ImportCategoriesRequestInterface $request): Imp
return \in_array($code, $categoryData->getAttributes());
}, ARRAY_FILTER_USE_KEY);
- $categoriesInElasticFormat['category'][$storeCode]['update'][] = $category;
+ $categoriesInElasticFormat['category'][$storeCode][CatalogRepository::UPDATE][] = $category;
} else {
- $categoriesInElasticFormat['category'][$storeCode]['save'][] = $category;
+ $categoriesInElasticFormat['category'][$storeCode][CatalogRepository::SAVE][] = $category;
}
}
@@ -333,7 +333,7 @@ public function deleteCategories(DeleteCategoriesRequestInterface $request): Del
$categoriesInElasticFormat = [
'category' => [
$storeId => [
- 'delete' => $request->getCategoryIds()
+ CatalogRepository::DELETE => $request->getCategoryIds()
]
]
];
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Client/CommandInterface.php b/app/code/Magento/CatalogStorefront/Model/Storage/Client/CommandInterface.php
index 53a849b92..f2540179c 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Client/CommandInterface.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Client/CommandInterface.php
@@ -59,4 +59,14 @@ public function bulkUpdate(string $dataSourceName, string $entityName, array $en
* @return void
*/
public function bulkDelete(string $dataSourceName, string $entityName, array $ids): void;
+
+ /**
+ * Performs delete by query.
+ *
+ * @param string $dataSourceName
+ * @param string $entityName
+ * @param array $entries
+ * @throws \RuntimeException
+ */
+ public function deleteByQuery(string $dataSourceName, string $entityName, array $entries): void;
}
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Client/Config/ProductVariant.php b/app/code/Magento/CatalogStorefront/Model/Storage/Client/Config/ProductVariant.php
new file mode 100644
index 000000000..f195e093c
--- /dev/null
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Client/Config/ProductVariant.php
@@ -0,0 +1,57 @@
+ [
+ [
+ 'default_mapping' => [
+ 'match' => '*',
+ 'match_mapping_type' => '*',
+ 'mapping' => [
+ 'index' => false,
+ ],
+ ],
+ ]
+ ],
+ 'properties' => [
+ 'id' => [
+ 'type' => 'keyword'
+ ],
+ 'option_value' => [
+ 'type' => 'keyword',
+ ],
+ 'parent_id' => [
+ 'type' => 'keyword',
+ ],
+ 'product_id' => [
+ 'type' => 'keyword',
+ ],
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchCommand.php b/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchCommand.php
index db474d1c7..62e373e19 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchCommand.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchCommand.php
@@ -135,6 +135,10 @@ private function getDocsArrayInBulkIndexFormat(
'_type' => $entityName,
'_index' => $indexName
];
+ if (isset($document['_id'])) {
+ $metaInfo['_id'] = $document['_id'];
+ unset($document['_id']);
+ }
if (isset($document['parent_id']['parent'])) {
$metaInfo['routing'] = $document['parent_id']['parent'];
}
@@ -152,6 +156,33 @@ private function getDocsArrayInBulkIndexFormat(
return $bulkArray;
}
+ /**
+ * Reformat documents array to delete by query format.
+ *
+ * @param string $indexName
+ * @param string $entityName
+ * @param array $documents
+ * @return array
+ */
+ private function getDocsArrayInDeleteByQueryFormat(
+ string $indexName,
+ string $entityName,
+ array $documents
+ ): array {
+ $array = [
+ 'index' => $indexName,
+ 'type' => $entityName,
+ 'body' => [],
+ 'refresh' => false
+ ];
+ foreach ($documents as $document) {
+ foreach ($document as $key => $value) {
+ $array['body']['query']['bool']['filter']['terms'][$key][] = $value;
+ }
+ }
+ return $array;
+ }
+
/**
* Handle error on Bulk insert
*
@@ -215,4 +246,31 @@ function ($id) {
);
}
}
+
+ /**
+ * @inheritdoc
+ *
+ * @throws \RuntimeException
+ */
+ public function deleteByQuery(string $dataSourceName, string $entityName, array $entries): void
+ {
+ $query = $this->getDocsArrayInDeleteByQueryFormat(
+ $dataSourceName,
+ $entityName,
+ $entries
+ );
+ try {
+ $this->getConnection()->deleteByQuery($query);
+ } catch (\Throwable $throwable) {
+ throw new \RuntimeException(
+ __(
+ 'Error occurred while deleting by query from "%1" index. Entity data: "%2". Error: %3',
+ $dataSourceName,
+ \json_encode($entries),
+ $throwable->getMessage()
+ ),
+ $throwable
+ );
+ }
+ }
}
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php b/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php
index f1b0170fd..3ca6ba0a7 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php
@@ -3,7 +3,6 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
declare(strict_types=1);
namespace Magento\CatalogStorefront\Model\Storage\Client;
@@ -12,6 +11,7 @@
use Magento\CatalogStorefront\Model\Storage\Data\DocumentIteratorFactory;
use Magento\CatalogStorefront\Model\Storage\Data\EntryInterface;
use Magento\CatalogStorefront\Model\Storage\Data\EntryIteratorInterface;
+use Magento\CatalogStorefront\Model\Storage\Data\SearchResultIteratorFactory;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\Exception\RuntimeException;
use Psr\Log\LoggerInterface;
@@ -21,10 +21,8 @@
*/
class ElasticsearchQuery implements QueryInterface
{
- /**
- * @var Config
- */
- private $config;
+ //TODO: Add pagination and remove max size search size https://github.com/magento/catalog-storefront/issues/418
+ private const SEARCH_LIMIT = 5000;
/**
* @var ConnectionPull
@@ -41,6 +39,11 @@ class ElasticsearchQuery implements QueryInterface
*/
private $documentIteratorFactory;
+ /**
+ * @var SearchResultIteratorFactory
+ */
+ private $searchResultIteratorFactory;
+
/**
* @var LoggerInterface
*/
@@ -49,22 +52,22 @@ class ElasticsearchQuery implements QueryInterface
/**
* Initialize Elasticsearch Client
*
- * @param Config $config
* @param ConnectionPull $connectionPull
* @param DocumentFactory $documentFactory
* @param DocumentIteratorFactory $documentIteratorFactory
+ * @param SearchResultIteratorFactory $searchResultIteratorFactory
* @param LoggerInterface $logger
*/
public function __construct(
- Config $config,
ConnectionPull $connectionPull,
DocumentFactory $documentFactory,
DocumentIteratorFactory $documentIteratorFactory,
+ SearchResultIteratorFactory $searchResultIteratorFactory,
LoggerInterface $logger
) {
- $this->config = $config;
$this->documentFactory = $documentFactory;
$this->documentIteratorFactory = $documentIteratorFactory;
+ $this->searchResultIteratorFactory = $searchResultIteratorFactory;
$this->connectionPull = $connectionPull;
$this->logger = $logger;
}
@@ -92,6 +95,81 @@ public function getEntry(string $indexName, string $entityName, int $id, array $
return $this->documentFactory->create($result);
}
+ /**
+ * @inheritdoc
+ */
+ public function searchMatchedEntries(
+ string $indexName,
+ string $entityName,
+ array $searchBody,
+ ?string $queryContext = 'must'
+ ): EntryIteratorInterface {
+ return $this->searchEntries($indexName, $entityName, $searchBody, $queryContext, 'match');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function searchFilteredEntries(
+ string $indexName,
+ string $entityName,
+ array $searchBody,
+ ?string $clauseType = 'term'
+ ): EntryIteratorInterface {
+ return $this->searchEntries($indexName, $entityName, $searchBody, 'filter', $clauseType);
+ }
+
+ /**
+ * Searches entries into elastic search storage.
+ *
+ * @param string $indexName
+ * @param string $entityName
+ * @param array $searchBody
+ * @param string $queryContext
+ * @param string $clauseType
+ * @return EntryIteratorInterface
+ * @throws NotFoundException
+ * @throws RuntimeException
+ */
+ private function searchEntries(
+ string $indexName,
+ string $entityName,
+ array $searchBody,
+ string $queryContext,
+ string $clauseType
+ ): EntryIteratorInterface {
+ //TODO: Add pagination and remove max size search size https://github.com/magento/catalog-storefront/issues/418
+ $query = [
+ 'index' => $indexName,
+ 'type' => $entityName,
+ 'body' => [],
+ 'size' => self::SEARCH_LIMIT
+ ];
+
+ foreach ($searchBody as $key => $value) {
+ $query['body']['query']['bool'][$queryContext][][$clauseType][$key] = $value;
+ }
+
+ try {
+ $result = $this->connectionPull->getConnection()->search($query);
+ } catch (\Throwable $throwable) {
+ throw new RuntimeException(
+ __("Storage error: {$throwable->getMessage()} Query was:" . \json_encode($query)),
+ $throwable
+ );
+ }
+ //TODO: Add pagination and remove max size search size https://github.com/magento/catalog-storefront/issues/418
+ if (isset($result['hits']['total']['value']) && $result['hits']['total']['value'] > self::SEARCH_LIMIT) {
+ throw new \OverflowException(
+ "Storage error: Search returned too many results to handle. Query was: " . \json_encode($query)
+ );
+ }
+
+ $this->checkErrors($result, $indexName);
+
+ return $this->searchResultIteratorFactory->create($result);
+ }
+
/**
* @inheritdoc
*/
@@ -127,7 +205,7 @@ public function getEntries(string $indexName, string $entityName, array $ids, ar
* @param string $indexName
* @throws NotFoundException
*/
- private function checkErrors(array $result, string $indexName)
+ private function checkErrors(array $result, string $indexName): void
{
$errors = [];
$notFound = [];
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php b/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php
index d50efca87..e0f0ede6b 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php
@@ -57,4 +57,46 @@ public function getEntries(
array $ids,
array $fields
): EntryIteratorInterface;
+
+ /**
+ * Search entries by specified search arguments using match query context and supplied clause type.
+ *
+ * $searchBody contains "search field" -> "search value".
+ * "search field" must be indexed in order for this query to work.
+ *
+ * @param string $indexName
+ * @param string $entityName
+ * @param array $searchBody
+ * @param string|null $queryContext
+ * @return EntryIteratorInterface
+ * @throws NotFoundException
+ * @throws RuntimeException
+ */
+ public function searchMatchedEntries(
+ string $indexName,
+ string $entityName,
+ array $searchBody,
+ ?string $queryContext = 'must'
+ ): EntryIteratorInterface;
+
+ /**
+ * Search entries by specified search arguments using filter query context and supplied clause type.
+ *
+ * $searchBody contains "search field" -> "search value".
+ * "search field" must be indexed in order for this query to work.
+ *
+ * @param string $indexName
+ * @param string $entityName
+ * @param array $searchBody
+ * @param string|null $clauseType
+ * @return EntryIteratorInterface
+ * @throws NotFoundException
+ * @throws RuntimeException
+ */
+ public function searchFilteredEntries(
+ string $indexName,
+ string $entityName,
+ array $searchBody,
+ ?string $clauseType = 'term'
+ ): EntryIteratorInterface;
}
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Data/Document.php b/app/code/Magento/CatalogStorefront/Model/Storage/Data/Document.php
index 38851648c..c96c68d60 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Data/Document.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Data/Document.php
@@ -25,7 +25,7 @@ class Document implements EntryInterface
public function __construct(array $data)
{
$this->data = $data['_source'] ?? [];
- $this->data['id'] = (string)$data['_id'];
+ $this->data['id'] = $this->data['id'] ?? (string)$data['_id'];
}
/**
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIterator.php b/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIterator.php
index da096d6d9..bd2ef6988 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIterator.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIterator.php
@@ -69,12 +69,13 @@ public function current(): EntryInterface
/**
* @inheritdoc
*/
- public function toArray(): array
+ public function toArray(bool $sortById = true): array
{
$data = [];
reset($this->documents);
- foreach ($this->documents as $doc) {
- $data[$doc->getId()] = $doc->getData();
+ foreach ($this->documents as $docKey => $doc) {
+ $id = $sortById ? $doc->getId() : $docKey;
+ $data[$id] = $doc->getData();
}
return $data;
}
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIteratorFactory.php b/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIteratorFactory.php
index 5a460140e..e71b3ef4a 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIteratorFactory.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Data/DocumentIteratorFactory.php
@@ -9,7 +9,7 @@
namespace Magento\CatalogStorefront\Model\Storage\Data;
/**
- * Document iterator factory.
+ * Factory for DocumentIterator class
*/
class DocumentIteratorFactory
{
@@ -54,7 +54,7 @@ public function create(array $ids, array $data = []): DocumentIterator
if (isset($item['found']) && $item['found'] === false) {
continue;
}
- $tmp[(int)$item['_id']] = $this->documentFactory->create($item);
+ $tmp[$item['_id']] = $this->documentFactory->create($item);
}
foreach ($ids as $id) {
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Data/EntryIteratorInterface.php b/app/code/Magento/CatalogStorefront/Model/Storage/Data/EntryIteratorInterface.php
index 405eb3203..0c78cf558 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/Data/EntryIteratorInterface.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Data/EntryIteratorInterface.php
@@ -23,7 +23,8 @@ public function current(): EntryInterface;
/**
* Convert data to array.
*
+ * @param bool $sortById
* @return array
*/
- public function toArray(): array;
+ public function toArray(bool $sortById = true): array;
}
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Data/SearchResultIteratorFactory.php b/app/code/Magento/CatalogStorefront/Model/Storage/Data/SearchResultIteratorFactory.php
new file mode 100644
index 000000000..96276e386
--- /dev/null
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/Data/SearchResultIteratorFactory.php
@@ -0,0 +1,56 @@
+objectManager = $objectManager;
+ $this->documentFactory = $documentFactory;
+ }
+
+ /**
+ * Create SearchResultIterator class instance with specified parameters
+ *
+ * @param array $result
+ *
+ * @return DocumentIterator
+ */
+ public function create(array $result): DocumentIterator
+ {
+ $documents = [];
+
+ foreach ($result['hits']['hits'] as $item) {
+ $documents[$item['_id']] = $this->documentFactory->create($item);
+ }
+
+ return $this->objectManager->create(DocumentIterator::class, ['documents' => $documents]);
+ }
+}
diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/State.php b/app/code/Magento/CatalogStorefront/Model/Storage/State.php
index 80c18230f..2ff1dad97 100644
--- a/app/code/Magento/CatalogStorefront/Model/Storage/State.php
+++ b/app/code/Magento/CatalogStorefront/Model/Storage/State.php
@@ -41,6 +41,8 @@ public function getAliasName(): string
/**
* Get current data source name of storage taking into account version of the data source.
*
+ * TODO: Adapt to work without store code https://github.com/magento/catalog-storefront/issues/417
+ *
* @param array $scopes
* @return string
*/
diff --git a/app/code/Magento/CatalogStorefront/Model/VariantService.php b/app/code/Magento/CatalogStorefront/Model/VariantService.php
new file mode 100644
index 000000000..21436a24e
--- /dev/null
+++ b/app/code/Magento/CatalogStorefront/Model/VariantService.php
@@ -0,0 +1,283 @@
+importVariantsResponseFactory = $importVariantsResponseFactory;
+ $this->logger = $logger;
+ $this->catalogRepository = $catalogRepository;
+ $this->deleteVariantsResponseFactory = $deleteVariantsResponseFactory;
+ $this->productVariantsDataProvider = $productVariantsDataProvider;
+ $this->productVariantMapper = $productVariantMapper;
+ $this->catalogService = $catalogService;
+ $this->productsGetRequestInterfaceFactory = $productsGetRequestInterfaceFactory;
+ }
+
+ /**
+ * Import requested variants in storage
+ *
+ * @param ImportVariantsRequestInterface $request
+ * @return ImportVariantsResponseInterface
+ */
+ public function importProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface
+ {
+ try {
+ $variantsInElasticFormat = [];
+ foreach ($request->getVariants() as $variantData) {
+ $optionValues = $variantData->getOptionValues();
+ $id = $variantData->getId();
+ $explodedId = explode('/', $id);
+ $parentId = $explodedId[1];
+ $childId = $explodedId[2];
+ foreach ($optionValues as $optionValue) {
+ preg_match('/(?<=:)(.*)(?=\/)/', $optionValue, $match);
+ $attrCode = $match[0];
+ $variant = [
+ '_id' => $id . '/' . $attrCode,
+ 'id' => $id,
+ 'option_value' => $optionValue,
+ 'product_id' => $childId,
+ 'parent_id' => $parentId
+ ];
+ //TODO: Adapt to work without store code https://github.com/magento/catalog-storefront/issues/417
+ $variantsInElasticFormat['product_variant'][self::EMPTY_STORE_CODE][CatalogRepository::SAVE][] =
+ $variant;
+ }
+ }
+
+ $this->catalogRepository->saveToStorage($variantsInElasticFormat);
+ $importVariantsResponse = $this->importVariantsResponseFactory->create();
+ $importVariantsResponse->setMessage('Records imported successfully');
+ $importVariantsResponse->setStatus(true);
+
+ return $importVariantsResponse;
+ } catch (\Exception $e) {
+ $message = 'Cannot process product variants import: ' . $e->getMessage();
+ $this->logger->error($message, ['exception' => $e]);
+ $importCategoriesResponse = $this->importVariantsResponseFactory->create();
+ $importCategoriesResponse->setMessage($message);
+ $importCategoriesResponse->setStatus(false);
+ return $importCategoriesResponse;
+ }
+ }
+
+ /**
+ * Delete product variants from storage.
+ *
+ * @param DeleteVariantsRequestInterface $request
+ * @return DeleteVariantsResponseInterface
+ */
+ public function deleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface
+ {
+ $deleteFields = \array_map(function ($id) {
+ return ['id' => $id];
+ }, $request->getId());
+
+ $variantsInElasticFormat = [
+ 'product_variant' => [
+ //TODO: Adapt to work without store code https://github.com/magento/catalog-storefront/issues/417
+ self::EMPTY_STORE_CODE => [
+ CatalogRepository::DELETE_BY_QUERY => $deleteFields
+ ]
+ ]
+ ];
+
+ $deleteVariantsResponse = $this->deleteVariantsResponseFactory->create();
+ try {
+ $this->catalogRepository->saveToStorage($variantsInElasticFormat);
+ $deleteVariantsResponse->setMessage('Product variants were removed successfully');
+ $deleteVariantsResponse->setStatus(true);
+ } catch (\Throwable $e) {
+ $message = 'Unable to delete product variants';
+ $this->logger->error($message, ['exception' => $e]);
+ $deleteVariantsResponse->setMessage($message);
+ $deleteVariantsResponse->setStatus(false);
+ }
+
+ return $deleteVariantsResponse;
+ }
+
+ /**
+ * Get product variants from storage.
+ *
+ * Only variants whose corresponding products are 'enabled' and stored in storage are returned.
+ * TODO: Add pagination https://github.com/magento/catalog-storefront/issues/418
+ *
+ * @param ProductVariantRequestInterface $request
+ * @return ProductVariantResponseInterface
+ * @throws \InvalidArgumentException
+ * @throws RuntimeException
+ * @throws \Throwable
+ */
+ public function getProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface
+ {
+ $productId = $request->getProductId();
+ $store = $request->getStore();
+ $rawVariants = $this->productVariantsDataProvider->fetchByProductId((int)$productId);
+
+ \ksort($rawVariants);
+ if (empty($rawVariants)) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'No products variants for product with id %s are found in catalog.',
+ $productId
+ )
+ );
+ }
+
+ $compositeVariants = [];
+ foreach ($rawVariants as $rawVariant) {
+ $compositeVariants[$rawVariant['id']]['id'] = $rawVariant['id'];
+ $compositeVariants[$rawVariant['id']]['option_values'][] = $rawVariant['option_value'];
+ $compositeVariants[$rawVariant['id']]['product_id'] = $rawVariant['product_id'];
+ }
+
+ $productsGetRequest = $this->productsGetRequestInterfaceFactory->create();
+ $productsGetRequest->setIds(\array_column($compositeVariants, 'product_id'));
+ $productsGetRequest->setStore($store);
+ $productsGetRequest->setAttributeCodes(["id", "status"]);
+ $catalogProducts = $this->catalogService->getProducts($productsGetRequest)->getItems();
+
+ $activeProducts = [];
+ foreach ($catalogProducts as $product) {
+ if ($product->getStatus() === self::PRODUCT_STATUS_ENABLED) {
+ $activeProducts[$product->getId()] = $product->getId();
+ }
+ }
+
+ $variants = [];
+ foreach ($compositeVariants as $compositeVariant) {
+ if (isset($activeProducts[$compositeVariant['product_id']])) {
+ $variants[] = $this->productVariantMapper->setData($compositeVariant)->build();
+ }
+ }
+
+ if (empty($variants)) {
+ throw new \InvalidArgumentException(
+ sprintf(
+ 'No valid products variants for product with id %s are found in catalog.',
+ $productId
+ )
+ );
+ }
+
+ $response = new ProductVariantResponse();
+ $response->setMatchedVariants($variants);
+ return $response;
+ }
+
+ /**
+ * TODO: Implement getVariantsMatch() method and remove the warning suppression.
+ *
+ * @param OptionSelectionRequestInterface $request
+ * @return ProductVariantResponseInterface
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface
+ {
+ return new ProductVariantResponse();
+ }
+}
diff --git a/app/code/Magento/CatalogStorefront/Test/Api/ProductVariants/ConfigurableVariantsTest.php b/app/code/Magento/CatalogStorefront/Test/Api/ProductVariants/ConfigurableVariantsTest.php
new file mode 100644
index 000000000..01ce45853
--- /dev/null
+++ b/app/code/Magento/CatalogStorefront/Test/Api/ProductVariants/ConfigurableVariantsTest.php
@@ -0,0 +1,172 @@
+variantService = Bootstrap::getObjectManager()->create(VariantService::class);
+ $this->variantsRequestInterface = Bootstrap::getObjectManager()->create(ProductVariantRequestInterface::class);
+ $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class);
+ $this->responseArrayMapper = Bootstrap::getObjectManager()->create(
+ ProductVariantResponseArrayMapper::class
+ );
+ }
+
+ /**
+ * Validate configurable product variants data
+ *
+ * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php
+ * @magentoDbIsolation disabled
+ * @throws NoSuchEntityException
+ * @throws \Throwable
+ */
+ public function testConfigurableProductVariants(): void
+ {
+ $simpleSkus = [
+ 'simple_0',
+ 'simple_1',
+ 'simple_2',
+ 'simple_3',
+ 'simple_4',
+ 'simple_5',
+ 'simple_6',
+ 'simple_7',
+ 'simple_8'
+ ];
+ /** @var $configurable Product */
+ $configurable = $this->productRepository->get('configurable');
+ $simples = [];
+ foreach ($simpleSkus as $sku) {
+ $simples[] = $this->productRepository->get($sku);
+ }
+
+ $this->variantsRequestInterface->setProductId((string)$configurable->getId());
+ $this->variantsRequestInterface->setStore('default');
+
+ //This sleep ensures that the elastic index has sufficient time to refresh
+ //See https://www.elastic.co/guide/en/elasticsearch/reference/6.8/docs-refresh.html#docs-refresh
+ sleep(1);
+ /** @var $variantServiceItem ProductVariantResponse */
+ $variantServiceItem = $this->variantService->getProductVariants($this->variantsRequestInterface);
+ $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants'];
+
+ $expected = $this->getExpectedProductVariants($configurable, $simples);
+ self::assertCount(9, $actual);
+ $this->compare($expected, $actual);
+ }
+
+ /**
+ * Validate that only one variant is returned when one simple product out of two is disabled.
+ *
+ * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable_disable_first_child.php
+ * @magentoDbIsolation disabled
+ * @throws NoSuchEntityException
+ * @throws \Throwable
+ */
+ public function testProductVariantsDisabledProduct(): void
+ {
+ /** @var $configurable Product */
+ $configurable = $this->productRepository->get('configurable');
+
+ $this->variantsRequestInterface->setProductId((string)$configurable->getId());
+ $this->variantsRequestInterface->setStore('default');
+
+ //This sleep ensures that the elastic index has sufficient time to refresh
+ //See https://www.elastic.co/guide/en/elasticsearch/reference/6.8/docs-refresh.html#docs-refresh
+ sleep(1);
+ /** @var $variantServiceItem ProductVariantResponse */
+ $variantServiceItem = $this->variantService->getProductVariants($this->variantsRequestInterface);
+ $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants'];
+ self::assertCount(1, $actual);
+ }
+
+ /**
+ * Get the expected variants for configurable products.
+ *
+ * @param Product $configurable
+ * @param Product[] $simples
+ * @return array
+ */
+ private function getExpectedProductVariants(Product $configurable, array $simples): array
+ {
+ $configurableOptions = $configurable->getExtensionAttributes()->getConfigurableProductOptions();
+ $variants = [];
+ foreach ($simples as $simple) {
+ $id = (\sprintf(
+ 'configurable/%1$s/%2$s',
+ $configurable->getId(),
+ $simple->getId(),
+ ));
+ $optionValues = [];
+ foreach ($configurableOptions as $configurableOption) {
+ $attributeCode = $configurableOption->getProductAttribute()->getAttributeCode();
+ foreach ($configurableOption->getValues() as $configurableOptionValue) {
+ if ($simple->getData($attributeCode) === $configurableOptionValue->getValueIndex()) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ $optionUid = \base64_encode(\sprintf(
+ 'configurable/%1$s/%2$s',
+ $configurableOption->getAttributeId(),
+ $configurableOptionValue->getValueIndex()
+ ));
+ $optionValues[] = \sprintf(
+ '%1$s:%2$s/%3$s',
+ $configurable->getId(),
+ $attributeCode,
+ $optionUid
+ );
+ }
+ }
+ }
+ $variants[$id] = [
+ 'id' => $id,
+ 'option_values' => $optionValues,
+ 'product_id' => $simple->getId(),
+ ];
+ }
+ return array_values($variants);
+ }
+}
diff --git a/app/code/Magento/CatalogStorefront/Test/Api/StorefrontTestsAbstract.php b/app/code/Magento/CatalogStorefront/Test/Api/StorefrontTestsAbstract.php
index 4d5a0a8d7..eb5419987 100644
--- a/app/code/Magento/CatalogStorefront/Test/Api/StorefrontTestsAbstract.php
+++ b/app/code/Magento/CatalogStorefront/Test/Api/StorefrontTestsAbstract.php
@@ -11,6 +11,7 @@
use Magento\CatalogStorefront\Model\Storage\State;
use Magento\Framework\App\ResourceConnection;
use Magento\Indexer\Model\Indexer;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\TestCase;
use Magento\TestFramework\Workaround\ConsumerInvoker;
@@ -29,7 +30,8 @@ abstract class StorefrontTestsAbstract extends TestCase
*/
private const FEEDS = [
'catalog_data_exporter_categories',
- 'catalog_data_exporter_products'
+ 'catalog_data_exporter_products',
+ 'catalog_data_exporter_product_variants'
];
/**
@@ -37,7 +39,8 @@ abstract class StorefrontTestsAbstract extends TestCase
*/
private const QUEUES = [
'catalog.category.export.queue',
- 'catalog.product.export.queue'
+ 'catalog.product.export.queue',
+ 'catalog.product.variants.export.queue'
];
/**
@@ -45,6 +48,21 @@ abstract class StorefrontTestsAbstract extends TestCase
*/
private $compareArraysRecursively;
+ /**
+ * @var DataDefinitionInterface
+ */
+ private $dataDefinition;
+
+ /**
+ * @var State
+ */
+ private $storageState;
+
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @inheritdoc
*/
@@ -52,6 +70,9 @@ protected function setUp(): void
{
parent::setUp();
$this->compareArraysRecursively = Bootstrap::getObjectManager()->create(CompareArraysRecursively::class);
+ $this->dataDefinition = Bootstrap::getObjectManager()->get(DataDefinitionInterface::class);
+ $this->storageState = Bootstrap::getObjectManager()->get(State::class);
+ $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class);
}
/**
@@ -70,31 +91,36 @@ protected function tearDown(): void
*/
private function clearCatalogStorage(): void
{
- /** @var DataDefinitionInterface $dataDefinition */
- $dataDefinition = Bootstrap::getObjectManager()->get(
- DataDefinitionInterface::class
- );
- /** @var State $storageState */
- $storageState = Bootstrap::getObjectManager()->get(
- State::class
- );
- $entityTypes = ['category', 'product'];
- /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */
- $storeManager = Bootstrap::getObjectManager()
- ->get(\Magento\Store\Model\StoreManagerInterface::class);
- $availableStores = $storeManager->getStores();
+ $entityTypes = ['category', 'product', 'product_variant'];
+ $availableStores = $this->storeManager->getStores();
+
foreach ($entityTypes as $entityType) {
- foreach ($availableStores as $store) {
- try {
- $sourceName = $storageState->getCurrentDataSourceName([$store->getCode(), $entityType]);
- $dataDefinition->deleteDataSource($sourceName);
- } catch (\Exception $e) {
- // Do nothing if no source
+ if ($entityType === 'product_variant') {
+ $this->deleteDataSource('', $entityType);
+ } else {
+ foreach ($availableStores as $store) {
+ $this->deleteDataSource($store->getCode(), $entityType);
}
}
}
}
+ /**
+ * Delete data source
+ *
+ * @param string $scope
+ * @param string $entityType
+ */
+ private function deleteDataSource(string $scope, string $entityType): void
+ {
+ try {
+ $sourceName = $this->storageState->getCurrentDataSourceName([$scope, $entityType]);
+ $this->dataDefinition->deleteDataSource($sourceName);
+ } catch (\Exception $e) {
+ // Do nothing if no source
+ }
+ }
+
/**
* On each tear down we need to clean all feed data
*
@@ -187,10 +213,12 @@ public function runConsumers(array $consumers = []) : void
*/
private function resetIndexerToOnSave(): void
{
- $indexer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ $indexer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
->get(Indexer::class);
$indexer->load('catalog_data_exporter_products');
$indexer->setScheduled(false);
+ $indexer->load('catalog_data_exporter_product_variants');
+ $indexer->setScheduled(false);
}
/**
diff --git a/app/code/Magento/CatalogStorefront/etc/di.xml b/app/code/Magento/CatalogStorefront/etc/di.xml
index 0b1276769..b497f8e84 100644
--- a/app/code/Magento/CatalogStorefront/etc/di.xml
+++ b/app/code/Magento/CatalogStorefront/etc/di.xml
@@ -13,12 +13,15 @@
+
+
- Magento\CatalogStorefront\Model\Storage\Client\Config\Product
- Magento\CatalogStorefront\Model\Storage\Client\Config\Category
+ - Magento\CatalogStorefront\Model\Storage\Client\Config\ProductVariant
diff --git a/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php b/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php
index e1fc92573..7dde8846a 100644
--- a/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php
+++ b/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php
@@ -40,46 +40,46 @@ public function __construct(
}
/**
- * Autogenerated description for ImportProductVariants in memory client service method
+ * Autogenerated description for importProductVariants in memory client service method
*
* @param ImportVariantsRequestInterface $request
* @return ImportVariantsResponseInterface
*/
- public function ImportProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface
+ public function importProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface
{
- return $this->service->ImportProductVariants($request);
+ return $this->service->importProductVariants($request);
}
/**
- * Autogenerated description for DeleteProductVariants in memory client service method
+ * Autogenerated description for deleteProductVariants in memory client service method
*
* @param DeleteVariantsRequestInterface $request
* @return DeleteVariantsResponseInterface
*/
- public function DeleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface
+ public function deleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface
{
- return $this->service->DeleteProductVariants($request);
+ return $this->service->deleteProductVariants($request);
}
/**
- * Autogenerated description for GetProductVariants in memory client service method
+ * Autogenerated description for getProductVariants in memory client service method
*
* @param ProductVariantRequestInterface $request
* @return ProductVariantResponseInterface
*/
- public function GetProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface
+ public function getProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface
{
- return $this->service->GetProductVariants($request);
+ return $this->service->getProductVariants($request);
}
/**
- * Autogenerated description for GetVariantsMatch in memory client service method
+ * Autogenerated description for getVariantsMatch in memory client service method
*
* @param OptionSelectionRequestInterface $request
* @return ProductVariantResponseInterface
*/
- public function GetVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface
+ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface
{
- return $this->service->GetVariantsMatch($request);
+ return $this->service->getVariantsMatch($request);
}
}
diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php
index dbb7ec5ed..52e1ffd0f 100644
--- a/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php
+++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php
@@ -58,23 +58,23 @@ public function __construct(
* @return ImportVariantsResponseInterface
* @throws \Throwable
*/
- public function ImportProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface
+ public function importProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface
{
- $protoRequest = $this->ImportProductVariantsToProto($request);
- [$protoResult, $status] = $this->protoClient->ImportProductVariants($protoRequest)->wait();
+ $protoRequest = $this->importProductVariantsToProto($request);
+ [$protoResult, $status] = $this->protoClient->importProductVariants($protoRequest)->wait();
if ($status->code !== 0) {
throw new \RuntimeException($status->details, $status->code);
}
- return $this->ImportProductVariantsFromProto($protoResult);
+ return $this->importProductVariantsFromProto($protoResult);
}
/**
- * Autogenerated description for ImportProductVariants method
+ * Autogenerated description for importProductVariants method
*
* @param ImportVariantsRequestInterface $value
* @return ImportVariantsRequest
*/
- private function ImportProductVariantsToProto(ImportVariantsRequestInterface $value): ImportVariantsRequest
+ private function importProductVariantsToProto(ImportVariantsRequestInterface $value): ImportVariantsRequest
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\ImportVariantsRequest
// to \Magento\CatalogStorefrontApi\Proto\ImportVariantsRequest
@@ -108,13 +108,13 @@ private function ImportProductVariantsToProto(ImportVariantsRequestInterface $va
}
/**
- * Autogenerated description for ImportProductVariants method
+ * Autogenerated description for importProductVariants method
*
* @param ImportVariantsResponse $value
* @return ImportVariantsResponseInterface
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function ImportProductVariantsFromProto(ImportVariantsResponse $value): ImportVariantsResponseInterface
+ private function importProductVariantsFromProto(ImportVariantsResponse $value): ImportVariantsResponseInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\ImportVariantsResponse
// to \Magento\CatalogStorefrontApi\Api\Data\ImportVariantsResponse
@@ -137,23 +137,23 @@ private function ImportProductVariantsFromProto(ImportVariantsResponse $value):
* @return DeleteVariantsResponseInterface
* @throws \Throwable
*/
- public function DeleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface
+ public function deleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface
{
- $protoRequest = $this->DeleteProductVariantsToProto($request);
- [$protoResult, $status] = $this->protoClient->DeleteProductVariants($protoRequest)->wait();
+ $protoRequest = $this->deleteProductVariantsToProto($request);
+ [$protoResult, $status] = $this->protoClient->deleteProductVariants($protoRequest)->wait();
if ($status->code !== 0) {
throw new \RuntimeException($status->details, $status->code);
}
- return $this->DeleteProductVariantsFromProto($protoResult);
+ return $this->deleteProductVariantsFromProto($protoResult);
}
/**
- * Autogenerated description for DeleteProductVariants method
+ * Autogenerated description for deleteProductVariants method
*
* @param DeleteVariantsRequestInterface $value
* @return DeleteVariantsRequest
*/
- private function DeleteProductVariantsToProto(DeleteVariantsRequestInterface $value): DeleteVariantsRequest
+ private function deleteProductVariantsToProto(DeleteVariantsRequestInterface $value): DeleteVariantsRequest
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\DeleteVariantsRequest
// to \Magento\CatalogStorefrontApi\Proto\DeleteVariantsRequest
@@ -173,13 +173,13 @@ private function DeleteProductVariantsToProto(DeleteVariantsRequestInterface $va
}
/**
- * Autogenerated description for DeleteProductVariants method
+ * Autogenerated description for deleteProductVariants method
*
* @param DeleteVariantsResponse $value
* @return DeleteVariantsResponseInterface
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function DeleteProductVariantsFromProto(DeleteVariantsResponse $value): DeleteVariantsResponseInterface
+ private function deleteProductVariantsFromProto(DeleteVariantsResponse $value): DeleteVariantsResponseInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\DeleteVariantsResponse
// to \Magento\CatalogStorefrontApi\Api\Data\DeleteVariantsResponse
@@ -202,23 +202,23 @@ private function DeleteProductVariantsFromProto(DeleteVariantsResponse $value):
* @return ProductVariantResponseInterface
* @throws \Throwable
*/
- public function GetProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface
+ public function getProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface
{
- $protoRequest = $this->GetProductVariantsToProto($request);
- [$protoResult, $status] = $this->protoClient->GetProductVariants($protoRequest)->wait();
+ $protoRequest = $this->getProductVariantsToProto($request);
+ [$protoResult, $status] = $this->protoClient->getProductVariants($protoRequest)->wait();
if ($status->code !== 0) {
throw new \RuntimeException($status->details, $status->code);
}
- return $this->GetProductVariantsFromProto($protoResult);
+ return $this->getProductVariantsFromProto($protoResult);
}
/**
- * Autogenerated description for GetProductVariants method
+ * Autogenerated description for getProductVariants method
*
* @param ProductVariantRequestInterface $value
* @return ProductVariantRequest
*/
- private function GetProductVariantsToProto(ProductVariantRequestInterface $value): ProductVariantRequest
+ private function getProductVariantsToProto(ProductVariantRequestInterface $value): ProductVariantRequest
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\ProductVariantRequest
// to \Magento\CatalogStorefrontApi\Proto\ProductVariantRequest
@@ -235,13 +235,13 @@ private function GetProductVariantsToProto(ProductVariantRequestInterface $value
}
/**
- * Autogenerated description for GetProductVariants method
+ * Autogenerated description for getProductVariants method
*
* @param ProductVariantResponse $value
* @return ProductVariantResponseInterface
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function GetProductVariantsFromProto(ProductVariantResponse $value): ProductVariantResponseInterface
+ private function getProductVariantsFromProto(ProductVariantResponse $value): ProductVariantResponseInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse
// to \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse
@@ -282,23 +282,23 @@ private function GetProductVariantsFromProto(ProductVariantResponse $value): Pro
* @return ProductVariantResponseInterface
* @throws \Throwable
*/
- public function GetVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface
+ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface
{
- $protoRequest = $this->GetVariantsMatchToProto($request);
- [$protoResult, $status] = $this->protoClient->GetVariantsMatch($protoRequest)->wait();
+ $protoRequest = $this->getVariantsMatchToProto($request);
+ [$protoResult, $status] = $this->protoClient->getVariantsMatch($protoRequest)->wait();
if ($status->code !== 0) {
throw new \RuntimeException($status->details, $status->code);
}
- return $this->GetVariantsMatchFromProto($protoResult);
+ return $this->getVariantsMatchFromProto($protoResult);
}
/**
- * Autogenerated description for GetVariantsMatch method
+ * Autogenerated description for getVariantsMatch method
*
* @param OptionSelectionRequestInterface $value
* @return OptionSelectionRequest
*/
- private function GetVariantsMatchToProto(OptionSelectionRequestInterface $value): OptionSelectionRequest
+ private function getVariantsMatchToProto(OptionSelectionRequestInterface $value): OptionSelectionRequest
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest
// to \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest
@@ -319,13 +319,13 @@ private function GetVariantsMatchToProto(OptionSelectionRequestInterface $value)
}
/**
- * Autogenerated description for GetVariantsMatch method
+ * Autogenerated description for getVariantsMatch method
*
* @param ProductVariantResponse $value
* @return ProductVariantResponseInterface
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function GetVariantsMatchFromProto(ProductVariantResponse $value): ProductVariantResponseInterface
+ private function getVariantsMatchFromProto(ProductVariantResponse $value): ProductVariantResponseInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse
// to \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse
diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php
index 47bcff6aa..d53e3a07e 100644
--- a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php
+++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php
@@ -24,38 +24,38 @@
interface VariantServiceInterface
{
/**
- * Autogenerated description for ImportProductVariants interface method
+ * Autogenerated description for importProductVariants interface method
*
* @param ImportVariantsRequestInterface $request
* @return ImportVariantsResponseInterface
* @throws \Throwable
*/
- public function ImportProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface;
+ public function importProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface;
/**
- * Autogenerated description for DeleteProductVariants interface method
+ * Autogenerated description for deleteProductVariants interface method
*
* @param DeleteVariantsRequestInterface $request
* @return DeleteVariantsResponseInterface
* @throws \Throwable
*/
- public function DeleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface;
+ public function deleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface;
/**
- * Autogenerated description for GetProductVariants interface method
+ * Autogenerated description for getProductVariants interface method
*
* @param ProductVariantRequestInterface $request
* @return ProductVariantResponseInterface
* @throws \Throwable
*/
- public function GetProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface;
+ public function getProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface;
/**
- * Autogenerated description for GetVariantsMatch interface method
+ * Autogenerated description for getVariantsMatch interface method
*
* @param OptionSelectionRequestInterface $request
* @return ProductVariantResponseInterface
* @throws \Throwable
*/
- public function GetVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface;
+ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface;
}
diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php
index e79790288..2b1abe936 100644
--- a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php
+++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php
@@ -48,19 +48,19 @@ public function __construct(
}
/**
- * Autogenerated description for ImportProductVariants method
+ * Autogenerated description for importProductVariants method
*
* @param \Spiral\GRPC\ContextInterface $ctx
* @param ImportVariantsRequest $in
* @return ImportVariantsResponse
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function ImportProductVariants(\Spiral\GRPC\ContextInterface $ctx, ImportVariantsRequest $in): ImportVariantsResponse
+ public function importProductVariants(\Spiral\GRPC\ContextInterface $ctx, ImportVariantsRequest $in): ImportVariantsResponse
{
try {
- $magentoDtoRequest = $this->ImportProductVariantsFromProto($in);
- $magentoDtoResponse = $this->service->ImportProductVariants($magentoDtoRequest);
- return $this->ImportProductVariantsToProto($magentoDtoResponse);
+ $magentoDtoRequest = $this->importProductVariantsFromProto($in);
+ $magentoDtoResponse = $this->service->importProductVariants($magentoDtoRequest);
+ return $this->importProductVariantsToProto($magentoDtoResponse);
} catch (\Exception $e) {
throw new \Spiral\GRPC\Exception\InvokeException(
$e->getMessage(),
@@ -72,12 +72,12 @@ public function ImportProductVariants(\Spiral\GRPC\ContextInterface $ctx, Import
}
/**
- * Autogenerated description for ImportProductVariants method
+ * Autogenerated description for importProductVariants method
*
* @param ImportVariantsRequest $value
* @return ImportVariantsRequestInterface
*/
- private function ImportProductVariantsFromProto(ImportVariantsRequest $value): ImportVariantsRequestInterface
+ private function importProductVariantsFromProto(ImportVariantsRequest $value): ImportVariantsRequestInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\ImportVariantsRequest
// to \Magento\CatalogStorefrontApi\Api\Data\ImportVariantsRequest
@@ -111,13 +111,13 @@ private function ImportProductVariantsFromProto(ImportVariantsRequest $value): I
}
/**
- * Autogenerated description for ImportProductVariants method
+ * Autogenerated description for importProductVariants method
*
* @param ImportVariantsResponseInterface $value
* @return ImportVariantsResponse
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function ImportProductVariantsToProto(ImportVariantsResponseInterface $value): ImportVariantsResponse
+ private function importProductVariantsToProto(ImportVariantsResponseInterface $value): ImportVariantsResponse
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\ImportVariantsResponse
// to \Magento\CatalogStorefrontApi\Proto\ImportVariantsResponse
@@ -134,19 +134,19 @@ private function ImportProductVariantsToProto(ImportVariantsResponseInterface $v
}
/**
- * Autogenerated description for DeleteProductVariants method
+ * Autogenerated description for deleteProductVariants method
*
* @param \Spiral\GRPC\ContextInterface $ctx
* @param DeleteVariantsRequest $in
* @return DeleteVariantsResponse
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function DeleteProductVariants(\Spiral\GRPC\ContextInterface $ctx, DeleteVariantsRequest $in): DeleteVariantsResponse
+ public function deleteProductVariants(\Spiral\GRPC\ContextInterface $ctx, DeleteVariantsRequest $in): DeleteVariantsResponse
{
try {
- $magentoDtoRequest = $this->DeleteProductVariantsFromProto($in);
- $magentoDtoResponse = $this->service->DeleteProductVariants($magentoDtoRequest);
- return $this->DeleteProductVariantsToProto($magentoDtoResponse);
+ $magentoDtoRequest = $this->deleteProductVariantsFromProto($in);
+ $magentoDtoResponse = $this->service->deleteProductVariants($magentoDtoRequest);
+ return $this->deleteProductVariantsToProto($magentoDtoResponse);
} catch (\Exception $e) {
throw new \Spiral\GRPC\Exception\InvokeException(
$e->getMessage(),
@@ -158,12 +158,12 @@ public function DeleteProductVariants(\Spiral\GRPC\ContextInterface $ctx, Delete
}
/**
- * Autogenerated description for DeleteProductVariants method
+ * Autogenerated description for deleteProductVariants method
*
* @param DeleteVariantsRequest $value
* @return DeleteVariantsRequestInterface
*/
- private function DeleteProductVariantsFromProto(DeleteVariantsRequest $value): DeleteVariantsRequestInterface
+ private function deleteProductVariantsFromProto(DeleteVariantsRequest $value): DeleteVariantsRequestInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\DeleteVariantsRequest
// to \Magento\CatalogStorefrontApi\Api\Data\DeleteVariantsRequest
@@ -183,13 +183,13 @@ private function DeleteProductVariantsFromProto(DeleteVariantsRequest $value): D
}
/**
- * Autogenerated description for DeleteProductVariants method
+ * Autogenerated description for deleteProductVariants method
*
* @param DeleteVariantsResponseInterface $value
* @return DeleteVariantsResponse
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function DeleteProductVariantsToProto(DeleteVariantsResponseInterface $value): DeleteVariantsResponse
+ private function deleteProductVariantsToProto(DeleteVariantsResponseInterface $value): DeleteVariantsResponse
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\DeleteVariantsResponse
// to \Magento\CatalogStorefrontApi\Proto\DeleteVariantsResponse
@@ -206,19 +206,19 @@ private function DeleteProductVariantsToProto(DeleteVariantsResponseInterface $v
}
/**
- * Autogenerated description for GetProductVariants method
+ * Autogenerated description for getProductVariants method
*
* @param \Spiral\GRPC\ContextInterface $ctx
* @param ProductVariantRequest $in
* @return ProductVariantResponse
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function GetProductVariants(\Spiral\GRPC\ContextInterface $ctx, ProductVariantRequest $in): ProductVariantResponse
+ public function getProductVariants(\Spiral\GRPC\ContextInterface $ctx, ProductVariantRequest $in): ProductVariantResponse
{
try {
- $magentoDtoRequest = $this->GetProductVariantsFromProto($in);
- $magentoDtoResponse = $this->service->GetProductVariants($magentoDtoRequest);
- return $this->GetProductVariantsToProto($magentoDtoResponse);
+ $magentoDtoRequest = $this->getProductVariantsFromProto($in);
+ $magentoDtoResponse = $this->service->getProductVariants($magentoDtoRequest);
+ return $this->getProductVariantsToProto($magentoDtoResponse);
} catch (\Exception $e) {
throw new \Spiral\GRPC\Exception\InvokeException(
$e->getMessage(),
@@ -230,12 +230,12 @@ public function GetProductVariants(\Spiral\GRPC\ContextInterface $ctx, ProductVa
}
/**
- * Autogenerated description for GetProductVariants method
+ * Autogenerated description for getProductVariants method
*
* @param ProductVariantRequest $value
* @return ProductVariantRequestInterface
*/
- private function GetProductVariantsFromProto(ProductVariantRequest $value): ProductVariantRequestInterface
+ private function getProductVariantsFromProto(ProductVariantRequest $value): ProductVariantRequestInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\ProductVariantRequest
// to \Magento\CatalogStorefrontApi\Api\Data\ProductVariantRequest
@@ -252,13 +252,13 @@ private function GetProductVariantsFromProto(ProductVariantRequest $value): Prod
}
/**
- * Autogenerated description for GetProductVariants method
+ * Autogenerated description for getProductVariants method
*
* @param ProductVariantResponseInterface $value
* @return ProductVariantResponse
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function GetProductVariantsToProto(ProductVariantResponseInterface $value): ProductVariantResponse
+ private function getProductVariantsToProto(ProductVariantResponseInterface $value): ProductVariantResponse
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse
// to \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse
@@ -293,19 +293,19 @@ private function GetProductVariantsToProto(ProductVariantResponseInterface $valu
}
/**
- * Autogenerated description for GetVariantsMatch method
+ * Autogenerated description for getVariantsMatch method
*
* @param \Spiral\GRPC\ContextInterface $ctx
* @param OptionSelectionRequest $in
* @return ProductVariantResponse
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function GetVariantsMatch(\Spiral\GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse
+ public function getVariantsMatch(\Spiral\GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse
{
try {
- $magentoDtoRequest = $this->GetVariantsMatchFromProto($in);
- $magentoDtoResponse = $this->service->GetVariantsMatch($magentoDtoRequest);
- return $this->GetVariantsMatchToProto($magentoDtoResponse);
+ $magentoDtoRequest = $this->getVariantsMatchFromProto($in);
+ $magentoDtoResponse = $this->service->getVariantsMatch($magentoDtoRequest);
+ return $this->getVariantsMatchToProto($magentoDtoResponse);
} catch (\Exception $e) {
throw new \Spiral\GRPC\Exception\InvokeException(
$e->getMessage(),
@@ -317,12 +317,12 @@ public function GetVariantsMatch(\Spiral\GRPC\ContextInterface $ctx, OptionSelec
}
/**
- * Autogenerated description for GetVariantsMatch method
+ * Autogenerated description for getVariantsMatch method
*
* @param OptionSelectionRequest $value
* @return OptionSelectionRequestInterface
*/
- private function GetVariantsMatchFromProto(OptionSelectionRequest $value): OptionSelectionRequestInterface
+ private function getVariantsMatchFromProto(OptionSelectionRequest $value): OptionSelectionRequestInterface
{
// convert data from \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest
// to \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest
@@ -343,13 +343,13 @@ private function GetVariantsMatchFromProto(OptionSelectionRequest $value): Optio
}
/**
- * Autogenerated description for GetVariantsMatch method
+ * Autogenerated description for getVariantsMatch method
*
* @param ProductVariantResponseInterface $value
* @return ProductVariantResponse
* phpcs:disable Generic.Metrics.NestingLevel.TooHigh
*/
- private function GetVariantsMatchToProto(ProductVariantResponseInterface $value): ProductVariantResponse
+ private function getVariantsMatchToProto(ProductVariantResponseInterface $value): ProductVariantResponse
{
// convert data from \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse
// to \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse
diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php
index f40d1cf16..e3cf0d378 100644
--- a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php
+++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php
@@ -24,38 +24,38 @@
interface VariantServiceServerInterface
{
/**
- * Autogenerated description for ImportProductVariants interface method
+ * Autogenerated description for importProductVariants interface method
*
* @param ImportVariantsRequestInterface $request
* @return ImportVariantsResponseInterface
* @throws \Throwable
*/
- public function ImportProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface;
+ public function importProductVariants(ImportVariantsRequestInterface $request): ImportVariantsResponseInterface;
/**
- * Autogenerated description for DeleteProductVariants interface method
+ * Autogenerated description for deleteProductVariants interface method
*
* @param DeleteVariantsRequestInterface $request
* @return DeleteVariantsResponseInterface
* @throws \Throwable
*/
- public function DeleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface;
+ public function deleteProductVariants(DeleteVariantsRequestInterface $request): DeleteVariantsResponseInterface;
/**
- * Autogenerated description for GetProductVariants interface method
+ * Autogenerated description for getProductVariants interface method
*
* @param ProductVariantRequestInterface $request
* @return ProductVariantResponseInterface
* @throws \Throwable
*/
- public function GetProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface;
+ public function getProductVariants(ProductVariantRequestInterface $request): ProductVariantResponseInterface;
/**
- * Autogenerated description for GetVariantsMatch interface method
+ * Autogenerated description for getVariantsMatch interface method
*
* @param OptionSelectionRequestInterface $request
* @return ProductVariantResponseInterface
* @throws \Throwable
*/
- public function GetVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface;
+ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface;
}
diff --git a/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php b/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php
index 90c78c896..38ecb95e0 100644
--- a/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php
+++ b/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php
@@ -270,22 +270,22 @@ public static function initOnce()
"6e744170692e70726f746f2e43617465676f726965734765745265717565" .
"73741a392e6d6167656e746f2e636174616c6f6753746f726566726f6e74" .
"4170692e70726f746f2e43617465676f72696573476574526573706f6e73" .
- "65220032d5040a0e56617269616e74536572766963651290010a15496d70" .
+ "65220032d5040a0e56617269616e74536572766963651290010a15696d70" .
"6f727450726f6475637456617269616e747312392e6d6167656e746f2e63" .
"6174616c6f6753746f726566726f6e744170692e70726f746f2e496d706f" .
"727456617269616e7473526571756573741a3a2e6d6167656e746f2e6361" .
"74616c6f6753746f726566726f6e744170692e70726f746f2e496d706f72" .
- "7456617269616e7473526573706f6e736522001290010a1544656c657465" .
+ "7456617269616e7473526573706f6e736522001290010a1564656c657465" .
"50726f6475637456617269616e747312392e6d6167656e746f2e63617461" .
"6c6f6753746f726566726f6e744170692e70726f746f2e44656c65746556" .
"617269616e7473526571756573741a3a2e6d6167656e746f2e636174616c" .
"6f6753746f726566726f6e744170692e70726f746f2e44656c6574655661" .
- "7269616e7473526573706f6e73652200128d010a1247657450726f647563" .
+ "7269616e7473526573706f6e73652200128d010a1267657450726f647563" .
"7456617269616e747312392e6d6167656e746f2e636174616c6f6753746f" .
"726566726f6e744170692e70726f746f2e50726f6475637456617269616e" .
"74526571756573741a3a2e6d6167656e746f2e636174616c6f6753746f72" .
"6566726f6e744170692e70726f746f2e50726f6475637456617269616e74" .
- "526573706f6e73652200128c010a1047657456617269616e74734d617463" .
+ "526573706f6e73652200128c010a1067657456617269616e74734d617463" .
"68123a2e6d6167656e746f2e636174616c6f6753746f726566726f6e7441" .
"70692e70726f746f2e4f7074696f6e53656c656374696f6e526571756573" .
"741a3a2e6d6167656e746f2e636174616c6f6753746f726566726f6e7441" .
diff --git a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php
index ca9a76934..5d9d3e6ab 100644
--- a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php
+++ b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php
@@ -28,14 +28,14 @@ public function __construct($hostname, $opts, $channel = null)
* @param array $options call options
* @return \Magento\CatalogStorefrontApi\Proto\ImportVariantsResponse
*/
- public function ImportProductVariants(
+ public function importProductVariants(
\Magento\CatalogStorefrontApi\Proto\ImportVariantsRequest $argument,
$metadata = [],
$options = []
)
{
return $this->_simpleRequest(
- '/magento.catalogStorefrontApi.proto.VariantService/ImportProductVariants',
+ '/magento.catalogStorefrontApi.proto.VariantService/importProductVariants',
$argument,
['\Magento\CatalogStorefrontApi\Proto\ImportVariantsResponse', 'decode'],
$metadata,
@@ -49,14 +49,14 @@ public function ImportProductVariants(
* @param array $options call options
* @return \Magento\CatalogStorefrontApi\Proto\DeleteVariantsResponse
*/
- public function DeleteProductVariants(
+ public function deleteProductVariants(
\Magento\CatalogStorefrontApi\Proto\DeleteVariantsRequest $argument,
$metadata = [],
$options = []
)
{
return $this->_simpleRequest(
- '/magento.catalogStorefrontApi.proto.VariantService/DeleteProductVariants',
+ '/magento.catalogStorefrontApi.proto.VariantService/deleteProductVariants',
$argument,
['\Magento\CatalogStorefrontApi\Proto\DeleteVariantsResponse', 'decode'],
$metadata,
@@ -71,14 +71,14 @@ public function DeleteProductVariants(
* @param array $options call options
* @return \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse
*/
- public function GetProductVariants(
+ public function getProductVariants(
\Magento\CatalogStorefrontApi\Proto\ProductVariantRequest $argument,
$metadata = [],
$options = []
)
{
return $this->_simpleRequest(
- '/magento.catalogStorefrontApi.proto.VariantService/GetProductVariants',
+ '/magento.catalogStorefrontApi.proto.VariantService/getProductVariants',
$argument,
['\Magento\CatalogStorefrontApi\Proto\ProductVariantResponse', 'decode'],
$metadata,
@@ -93,14 +93,14 @@ public function GetProductVariants(
* @param array $options call options
* @return \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse
*/
- public function GetVariantsMatch(
+ public function getVariantsMatch(
\Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest $argument,
$metadata = [],
$options = []
)
{
return $this->_simpleRequest(
- '/magento.catalogStorefrontApi.proto.VariantService/GetVariantsMatch',
+ '/magento.catalogStorefrontApi.proto.VariantService/getVariantsMatch',
$argument,
['\Magento\CatalogStorefrontApi\Proto\ProductVariantResponse', 'decode'],
$metadata,
diff --git a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php
index b26932ba1..d149f308d 100644
--- a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php
+++ b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php
@@ -18,7 +18,7 @@ interface VariantServiceInterface extends GRPC\ServiceInterface
*
* @throws GRPC\Exception\InvokeException
*/
- public function ImportProductVariants(GRPC\ContextInterface $ctx, ImportVariantsRequest $in): ImportVariantsResponse;
+ public function importProductVariants(GRPC\ContextInterface $ctx, ImportVariantsRequest $in): ImportVariantsResponse;
/**
* @param GRPC\ContextInterface $ctx
@@ -27,7 +27,7 @@ public function ImportProductVariants(GRPC\ContextInterface $ctx, ImportVariants
*
* @throws GRPC\Exception\InvokeException
*/
- public function DeleteProductVariants(GRPC\ContextInterface $ctx, DeleteVariantsRequest $in): DeleteVariantsResponse;
+ public function deleteProductVariants(GRPC\ContextInterface $ctx, DeleteVariantsRequest $in): DeleteVariantsResponse;
/**
* @param GRPC\ContextInterface $ctx
@@ -36,7 +36,7 @@ public function DeleteProductVariants(GRPC\ContextInterface $ctx, DeleteVariants
*
* @throws GRPC\Exception\InvokeException
*/
- public function GetProductVariants(GRPC\ContextInterface $ctx, ProductVariantRequest $in): ProductVariantResponse;
+ public function getProductVariants(GRPC\ContextInterface $ctx, ProductVariantRequest $in): ProductVariantResponse;
/**
* @param GRPC\ContextInterface $ctx
@@ -45,5 +45,5 @@ public function GetProductVariants(GRPC\ContextInterface $ctx, ProductVariantReq
*
* @throws GRPC\Exception\InvokeException
*/
- public function GetVariantsMatch(GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse;
+ public function getVariantsMatch(GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse;
}
diff --git a/app/code/Magento/Grpc/Console/Command/ProtoMarshalCommand.php b/app/code/Magento/Grpc/Console/Command/ProtoMarshalCommand.php
index d46a98b13..522c53095 100644
--- a/app/code/Magento/Grpc/Console/Command/ProtoMarshalCommand.php
+++ b/app/code/Magento/Grpc/Console/Command/ProtoMarshalCommand.php
@@ -67,7 +67,8 @@ public function __construct(
* TODO: probably we need to have a dedicated extension point in setup:di:compile for code generation
*
* Currently is not used, use cli command
- * `bin/magento storefront:grpc:init \\Magento\\CatalogStorefrontApi\\Api\\CatalogProxyServer` instead
+ * `bin/magento storefront:grpc:init \\Magento\\CatalogStorefrontApi\\Api\\CatalogProxyServer
+ * \\Magento\\CatalogStorefrontApi\\Api\\VariantServiceProxyServer` instead
*/
// if (\Magento\Setup\Console\Command\DiCompileCommand::NAME == $input->getFirstArgument()) {
diff --git a/app/code/Magento/Grpc/README.md b/app/code/Magento/Grpc/README.md
index 2000151f4..4d7b78f8c 100644
--- a/app/code/Magento/Grpc/README.md
+++ b/app/code/Magento/Grpc/README.md
@@ -3,7 +3,7 @@
This module provides gRPC entry point for Magento. Additionally, it provides possibility to expose gRPC APIs for other Magento modules
# Installation
-* Run `bin/magento storefront:grpc:init \\Magento\\CatalogStorefrontApi\\Api\\CatalogProxyServer` CLI command. The command does the following actions:
+* Run `bin/magento storefront:grpc:init \\Magento\\CatalogStorefrontApi\\Api\\CatalogProxyServer \\Magento\\CatalogStorefrontApi\\Api\\VariantServiceProxyServer` CLI command. The command does the following actions:
* Copies certain bin files to `vendor/bin` directory
* Register services for later use in gRPC sever
* Install `rr-grpc` server - https://github.com/spiral/php-grpc. Put the binary in some directory registered in PATH system variable
diff --git a/dev/docs/designDocuments/Configurable-products-option-variants.md b/dev/docs/designDocuments/Configurable-products-option-variants.md
index 9bad33ac6..2b83fd7f4 100644
--- a/dev/docs/designDocuments/Configurable-products-option-variants.md
+++ b/dev/docs/designDocuments/Configurable-products-option-variants.md
@@ -112,7 +112,7 @@ so exported `ProductVariants` will look like this:
## Storefront Application part
-Import API (`ImportProductVariants`) should split `option_values` and save them as separate records into SF App storage.
+Import API (`importProductVariants`) should split `option_values` and save them as separate records into SF App storage.
Parent id can be parsed from `option_values` too
| product_id | id | feed_data |
@@ -151,18 +151,18 @@ message ProductVariantResponse {
}
service VariantSearchService {
// get all variants that belong to a product
- rpc GetProductVariants (ProductVariantRequest) returns (ProductVariantResponse);
+ rpc getProductVariants (ProductVariantRequest) returns (ProductVariantResponse);
// match the variants which correspond, and do not contradict, the merchant selection (%like operation)
- rpc GetVariantsMatch (OptionSelectionRequest) returns (ProductVariantResponse);
+ rpc getVariantsMatch (OptionSelectionRequest) returns (ProductVariantResponse);
// match the variants which exactly matched with merchant selection (&& operation)
- rpc GetVariantsExactlyMatch (OptionSelectionRequest) returns (ProductVariantResponse);
+ rpc getVariantsExactlyMatch (OptionSelectionRequest) returns (ProductVariantResponse);
// get all variants which contain at least one of merchant selection (|| operation)
- rpc GetVariantsInclude (OptionSelectionRequest) returns (ProductVariantResponse);
+ rpc getVariantsInclude (OptionSelectionRequest) returns (ProductVariantResponse);
}
```
### Get all variants for a configurable product with id `42`
-`rpc GetProductVariants (ProductVariantRequest) returns (ProductVariantResponse)`
+`rpc getProductVariants (ProductVariantRequest) returns (ProductVariantResponse)`
1. internal Request to get variants data for by parent id:
`getVariants(parent_id, ...)`
diff --git a/dev/docs/designDocuments/Product-Variants-services.md b/dev/docs/designDocuments/Product-Variants-services.md
index f8adbdfb1..98d1d3a59 100644
--- a/dev/docs/designDocuments/Product-Variants-services.md
+++ b/dev/docs/designDocuments/Product-Variants-services.md
@@ -35,7 +35,7 @@ message ProductVariantResponse {
}
```
------------------------------------------------------
-rpc GetVariantsExactlyMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
+rpc getVariantsExactlyMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
REQUEST #1.1:
```
@@ -76,12 +76,12 @@ Response #1.2:
[
]
```
-`GetVariantsExactlyMatch` should return only fully matched records.
+`getVariantsExactlyMatch` should return only fully matched records.
`42:size/Y29uZmlndXJhYmxlLzpzaXplLWlkOi86eGwtaWQ6` option value is present in `configurable/42/1` and `configurable/42/2` variants,
but matched variants partially, because each variant has 2 option values, requested values should match both records as into Request 1.1
------------------------------------------------------
-rpc GetVariantsInclude (OptionSelectionRequest) returns (ProductVariantResponse) {}
+rpc getVariantsInclude (OptionSelectionRequest) returns (ProductVariantResponse) {}
REQUEST #2.1:
```
@@ -142,7 +142,7 @@ Response #2.2 (2 variants matches with 3 option_values):
```
------------------------------------------------------
-rpc GetVariantsMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
+rpc getVariantsMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
REQUEST #3.1:
```
diff --git a/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/CatergoriesTest.php b/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/CatergoriesTest.php
index 1ba5853ce..c9c69d23e 100644
--- a/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/CatergoriesTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/CatergoriesTest.php
@@ -104,7 +104,7 @@ public function testSaveAndDeleteCategory() : void
self::assertEquals(self::CATEGORY_ID, $category->getId());
$entitiesData = [
[
- 'entity_id' => (int) $category->getId(),
+ 'entity_id' => (string) $category->getId(),
]
];
$message = $this->messageBuilder->build(
diff --git a/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariantsTest.php b/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariantsTest.php
new file mode 100644
index 000000000..c206216bd
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/ProductVariantsTest.php
@@ -0,0 +1,216 @@
+productVariantsConsumer = Bootstrap::getObjectManager()->create(ProductVariantsConsumer::class);
+ $this->variantService = Bootstrap::getObjectManager()->create(VariantService::class);
+ $this->variantsGetRequestInterface = Bootstrap::getObjectManager()->create(
+ ProductVariantRequestInterface::class
+ );
+ $this->messageBuilder = Bootstrap::getObjectManager()->create(ChangedEntitiesMessageBuilder::class);
+ $this->productVariantFeed = Bootstrap::getObjectManager()->get(FeedPool::class)->getFeed('variants');
+ $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class);
+ $this->responseArrayMapper = Bootstrap::getObjectManager()->create(ProductVariantResponseArrayMapper::class);
+ }
+
+ /**
+ * Validate save and delete product variant operations
+ *
+ * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php
+ * @magentoDbIsolation disabled
+ * @throws NoSuchEntityException
+ * @throws StateException
+ * @throws \Throwable
+ */
+ public function testSaveAndDeleteProductVariant(): void
+ {
+ $configurable = $this->getProduct(self::CONFIGURABLE_SKU);
+ $configurableId = $configurable->getId();
+
+ $entitiesData = [];
+ foreach (self::SIMPLE_SKUS as $simpleSku) {
+ $simpleId = $this->getProduct($simpleSku)->getId();
+ $entitiesData[]['entity_id'] = \sprintf('configurable/%1$s/%2$s', $configurableId, $simpleId);
+ }
+
+ $productVariantFeed = $this->productVariantFeed->getFeedByProductIds([$configurableId]);
+ $this->assertNotEmpty($productVariantFeed['feed']);
+ $this->assertCount(9, $productVariantFeed['feed']);
+ $expectedData = $this->formatFeedData($productVariantFeed['feed']);
+
+ $updateMessage = $this->messageBuilder->build(
+ 'product_variants_updated',
+ $entitiesData
+ );
+ $this->productVariantsConsumer->processMessage($updateMessage);
+
+ $this->variantsGetRequestInterface->setProductId((string)$configurableId);
+ $this->variantsGetRequestInterface->setStore(self::STORE_CODE);
+ //This sleep ensures that the elastic index has sufficient time to refresh
+ //See https://www.elastic.co/guide/en/elasticsearch/reference/6.8/docs-refresh.html#docs-refresh
+ sleep(1);
+ $response = $this->variantService->getProductVariants($this->variantsGetRequestInterface);
+ $variants = $this->responseArrayMapper->convertToArray($response);
+
+ $this->assertNotEmpty($variants);
+ $this->compare($expectedData, $variants['matched_variants']);
+
+ $this->deleteProduct($configurable->getSku());
+ $deletedFeed = $this->productVariantFeed->getDeletedByProductIds([$configurableId]);
+ $this->assertNotEmpty($deletedFeed);
+
+ $deleteMessage = $this->messageBuilder->build(
+ 'product_variants_deleted',
+ $entitiesData
+ );
+ $this->productVariantsConsumer->processMessage($deleteMessage);
+ //This sleep ensures that the elastic index has sufficient time to refresh
+ //See https://www.elastic.co/guide/en/elasticsearch/reference/6.8/docs-refresh.html#docs-refresh
+ sleep(4);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage(sprintf(self::ERROR_MESSAGE, $configurableId));
+ $this->variantService->getProductVariants($this->variantsGetRequestInterface);
+ }
+
+ /**
+ * Transform variant feed data in catalog storefront format
+ *
+ * @param array $feedData
+ * @return array|void
+ */
+ private function formatFeedData(array $feedData)
+ {
+ try {
+ return \array_map(function (array $feedItem) {
+ $childId = \explode('/', $feedItem['id'])[2];
+ $feedItem['product_id'] = $childId;
+ unset($feedItem['modifiedAt'], $feedItem['deleted'], $feedItem['parent_id']);
+ return $feedItem;
+ }, $feedData);
+ } catch (\Exception $e) {
+ $this->fail('Feed data did not match the expected format');
+ }
+ }
+
+ /**
+ * Get product
+ *
+ * @param string $sku
+ * @return ProductInterface
+ * @throws NoSuchEntityException
+ */
+ private function getProduct(string $sku): ProductInterface
+ {
+ try {
+ return $this->productRepository->get($sku);
+ } catch (NoSuchEntityException $e) {
+ throw new NoSuchEntityException(__('Could not retrieve product with sku ' . $sku));
+ }
+ }
+
+ /**
+ * Delete product
+ *
+ * @param string $sku
+ * @throws NoSuchEntityException
+ * @throws StateException
+ */
+ private function deleteProduct(string $sku): void
+ {
+ try {
+ $registry = Bootstrap::getObjectManager()->get(Registry::class);
+ $registry->unregister('isSecureArea');
+ $registry->register('isSecureArea', true);
+ $this->productRepository->deleteById($sku);
+ $registry->unregister('isSecureArea');
+ $registry->register('isSecureArea', false);
+ } catch (NoSuchEntityException $e) {
+ throw new NoSuchEntityException(__('Could not delete product with sku ' . $sku));
+ }
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/ProductsTest.php b/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/ProductsTest.php
index dfbc559ae..a92bfd08e 100644
--- a/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/ProductsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/CatalogMessageBroker/Model/MessageBus/ProductsTest.php
@@ -92,10 +92,10 @@ public function testSaveAndDeleteProduct() : void
$this->assertEquals(self::TEST_SKU, $product->getSku());
$entitiesData = [
[
- 'entity_id' => (int) $product->getId(),
+ 'entity_id' => (string) $product->getId(),
]
];
- $productFeed = $this->productFeed->getFeedByIds([(int)$product->getId()], [self::STORE_CODE]);
+ $productFeed = $this->productFeed->getFeedByIds([(string)$product->getId()], [self::STORE_CODE]);
$this->assertNotEmpty($productFeed);
$updateMessage = $this->messageBuilder->build(
@@ -113,7 +113,7 @@ public function testSaveAndDeleteProduct() : void
$this->assertEquals($item->getSku(), $product->getSku());
$this->deleteProduct($product->getSku());
- $deletedFeed = $this->productFeed->getDeletedByIds([(int)$product->getId()], [self::STORE_CODE]);
+ $deletedFeed = $this->productFeed->getDeletedByIds([(string)$product->getId()], [self::STORE_CODE]);
$this->assertNotEmpty($deletedFeed);
$deleteMessage = $this->messageBuilder->build(
diff --git a/dev/tests/integration/framework/Magento/TestFramework/Workaround/ConsumerInvoker.php b/dev/tests/integration/framework/Magento/TestFramework/Workaround/ConsumerInvoker.php
index 5e6605865..da715eb9d 100644
--- a/dev/tests/integration/framework/Magento/TestFramework/Workaround/ConsumerInvoker.php
+++ b/dev/tests/integration/framework/Magento/TestFramework/Workaround/ConsumerInvoker.php
@@ -24,6 +24,7 @@ class ConsumerInvoker
*/
private const CONSUMERS = [
'catalog.product.export.consumer',
+ 'catalog.product.variants.export.consumer',
'catalog.category.export.consumer',
];
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php
new file mode 100644
index 000000000..e1bc567f2
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php
@@ -0,0 +1,126 @@
+requireDataFixture(
+ 'Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options.php'
+);
+
+/** @var ObjectManagerInterface $objectManager */
+$objectManager = Bootstrap::getObjectManager();
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager
+ ->get(ProductRepositoryInterface::class);
+/** @var AttributeRepository $attributeRepository */
+$attributeRepository = $objectManager->create(AttributeRepository::class);
+/** @var WebsiteRepositoryInterface $websiteRepository */
+$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class);
+$defaultWebsiteId = $websiteRepository->get('base')->getId();
+
+/** @var $installer CategorySetup */
+$installer = $objectManager->create(CategorySetup::class);
+$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+
+/** Get attributes */
+$colorAttribute = $attributeRepository->get('catalog_product', 'color_test');
+$sizeAttribute = $attributeRepository->get('catalog_product', 'size_test');
+$colorAttributeOptions = $colorAttribute->getOptions();
+$sizeAttributeOptions = $sizeAttribute->getOptions();
+
+//remove the first option which is empty
+array_shift($sizeAttributeOptions);
+array_shift($colorAttributeOptions);
+
+/** Create simple products */
+$associatedProductIds = [];
+$colorAttributeValues = [];
+$sizeAttributeValues = [];
+$i = 0;
+foreach ($colorAttributeOptions as $colorOption) {
+ foreach ($sizeAttributeOptions as $sizeOption) {
+ /** @var $childProduct Product */
+ $childProduct = $objectManager->create(Product::class);
+ $childProduct->setTypeId(Type::TYPE_SIMPLE)
+ ->setAttributeSetId($attributeSetId)
+ ->setWebsiteIds([$defaultWebsiteId])
+ ->setName('Simple ' . $colorOption->getLabel() . '-' . $sizeOption->getLabel())
+ ->setSku('simple_' . $i)
+ ->setPrice(45)
+ ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE)
+ ->setStatus(Status::STATUS_ENABLED)
+ ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1])
+ ->setColorTest($colorOption->getValue())
+ ->setSizeTest($sizeOption->getValue());
+
+ $childProduct = $productRepository->save($childProduct);
+
+ $colorAttributeValues[] = [
+ 'label' => 'color test ' . $i,
+ 'attribute_id' => $colorAttribute->getId(),
+ 'value_index' => $colorOption->getValue(),
+ ];
+ $sizeAttributeValues[] = [
+ 'label' => 'size test ' . $i,
+ 'attribute_id' => $sizeAttribute->getId(),
+ 'value_index' => $sizeOption->getValue(),
+ ];
+ $associatedProductIds[] = $childProduct->getId();
+ $i++;
+ }
+}
+
+/** Create configurable product */
+/** @var Factory $optionsFactory */
+$optionsFactory = $objectManager->create(Factory::class);
+$configurableAttributesData = [
+ [
+ 'attribute_id' => $colorAttribute->getId(),
+ 'code' => $colorAttribute->getAttributeCode(),
+ 'label' => $colorAttribute->getStoreLabel(),
+ 'position' => '0',
+ 'values' => $colorAttributeValues,
+ ],
+ [
+ 'attribute_id' => $sizeAttribute->getId(),
+ 'code' => $sizeAttribute->getAttributeCode(),
+ 'label' => $sizeAttribute->getStoreLabel(),
+ 'position' => '1',
+ 'values' => $sizeAttributeValues,
+ ],
+];
+
+$configurableProduct = $objectManager->create(Product::class);
+$configurableProduct->setTypeId(Configurable::TYPE_CODE)
+ ->setAttributeSetId($attributeSetId)
+ ->setWebsiteIds([$defaultWebsiteId])
+ ->setName('Configurable Product')
+ ->setSku('configurable')
+ ->setVisibility(Visibility::VISIBILITY_BOTH)
+ ->setStatus(Status::STATUS_ENABLED)
+ ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]);
+$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
+
+$configurableOptions = $optionsFactory->create($configurableAttributesData);
+$extensionConfigurableAttributes = $configurableProduct->getExtensionAttributes();
+$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions);
+$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds);
+$configurableProduct->setExtensionAttributes($extensionConfigurableAttributes);
+
+$productRepository->save($configurableProduct);
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_nine_simples_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_nine_simples_rollback.php
new file mode 100644
index 000000000..1d4f25cc0
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_nine_simples_rollback.php
@@ -0,0 +1,53 @@
+get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+//simple_' . $colorOption->getLabel() . '-' . $sizeOption->getLabel()
+$productsToDelete = [
+ 'simple_0',
+ 'simple_1',
+ 'simple_2',
+ 'simple_3',
+ 'simple_4',
+ 'simple_5',
+ 'simple_6',
+ 'simple_7',
+ 'simple_8',
+ 'configurable'
+];
+
+foreach ($productsToDelete as $sku) {
+ try {
+ $product = $productRepository->get($sku, true);
+
+ $stockStatus = $objectManager->create(\Magento\CatalogInventory\Model\Stock\Status::class);
+ $stockStatus->load($product->getEntityId(), 'product_id');
+ $stockStatus->delete();
+
+ $productRepository->delete($product);
+ } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ //Product already removed
+ }
+}
+
+Resolver::getInstance()->requireDataFixture(
+ 'Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options_rollback.php'
+);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options.php
new file mode 100644
index 000000000..e5d9e0743
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options.php
@@ -0,0 +1,105 @@
+create(AttributeRepositoryInterface::class);
+
+
+$eavConfig = $objectManager->get(Config::class);
+$colorAttribute = $eavConfig->getAttribute('catalog_product', 'color_test');
+$sizeAttribute = $eavConfig->getAttribute('catalog_product', 'size_test');
+$eavConfig->clear();
+
+
+/** @var CategorySetup $installer */
+$installer = $objectManager->create(CategorySetup::class);
+
+
+if (!$sizeAttribute->getId()) {
+ /** @var Attribute $sizeAttribute */
+ $sizeAttribute = $objectManager->create(Attribute::class);
+ $sizeAttribute->setData(
+ [
+ 'attribute_code' => 'size_test',
+ 'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
+ 'is_global' => 1,
+ 'is_user_defined' => 1,
+ 'frontend_input' => 'select',
+ 'is_unique' => 0,
+ 'is_required' => 0,
+ 'is_searchable' => 0,
+ 'is_visible_in_advanced_search' => 0,
+ 'is_comparable' => 0,
+ 'is_filterable' => 0,
+ 'is_filterable_in_search' => 0,
+ 'is_used_for_promo_rules' => 0,
+ 'is_html_allowed_on_front' => 1,
+ 'is_visible_on_front' => 0,
+ 'used_in_product_listing' => 0,
+ 'used_for_sort_by' => 0,
+ 'frontend_label' => ['Size Test'],
+ 'backend_type' => 'int',
+ 'option' => [
+ 'value' => [
+ 'small' => ['Small'],
+ 'medium' => ['Medium'],
+ 'large' => ['Large']
+ ],
+ 'order' => ['small' => 0, 'medium' => 1, 'large' => 2],
+ ]
+ ]
+ );
+ $attributeRepository->save($sizeAttribute);
+ $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $sizeAttribute->getId());
+}
+
+if (!$colorAttribute->getId()) {
+ /** @var Attribute $colorAttribute */
+ $colorAttribute = $objectManager->create(Attribute::class);
+ $colorAttribute->setData(
+ [
+ 'attribute_code' => 'color_test',
+ 'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
+ 'is_global' => 1,
+ 'is_user_defined' => 1,
+ 'frontend_input' => 'select',
+ 'is_unique' => 0,
+ 'is_required' => 0,
+ 'is_searchable' => 0,
+ 'is_visible_in_advanced_search' => 0,
+ 'is_comparable' => 0,
+ 'is_filterable' => 0,
+ 'is_filterable_in_search' => 0,
+ 'is_used_for_promo_rules' => 0,
+ 'is_html_allowed_on_front' => 1,
+ 'is_visible_on_front' => 0,
+ 'used_in_product_listing' => 0,
+ 'used_for_sort_by' => 0,
+ 'frontend_label' => ['Color Test'],
+ 'backend_type' => 'int',
+ 'option' => [
+ 'value' => [
+ 'red' => ['Red'],
+ 'blue' => ['Blue'],
+ 'green' => ['Green'],
+ ],
+ 'order' => ['red' => 0, 'blue' => 1, 'green' => 2],
+ ],
+ ]
+ );
+ $attributeRepository->save($colorAttribute);
+ $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $colorAttribute->getId());
+}
+
+$eavConfig->clear();
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options_rollback.php
new file mode 100644
index 000000000..ce2180a3b
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_two_attributes_three_options_rollback.php
@@ -0,0 +1,33 @@
+get(Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+$attributeCodes = ['color_test', 'size_test'];
+foreach ($attributeCodes as $attributeCode) {
+ try {
+ /** @var Attribute $attribute */
+ $attribute = $objectManager->create(Attribute::class);
+ $attribute->load($attributeCode, 'attribute_code');
+ if ($attribute->getId()) {
+ $attribute->delete();
+ }
+ } catch (Exception $e) {
+ // Nothing to delete
+ }
+}
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_storefront_ce.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_storefront_ce.txt
index 1d8b6c087..4643313ab 100644
--- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_storefront_ce.txt
+++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/composer_root_modules_storefront_ce.txt
@@ -9,6 +9,5 @@ magento/module-catalog-storefront
magento/module-catalog-storefront-api
magento/module-catalog-storefront-connector
magento/module-catalog-storefront-graph-ql
-magento/module-configurable-product-extractor
magento/module-storefront-graph-ql
magento/module-downloadable-product-data-exporter
diff --git a/magento.proto b/magento.proto
index 3b5a80867..76779c91f 100755
--- a/magento.proto
+++ b/magento.proto
@@ -456,17 +456,17 @@ message OptionSelectionRequest
}
service VariantService {
- rpc ImportProductVariants (ImportVariantsRequest) returns (ImportVariantsResponse) {}
- rpc DeleteProductVariants (DeleteVariantsRequest) returns (DeleteVariantsResponse) {}
+ rpc importProductVariants (ImportVariantsRequest) returns (ImportVariantsResponse) {}
+ rpc deleteProductVariants (DeleteVariantsRequest) returns (DeleteVariantsResponse) {}
// get all variants that belong to a product
- rpc GetProductVariants (ProductVariantRequest) returns (ProductVariantResponse) {}
+ rpc getProductVariants (ProductVariantRequest) returns (ProductVariantResponse) {}
// match the variants which correspond, and do not contradict, the merchant selection (%like operation)
- rpc GetVariantsMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
+ rpc getVariantsMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
// will be implemented in future
// match the variants which exactly matched with merchant selection (&& operation)
- //rpc GetVariantsExactlyMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
+ //rpc getVariantsExactlyMatch (OptionSelectionRequest) returns (ProductVariantResponse) {}
// get all variants which contain at least one of merchant selection (|| operation)
- //rpc GetVariantsInclude (OptionSelectionRequest) returns (ProductVariantResponse) {}
+ //rpc getVariantsInclude (OptionSelectionRequest) returns (ProductVariantResponse) {}
}