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) {} }