diff --git a/app/code/Magento/CatalogStorefront/DataProvider/ProductVariantsDataProvider.php b/app/code/Magento/CatalogStorefront/DataProvider/ProductVariantsDataProvider.php index 6fff424fd..b6c4c6ddf 100644 --- a/app/code/Magento/CatalogStorefront/DataProvider/ProductVariantsDataProvider.php +++ b/app/code/Magento/CatalogStorefront/DataProvider/ProductVariantsDataProvider.php @@ -53,12 +53,13 @@ public function __construct( /** * Fetch product variants data from storage * - * @param int $parentId + * @param array $parentIds * @return array + * @throws NotFoundException * @throws RuntimeException * @throws \Throwable */ - public function fetchByProductId(int $parentId): array + public function fetchByParentIds(array $parentIds): array { // TODO: Adapt to work without store code https://github.com/magento/catalog-storefront/issues/417 $storageName = $this->storageState->getCurrentDataSourceName( @@ -68,21 +69,91 @@ public function fetchByProductId(int $parentId): array $entities = $this->query->searchFilteredEntries( $storageName, ProductVariant::ENTITY_NAME, - ['parent_id' => $parentId] + ['parent_id' => $parentIds] ); } catch (NotFoundException $notFoundException) { $this->logger->error( \sprintf( 'Cannot find product variants for product id "%s"', - $parentId, + \implode(",", $parentIds), ), ['exception' => $notFoundException] ); return []; } catch (\Throwable $e) { - $this->logger->error($e); + $this->logger->error('Error while trying to fetch product variants by parent ids: ' . $e); throw $e; } return $entities->toArray(false); } + + /** + * Fetch product variants data from storage + * + * @param array $variantIds + * @return array + * @throws RuntimeException + * @throws \Throwable + */ + public function fetchByVariantIds(array $variantIds): 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, + ['id' => $variantIds] + ); + } catch (NotFoundException $notFoundException) { + $this->logger->error( + \sprintf( + 'Cannot find product variants for variant ids "%s"', + \implode(",", $variantIds), + ), + ['exception' => $notFoundException] + ); + return []; + } catch (\Throwable $e) { + $this->logger->error('Error while trying to fetch product variants by id: ' . $e); + throw $e; + } + return $entities->toArray(false); + } + + /** + * Get matching variant ids by option values + * + * If strict is true, only variants which contain all of the provided values, will be returned + * + * @param array $values + * @param bool $strict + * @return array + * @throws \Throwable + */ + public function fetchVariantIdsByOptionValues(array $values, bool $strict = true): 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] + ); + + $minDocCount = $strict ? count($values) : 1; + try { + $queryResult = $this->query->searchAggregatedFilteredEntries( + $storageName, + ProductVariant::ENTITY_NAME, + ['option_value' => $values], + 'id', + $minDocCount + ); + $variantIds = \array_column($queryResult, 'id'); + } catch (\Throwable $e) { + $this->logger->error('Error while trying to fetch product variants by option values: ' . $e); + throw $e; + } + return $variantIds; + } } diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php b/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php index 3ca6ba0a7..b5cbb154b 100644 --- a/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php +++ b/app/code/Magento/CatalogStorefront/Model/Storage/Client/ElasticsearchQuery.php @@ -104,7 +104,9 @@ public function searchMatchedEntries( array $searchBody, ?string $queryContext = 'must' ): EntryIteratorInterface { - return $this->searchEntries($indexName, $entityName, $searchBody, $queryContext, 'match'); + $searchQuery = $this->buildSearchQuery($searchBody, $queryContext, 'match'); + $searchResult = $this->searchEntries($indexName, $entityName, $searchQuery); + return $this->searchResultIteratorFactory->create($searchResult); } /** @@ -114,9 +116,36 @@ public function searchFilteredEntries( string $indexName, string $entityName, array $searchBody, - ?string $clauseType = 'term' + ?string $clauseType = 'terms' ): EntryIteratorInterface { - return $this->searchEntries($indexName, $entityName, $searchBody, 'filter', $clauseType); + $searchQuery = $this->buildSearchQuery($searchBody, 'filter', $clauseType); + $searchResult = $this->searchEntries($indexName, $entityName, $searchQuery); + return $this->searchResultIteratorFactory->create($searchResult); + } + + /** + * @inheritdoc + */ + public function searchAggregatedFilteredEntries( + string $indexName, + string $entityName, + array $searchBody, + string $aggregateField, + int $minDocCount, + ?string $clauseType = 'terms' + ): array { + $searchQuery = $this->buildSearchQuery($searchBody, 'filter', $clauseType); + $aggregationQuery = $this->buildTermsAggregationQuery($aggregateField, $minDocCount, $entityName); + $searchResult = $this->searchEntries($indexName, $entityName, $searchQuery, $aggregationQuery); + $buckets = $searchResult['aggregations'][$entityName]['buckets']; + $result = []; + foreach ($buckets as $match) { + $result[] = [ + $aggregateField => $match['key'], + 'doc_count' => $match['doc_count'] + ]; + } + return $result; } /** @@ -124,32 +153,47 @@ public function searchFilteredEntries( * * @param string $indexName * @param string $entityName - * @param array $searchBody - * @param string $queryContext - * @param string $clauseType - * @return EntryIteratorInterface - * @throws NotFoundException + * @param array $searchQuery + * @param array $aggregationQuery + * @return array * @throws RuntimeException + * @throws \OverflowException */ 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 + array $searchQuery, + array $aggregationQuery = [] + ): array { + $searchBody['query'] = $searchQuery; + $size = self::SEARCH_LIMIT; + if (!empty($aggregationQuery)) { + $searchBody['aggregations'] = $aggregationQuery; + $size = 0; + } + return $this->searchRequest($indexName, $entityName, $searchBody, $size); + } + + /** + * Perform client search request. + * + * @param string $indexName + * @param string $entityName + * @param array $searchBody + * @param int $size + * @return array + * @throws RuntimeException + * @throws \OverflowException + */ + private function searchRequest(string $indexName, string $entityName, array $searchBody, int $size): array + { $query = [ 'index' => $indexName, 'type' => $entityName, - 'body' => [], - 'size' => self::SEARCH_LIMIT + 'body' => $searchBody, + 'size' => $size ]; - foreach ($searchBody as $key => $value) { - $query['body']['query']['bool'][$queryContext][][$clauseType][$key] = $value; - } - try { $result = $this->connectionPull->getConnection()->search($query); } catch (\Throwable $throwable) { @@ -158,6 +202,7 @@ private function searchEntries( $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( @@ -165,9 +210,43 @@ private function searchEntries( ); } - $this->checkErrors($result, $indexName); + return $result; + } - return $this->searchResultIteratorFactory->create($result); + /** + * Form a search query + * + * @param array $searchBody + * @param string $queryContext + * @param string $clauseType + * @return array + */ + private function buildSearchQuery(array $searchBody, string $queryContext, string $clauseType): array + { + $query = []; + foreach ($searchBody as $key => $value) { + $query['bool'][$queryContext][][$clauseType][$key] = $value; + } + return $query; + } + + /** + * Form a query for terms aggregation + * + * @param string $field + * @param int $minDocCount + * @param string $entityName + * @return array + */ + private function buildTermsAggregationQuery(string $field, int $minDocCount, string $entityName): array + { + //TODO: Add pagination and remove max size search size https://github.com/magento/catalog-storefront/issues/418 + $query[$entityName]['terms'] = [ + 'size' => self::SEARCH_LIMIT, + 'field' => $field, + 'min_doc_count' => $minDocCount + ]; + return $query; } /** diff --git a/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php b/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php index e0f0ede6b..5753eb50e 100644 --- a/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php +++ b/app/code/Magento/CatalogStorefront/Model/Storage/Client/QueryInterface.php @@ -71,6 +71,7 @@ public function getEntries( * @return EntryIteratorInterface * @throws NotFoundException * @throws RuntimeException + * @throws \OverflowException */ public function searchMatchedEntries( string $indexName, @@ -92,11 +93,39 @@ public function searchMatchedEntries( * @return EntryIteratorInterface * @throws NotFoundException * @throws RuntimeException + * @throws \OverflowException */ public function searchFilteredEntries( string $indexName, string $entityName, array $searchBody, - ?string $clauseType = 'term' + ?string $clauseType = 'terms' ): EntryIteratorInterface; + + /** + * Search entries by specified search arguments, then aggregate the results by a specific field + * + * $searchBody contains "search field" -> "search value". + * "search field" must be indexed in order for this query to work. + * $aggregateField contains a field which will be used for the aggregate query, + * $minDocCount is the minimum match requirement for the aggregate + * + * @param string $indexName + * @param string $entityName + * @param array $searchBody + * @param string $aggregateField + * @param int $minDocCount + * @param string|null $clauseType + * @return array + * @throws RuntimeException + * @throws \OverflowException + */ + public function searchAggregatedFilteredEntries( + string $indexName, + string $entityName, + array $searchBody, + string $aggregateField, + int $minDocCount, + ?string $clauseType = 'terms' + ): array; } diff --git a/app/code/Magento/CatalogStorefront/Model/VariantService.php b/app/code/Magento/CatalogStorefront/Model/VariantService.php index 21436a24e..413a84f9d 100644 --- a/app/code/Magento/CatalogStorefront/Model/VariantService.php +++ b/app/code/Magento/CatalogStorefront/Model/VariantService.php @@ -216,10 +216,10 @@ public function getProductVariants(ProductVariantRequestInterface $request): Pro { $productId = $request->getProductId(); $store = $request->getStore(); - $rawVariants = $this->productVariantsDataProvider->fetchByProductId((int)$productId); + $variantData = $this->productVariantsDataProvider->fetchByParentIds([(int)$productId]); - \ksort($rawVariants); - if (empty($rawVariants)) { + \ksort($variantData); + if (empty($variantData)) { throw new \InvalidArgumentException( sprintf( 'No products variants for product with id %s are found in catalog.', @@ -228,34 +228,10 @@ public function getProductVariants(ProductVariantRequestInterface $request): Pro ); } - $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(); - } - } + $variants = $this->formatVariants($variantData); + $validVariants = $this->validateVariants($variants, $store); - if (empty($variants)) { + if (empty($validVariants)) { throw new \InvalidArgumentException( sprintf( 'No valid products variants for product with id %s are found in catalog.', @@ -264,20 +240,163 @@ public function getProductVariants(ProductVariantRequestInterface $request): Pro ); } + $output = []; + foreach ($validVariants as $variant) { + $output[] = $this->productVariantMapper->setData($variant)->build(); + } + $response = new ProductVariantResponse(); - $response->setMatchedVariants($variants); + $response->setMatchedVariants($output); return $response; } /** - * TODO: Implement getVariantsMatch() method and remove the warning suppression. + * Match the variants which correspond, and do not contradict, the merchant selection. * * @param OptionSelectionRequestInterface $request * @return ProductVariantResponseInterface + * @throws RuntimeException + * @throws \Throwable * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface { - return new ProductVariantResponse(); + $values = $request->getValues(); + $store = $request->getStore(); + $variantIds = $this->productVariantsDataProvider->fetchVariantIdsByOptionValues($values); + $variantData = empty($variantIds) ? [] : $this->productVariantsDataProvider->fetchByVariantIds($variantIds); + + $validVariants = []; + if (!empty($variantData)) { + \ksort($variantData); + $variants = $this->formatVariants($variantData); + $validVariants = $this->validateVariants($variants, $store); + } + + $output = []; + foreach ($validVariants as $variant) { + $output[] = $this->productVariantMapper->setData($variant)->build(); + } + + $response = new ProductVariantResponse(); + $response->setMatchedVariants($output); + return $response; + } + + /** + * Match full variant which matches the merchant selection exactly + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws RuntimeException + * @throws \Throwable + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getVariantsExactlyMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface + { + $values = $request->getValues(); + $store = $request->getStore(); + $variantIds = $this->productVariantsDataProvider->fetchVariantIdsByOptionValues($values); + $variantData = count($variantIds) !== 1 ? + [] : + $this->productVariantsDataProvider->fetchByVariantIds($variantIds); + + $validVariants = []; + if (!empty($variantData) && count($variantData) === count($values)) { + \ksort($variantData); + $variants = $this->formatVariants($variantData); + $validVariants = $this->validateVariants($variants, $store); + } + + $output = []; + foreach ($validVariants as $variant) { + $output[] = $this->productVariantMapper->setData($variant)->build(); + } + + $response = new ProductVariantResponse(); + $response->setMatchedVariants($output); + return $response; + } + + /** + * Get all variants which contain at least one of merchant selections + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws RuntimeException + * @throws \Throwable + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getVariantsInclude(OptionSelectionRequestInterface $request): ProductVariantResponseInterface + { + $values = $request->getValues(); + $store = $request->getStore(); + $variantIds = $this->productVariantsDataProvider->fetchVariantIdsByOptionValues($values, false); + $variantData = empty($variantIds) ? [] : $this->productVariantsDataProvider->fetchByVariantIds($variantIds); + + $validVariants = []; + if (!empty($variantData)) { + \ksort($variantData); + $variants = $this->formatVariants($variantData); + $validVariants = $this->validateVariants($variants, $store); + } + + $output = []; + foreach ($validVariants as $variant) { + $output[] = $this->productVariantMapper->setData($variant)->build(); + } + + $response = new ProductVariantResponse(); + $response->setMatchedVariants($output); + return $response; + } + + /** + * Combine option values into product variants + * + * @param array $optionValueData + * @return array + */ + private function formatVariants(array $optionValueData): array + { + $variants = []; + foreach ($optionValueData as $optionValue) { + $variants[$optionValue['id']]['id'] = $optionValue['id']; + $variants[$optionValue['id']]['option_values'][] = $optionValue['option_value']; + $variants[$optionValue['id']]['product_id'] = $optionValue['product_id']; + } + return $variants; + } + + /** + * Validate that the variant products exist and are enabled. Unset invalid variants. + * + * @param array $variants + * @param string $store + * @return array + * @throws \Throwable + */ + private function validateVariants(array $variants, string $store): array + { + $productsGetRequest = $this->productsGetRequestInterfaceFactory->create(); + $productsGetRequest->setIds(\array_column($variants, 'product_id')); + $productsGetRequest->setStore($store); + $productsGetRequest->setAttributeCodes(["id", "status"]); + $catalogProducts = $this->catalogService->getProducts($productsGetRequest)->getItems(); + + $activeProductIds = []; + foreach ($catalogProducts as $product) { + if ($product->getStatus() === self::PRODUCT_STATUS_ENABLED) { + $activeProductIds[$product->getId()] = $product->getId(); + } + } + + foreach ($variants as $key => $compositeVariant) { + if (!isset($activeProductIds[$compositeVariant['product_id']])) { + unset($variants[$key]); + } + } + + return $variants; } } diff --git a/app/code/Magento/CatalogStorefront/Test/Api/ProductVariants/ConfigurableVariantsTest.php b/app/code/Magento/CatalogStorefront/Test/Api/ProductVariants/ConfigurableVariantsTest.php index 01ce45853..ce665fec5 100644 --- a/app/code/Magento/CatalogStorefront/Test/Api/ProductVariants/ConfigurableVariantsTest.php +++ b/app/code/Magento/CatalogStorefront/Test/Api/ProductVariants/ConfigurableVariantsTest.php @@ -11,6 +11,7 @@ use Magento\Catalog\Model\Product; use Magento\CatalogStorefront\Model\VariantService; use Magento\CatalogStorefront\Test\Api\StorefrontTestsAbstract; +use Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequestInterface; use Magento\CatalogStorefrontApi\Api\Data\ProductVariantRequestInterface; use Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse; use Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponseArrayMapper; @@ -22,6 +23,21 @@ */ class ConfigurableVariantsTest extends StorefrontTestsAbstract { + /** + * Simple product skus from Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php + */ + private const SIMPLE_SKUS = [ + 'simple_0', + 'simple_1', + 'simple_2', + 'simple_3', + 'simple_4', + 'simple_5', + 'simple_6', + 'simple_7', + 'simple_8' + ]; + /** * @var VariantService */ @@ -42,6 +58,11 @@ class ConfigurableVariantsTest extends StorefrontTestsAbstract */ private $responseArrayMapper; + /** + * @var OptionSelectionRequestInterface + */ + private $optionSelectionRequestInterface; + /** * @inheritdoc */ @@ -50,6 +71,9 @@ protected function setUp(): void parent::setUp(); $this->variantService = Bootstrap::getObjectManager()->create(VariantService::class); $this->variantsRequestInterface = Bootstrap::getObjectManager()->create(ProductVariantRequestInterface::class); + $this->optionSelectionRequestInterface = Bootstrap::getObjectManager()->create( + OptionSelectionRequestInterface::class + ); $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); $this->responseArrayMapper = Bootstrap::getObjectManager()->create( ProductVariantResponseArrayMapper::class @@ -66,30 +90,18 @@ protected function setUp(): void */ public function testConfigurableProductVariants(): void { - $simpleSkus = [ - 'simple_0', - 'simple_1', - 'simple_2', - 'simple_3', - 'simple_4', - 'simple_5', - 'simple_6', - 'simple_7', - 'simple_8' - ]; + //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 $configurable Product */ $configurable = $this->productRepository->get('configurable'); $simples = []; - foreach ($simpleSkus as $sku) { + foreach (self::SIMPLE_SKUS 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']; @@ -109,19 +121,147 @@ public function testConfigurableProductVariants(): void */ public function testProductVariantsDisabledProduct(): void { + //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 $configurable Product */ $configurable = $this->productRepository->get('configurable'); $this->variantsRequestInterface->setProductId((string)$configurable->getId()); $this->variantsRequestInterface->setStore('default'); + /** @var $variantServiceItem ProductVariantResponse */ + $variantServiceItem = $this->variantService->getProductVariants($this->variantsRequestInterface); + $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants']; + self::assertCount(1, $actual); + } + /** + * Validate matching of variants by option values using getVariantsMatch + * + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php + * @magentoDbIsolation disabled + * @throws NoSuchEntityException + * @throws \Throwable + */ + public function testGetVariantsMatch(): void + { //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 $configurable Product */ + $configurable = $this->productRepository->get('configurable'); + $simples = []; + foreach (self::SIMPLE_SKUS as $sku) { + $simples[] = $this->productRepository->get($sku); + } + $availableVariants = $this->getExpectedProductVariants($configurable, $simples); + + // Match using one option value. Expect 3 simple products. + $optionValues = [$availableVariants[0]['option_values'][0]]; + $this->optionSelectionRequestInterface->setStore('default'); + $this->optionSelectionRequestInterface->setValues($optionValues); /** @var $variantServiceItem ProductVariantResponse */ - $variantServiceItem = $this->variantService->getProductVariants($this->variantsRequestInterface); + $variantServiceItem = $this->variantService->getVariantsMatch($this->optionSelectionRequestInterface); + $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants']; + $expected = \array_values(\array_filter($availableVariants, function ($variant) use ($optionValues) { + return \in_array($optionValues, $variant['option_values']); + })); + self::assertCount(3, $actual); + $this->compare($expected, $actual); + + // Match using two option values. Expect 1 simple product. + $optionValues = [$availableVariants[3]['option_values'][0], $availableVariants[3]['option_values'][1]]; + $this->optionSelectionRequestInterface->setValues($optionValues); + /** @var $variantServiceItem ProductVariantResponse */ + $variantServiceItem = $this->variantService->getVariantsMatch($this->optionSelectionRequestInterface); + $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants']; + $expected = [$availableVariants[3]]; + self::assertCount(1, $actual); + $this->compare($expected, $actual); + } + + /** + * Validate matching of variants by option values using getVariantsExactlyMatch + * + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php + * @magentoDbIsolation disabled + * @throws NoSuchEntityException + * @throws \Throwable + */ + public function testGetVariantsExactlyMatch(): void + { + //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 $configurable Product */ + $configurable = $this->productRepository->get('configurable'); + $simples = []; + foreach (self::SIMPLE_SKUS as $sku) { + $simples[] = $this->productRepository->get($sku); + } + $availableVariants = $this->getExpectedProductVariants($configurable, $simples); + + // Use exact match using two option values. Expect 1 simple product. + $optionValues = $availableVariants[0]['option_values']; + $this->optionSelectionRequestInterface->setStore('default'); + $this->optionSelectionRequestInterface->setValues($optionValues); + /** @var $variantServiceItem ProductVariantResponse */ + $variantServiceItem = $this->variantService->getVariantsExactlyMatch($this->optionSelectionRequestInterface); $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants']; + $expected = [$availableVariants[0]]; self::assertCount(1, $actual); + $this->compare($expected, $actual); + + // Use exact match using one option value. Expect 0 simple products. + $optionValues = [$availableVariants[0]['option_values'][0]]; + $this->optionSelectionRequestInterface->setValues($optionValues); + /** @var $variantServiceItem ProductVariantResponse */ + $variantServiceItem = $this->variantService->getVariantsExactlyMatch($this->optionSelectionRequestInterface); + $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants']; + self::assertEmpty($actual); + } + + /** + * Validate matching of variants by option values using getVariantsInclude + * + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_product_nine_simples.php + * @magentoDbIsolation disabled + * @throws NoSuchEntityException + * @throws \Throwable + */ + public function testGetVariantsInclude(): void + { + //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 $configurable Product */ + $configurable = $this->productRepository->get('configurable'); + $simples = []; + foreach (self::SIMPLE_SKUS as $sku) { + $simples[] = $this->productRepository->get($sku); + } + $availableVariants = $this->getExpectedProductVariants($configurable, $simples); + + // Use include match using two option values. Expect 6 simple products. + $optionValues = [ + $availableVariants[0]['option_values'][0], + $availableVariants[1]['option_values'][0] + ]; + $this->optionSelectionRequestInterface->setStore('default'); + $this->optionSelectionRequestInterface->setValues($optionValues); + /** @var $variantServiceItem ProductVariantResponse */ + $variantServiceItem = $this->variantService->getVariantsInclude($this->optionSelectionRequestInterface); + $actual = $this->responseArrayMapper->convertToArray($variantServiceItem)['matched_variants']; + $expected = \array_values(\array_filter($availableVariants, function ($variant) use ($optionValues) { + foreach ($optionValues as $optionValue) { + if (\in_array($optionValue, $variant['option_values'])) { + return true; + } + } + return false; + })); + self::assertCount(6, $actual); + $this->compare($expected, $actual); } /** diff --git a/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php b/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php index 7dde8846a..b03ce2866 100644 --- a/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php +++ b/app/code/Magento/CatalogStorefrontApi/Api/InMemoryVariantService.php @@ -82,4 +82,26 @@ public function getVariantsMatch(OptionSelectionRequestInterface $request): Prod { return $this->service->getVariantsMatch($request); } + + /** + * Autogenerated description for getVariantsExactlyMatch in memory client service method + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + */ + public function getVariantsExactlyMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface + { + return $this->service->getVariantsExactlyMatch($request); + } + + /** + * Autogenerated description for getVariantsInclude in memory client service method + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + */ + public function getVariantsInclude(OptionSelectionRequestInterface $request): ProductVariantResponseInterface + { + return $this->service->getVariantsInclude($request); + } } diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php index 52e1ffd0f..85d3b1013 100644 --- a/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php +++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantService.php @@ -358,4 +358,172 @@ private function getVariantsMatchFromProto(ProductVariantResponse $value): Produ return $out; } + + /** + * @inheritdoc + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws \Throwable + */ + public function getVariantsExactlyMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface + { + $protoRequest = $this->getVariantsExactlyMatchToProto($request); + [$protoResult, $status] = $this->protoClient->getVariantsExactlyMatch($protoRequest)->wait(); + if ($status->code !== 0) { + throw new \RuntimeException($status->details, $status->code); + } + return $this->getVariantsExactlyMatchFromProto($protoResult); + } + + /** + * Autogenerated description for getVariantsExactlyMatch method + * + * @param OptionSelectionRequestInterface $value + * @return OptionSelectionRequest + */ + private function getVariantsExactlyMatchToProto(OptionSelectionRequestInterface $value): OptionSelectionRequest + { + // convert data from \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest + // to \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest + /** @var \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest(); + $r->setStore($value->getStore()); + $values = []; + foreach ($value->getValues() as $newValue) { + $values[] = $newValue; + } + $r->setValues($values); + return $r; + }; + $proto = $p(); + + return $proto; + } + + /** + * Autogenerated description for getVariantsExactlyMatch method + * + * @param ProductVariantResponse $value + * @return ProductVariantResponseInterface + * phpcs:disable Generic.Metrics.NestingLevel.TooHigh + */ + private function getVariantsExactlyMatchFromProto(ProductVariantResponse $value): ProductVariantResponseInterface + { + // convert data from \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse + // to \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse + /** @var \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse(); + $res = []; + foreach ($value->getMatchedVariants() as $item1) { + // convert data from \Magento\CatalogStorefrontApi\Proto\ProductVariant + // to \Magento\CatalogStorefrontApi\Api\Data\ProductVariant + /** @var \Magento\CatalogStorefrontApi\Proto\ProductVariant $item1 **/ + $p = function () use ($item1) { + $r = new \Magento\CatalogStorefrontApi\Api\Data\ProductVariant(); + $r->setId($item1->getId()); + $values = []; + foreach ($item1->getOptionValues() as $newValue) { + $values[] = $newValue; + } + $r->setOptionValues($values); + $r->setProductId($item1->getProductId()); + return $r; + }; + $out = $p(); + $res[] = $out; + } + $r->setMatchedVariants($res); + return $r; + }; + $out = $p(); + + return $out; + } + + /** + * @inheritdoc + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws \Throwable + */ + public function getVariantsInclude(OptionSelectionRequestInterface $request): ProductVariantResponseInterface + { + $protoRequest = $this->getVariantsIncludeToProto($request); + [$protoResult, $status] = $this->protoClient->getVariantsInclude($protoRequest)->wait(); + if ($status->code !== 0) { + throw new \RuntimeException($status->details, $status->code); + } + return $this->getVariantsIncludeFromProto($protoResult); + } + + /** + * Autogenerated description for getVariantsInclude method + * + * @param OptionSelectionRequestInterface $value + * @return OptionSelectionRequest + */ + private function getVariantsIncludeToProto(OptionSelectionRequestInterface $value): OptionSelectionRequest + { + // convert data from \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest + // to \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest + /** @var \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest(); + $r->setStore($value->getStore()); + $values = []; + foreach ($value->getValues() as $newValue) { + $values[] = $newValue; + } + $r->setValues($values); + return $r; + }; + $proto = $p(); + + return $proto; + } + + /** + * Autogenerated description for getVariantsInclude method + * + * @param ProductVariantResponse $value + * @return ProductVariantResponseInterface + * phpcs:disable Generic.Metrics.NestingLevel.TooHigh + */ + private function getVariantsIncludeFromProto(ProductVariantResponse $value): ProductVariantResponseInterface + { + // convert data from \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse + // to \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse + /** @var \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse(); + $res = []; + foreach ($value->getMatchedVariants() as $item1) { + // convert data from \Magento\CatalogStorefrontApi\Proto\ProductVariant + // to \Magento\CatalogStorefrontApi\Api\Data\ProductVariant + /** @var \Magento\CatalogStorefrontApi\Proto\ProductVariant $item1 **/ + $p = function () use ($item1) { + $r = new \Magento\CatalogStorefrontApi\Api\Data\ProductVariant(); + $r->setId($item1->getId()); + $values = []; + foreach ($item1->getOptionValues() as $newValue) { + $values[] = $newValue; + } + $r->setOptionValues($values); + $r->setProductId($item1->getProductId()); + return $r; + }; + $out = $p(); + $res[] = $out; + } + $r->setMatchedVariants($res); + return $r; + }; + $out = $p(); + + return $out; + } } diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php index d53e3a07e..67e8ebecd 100644 --- a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php +++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceInterface.php @@ -58,4 +58,22 @@ public function getProductVariants(ProductVariantRequestInterface $request): Pro * @throws \Throwable */ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface; + + /** + * Autogenerated description for getVariantsExactlyMatch interface method + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws \Throwable + */ + public function getVariantsExactlyMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface; + + /** + * Autogenerated description for getVariantsInclude interface method + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws \Throwable + */ + public function getVariantsInclude(OptionSelectionRequestInterface $request): ProductVariantResponseInterface; } diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php index 2b1abe936..3ff5453bf 100644 --- a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php +++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceProxyServer.php @@ -382,4 +382,186 @@ private function getVariantsMatchToProto(ProductVariantResponseInterface $value) return $proto; } + + /** + * Autogenerated description for getVariantsExactlyMatch method + * + * @param \Spiral\GRPC\ContextInterface $ctx + * @param OptionSelectionRequest $in + * @return ProductVariantResponse + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getVariantsExactlyMatch(\Spiral\GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse + { + try { + $magentoDtoRequest = $this->getVariantsExactlyMatchFromProto($in); + $magentoDtoResponse = $this->service->getVariantsExactlyMatch($magentoDtoRequest); + return $this->getVariantsExactlyMatchToProto($magentoDtoResponse); + } catch (\Exception $e) { + throw new \Spiral\GRPC\Exception\InvokeException( + $e->getMessage(), + \Spiral\GRPC\StatusCode::UNKNOWN, + [], + $e + ); + } + } + + /** + * Autogenerated description for getVariantsExactlyMatch method + * + * @param OptionSelectionRequest $value + * @return OptionSelectionRequestInterface + */ + private function getVariantsExactlyMatchFromProto(OptionSelectionRequest $value): OptionSelectionRequestInterface + { + // convert data from \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest + // to \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest + /** @var \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest(); + $r->setStore($value->getStore()); + $values = []; + foreach ($value->getValues() as $newValue) { + $values[] = $newValue; + } + $r->setValues($values); + return $r; + }; + $out = $p(); + + return $out; + } + + /** + * Autogenerated description for getVariantsExactlyMatch method + * + * @param ProductVariantResponseInterface $value + * @return ProductVariantResponse + * phpcs:disable Generic.Metrics.NestingLevel.TooHigh + */ + private function getVariantsExactlyMatchToProto(ProductVariantResponseInterface $value): ProductVariantResponse + { + // convert data from \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse + // to \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse + /** @var \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse(); + $res = []; + foreach ($value->getMatchedVariants() as $item1) { + // convert data from \Magento\CatalogStorefrontApi\Api\Data\ProductVariant + // to \Magento\CatalogStorefrontApi\Proto\ProductVariant + /** @var \Magento\CatalogStorefrontApi\Api\Data\ProductVariant $item1 **/ + $p = function () use ($item1) { + $r = new \Magento\CatalogStorefrontApi\Proto\ProductVariant(); + $r->setId($item1->getId()); + $values = []; + foreach ($item1->getOptionValues() as $newValue) { + $values[] = $newValue; + } + $r->setOptionValues($values); + $r->setProductId($item1->getProductId()); + return $r; + }; + $proto = $p(); + $res[] = $proto; + } + $r->setMatchedVariants($res); + return $r; + }; + $proto = $p(); + + return $proto; + } + + /** + * Autogenerated description for getVariantsInclude method + * + * @param \Spiral\GRPC\ContextInterface $ctx + * @param OptionSelectionRequest $in + * @return ProductVariantResponse + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getVariantsInclude(\Spiral\GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse + { + try { + $magentoDtoRequest = $this->getVariantsIncludeFromProto($in); + $magentoDtoResponse = $this->service->getVariantsInclude($magentoDtoRequest); + return $this->getVariantsIncludeToProto($magentoDtoResponse); + } catch (\Exception $e) { + throw new \Spiral\GRPC\Exception\InvokeException( + $e->getMessage(), + \Spiral\GRPC\StatusCode::UNKNOWN, + [], + $e + ); + } + } + + /** + * Autogenerated description for getVariantsInclude method + * + * @param OptionSelectionRequest $value + * @return OptionSelectionRequestInterface + */ + private function getVariantsIncludeFromProto(OptionSelectionRequest $value): OptionSelectionRequestInterface + { + // convert data from \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest + // to \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest + /** @var \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Api\Data\OptionSelectionRequest(); + $r->setStore($value->getStore()); + $values = []; + foreach ($value->getValues() as $newValue) { + $values[] = $newValue; + } + $r->setValues($values); + return $r; + }; + $out = $p(); + + return $out; + } + + /** + * Autogenerated description for getVariantsInclude method + * + * @param ProductVariantResponseInterface $value + * @return ProductVariantResponse + * phpcs:disable Generic.Metrics.NestingLevel.TooHigh + */ + private function getVariantsIncludeToProto(ProductVariantResponseInterface $value): ProductVariantResponse + { + // convert data from \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse + // to \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse + /** @var \Magento\CatalogStorefrontApi\Api\Data\ProductVariantResponse $value **/ + $p = function () use ($value) { + $r = new \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse(); + $res = []; + foreach ($value->getMatchedVariants() as $item1) { + // convert data from \Magento\CatalogStorefrontApi\Api\Data\ProductVariant + // to \Magento\CatalogStorefrontApi\Proto\ProductVariant + /** @var \Magento\CatalogStorefrontApi\Api\Data\ProductVariant $item1 **/ + $p = function () use ($item1) { + $r = new \Magento\CatalogStorefrontApi\Proto\ProductVariant(); + $r->setId($item1->getId()); + $values = []; + foreach ($item1->getOptionValues() as $newValue) { + $values[] = $newValue; + } + $r->setOptionValues($values); + $r->setProductId($item1->getProductId()); + return $r; + }; + $proto = $p(); + $res[] = $proto; + } + $r->setMatchedVariants($res); + return $r; + }; + $proto = $p(); + + return $proto; + } } diff --git a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php index e3cf0d378..2814b6608 100644 --- a/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php +++ b/app/code/Magento/CatalogStorefrontApi/Api/VariantServiceServerInterface.php @@ -58,4 +58,22 @@ public function getProductVariants(ProductVariantRequestInterface $request): Pro * @throws \Throwable */ public function getVariantsMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface; + + /** + * Autogenerated description for getVariantsExactlyMatch interface method + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws \Throwable + */ + public function getVariantsExactlyMatch(OptionSelectionRequestInterface $request): ProductVariantResponseInterface; + + /** + * Autogenerated description for getVariantsInclude interface method + * + * @param OptionSelectionRequestInterface $request + * @return ProductVariantResponseInterface + * @throws \Throwable + */ + public function getVariantsInclude(OptionSelectionRequestInterface $request): ProductVariantResponseInterface; } diff --git a/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php b/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php index 38ecb95e0..ec8ba9f0d 100644 --- a/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php +++ b/app/code/Magento/CatalogStorefrontApi/Metadata/Catalog.php @@ -16,7 +16,7 @@ public static function initOnce() return; } $pool->internalAddGeneratedFile(hex2bin( - "0ace400a0d636174616c6f672e70726f746f12226d6167656e746f2e6361" . + "0af5420a0d636174616c6f672e70726f746f12226d6167656e746f2e6361" . "74616c6f6753746f726566726f6e744170692e70726f746f22be0b0a0750" . "726f64756374120a0a02696418012001280912180a106174747269627574" . "655f7365745f696418022001280912130a0b6861735f6f7074696f6e7318" . @@ -270,7 +270,7 @@ public static function initOnce() "6e744170692e70726f746f2e43617465676f726965734765745265717565" . "73741a392e6d6167656e746f2e636174616c6f6753746f726566726f6e74" . "4170692e70726f746f2e43617465676f72696573476574526573706f6e73" . - "65220032d5040a0e56617269616e74536572766963651290010a15696d70" . + "65220032fc060a0e56617269616e74536572766963651290010a15696d70" . "6f727450726f6475637456617269616e747312392e6d6167656e746f2e63" . "6174616c6f6753746f726566726f6e744170692e70726f746f2e496d706f" . "727456617269616e7473526571756573741a3a2e6d6167656e746f2e6361" . @@ -290,8 +290,18 @@ public static function initOnce() "70692e70726f746f2e4f7074696f6e53656c656374696f6e526571756573" . "741a3a2e6d6167656e746f2e636174616c6f6753746f726566726f6e7441" . "70692e70726f746f2e50726f6475637456617269616e74526573706f6e73" . - "6522004228e202254d6167656e746f5c436174616c6f6753746f72656672" . - "6f6e744170695c4d65746164617461620670726f746f33" + "6522001293010a1767657456617269616e747345786163746c794d617463" . + "68123a2e6d6167656e746f2e636174616c6f6753746f726566726f6e7441" . + "70692e70726f746f2e4f7074696f6e53656c656374696f6e526571756573" . + "741a3a2e6d6167656e746f2e636174616c6f6753746f726566726f6e7441" . + "70692e70726f746f2e50726f6475637456617269616e74526573706f6e73" . + "652200128e010a1267657456617269616e7473496e636c756465123a2e6d" . + "6167656e746f2e636174616c6f6753746f726566726f6e744170692e7072" . + "6f746f2e4f7074696f6e53656c656374696f6e526571756573741a3a2e6d" . + "6167656e746f2e636174616c6f6753746f726566726f6e744170692e7072" . + "6f746f2e50726f6475637456617269616e74526573706f6e736522004228" . + "e202254d6167656e746f5c436174616c6f6753746f726566726f6e744170" . + "695c4d65746164617461620670726f746f33" ), true); static::$is_initialized = true; diff --git a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php index 5d9d3e6ab..699cae70d 100644 --- a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php +++ b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceClient.php @@ -107,4 +107,48 @@ public function getVariantsMatch( $options ); } + + /** + * match the variants which exactly matched with merchant selection (&& operation) + * @param \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest $argument input argument + * @param array $metadata metadata + * @param array $options call options + * @return \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse + */ + public function getVariantsExactlyMatch( + \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest $argument, + $metadata = [], + $options = [] + ) + { + return $this->_simpleRequest( + '/magento.catalogStorefrontApi.proto.VariantService/getVariantsExactlyMatch', + $argument, + ['\Magento\CatalogStorefrontApi\Proto\ProductVariantResponse', 'decode'], + $metadata, + $options + ); + } + + /** + * get all variants which contain at least one of merchant selection (|| operation) + * @param \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest $argument input argument + * @param array $metadata metadata + * @param array $options call options + * @return \Magento\CatalogStorefrontApi\Proto\ProductVariantResponse + */ + public function getVariantsInclude( + \Magento\CatalogStorefrontApi\Proto\OptionSelectionRequest $argument, + $metadata = [], + $options = [] + ) + { + return $this->_simpleRequest( + '/magento.catalogStorefrontApi.proto.VariantService/getVariantsInclude', + $argument, + ['\Magento\CatalogStorefrontApi\Proto\ProductVariantResponse', 'decode'], + $metadata, + $options + ); + } } diff --git a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php index d149f308d..e413477cb 100644 --- a/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php +++ b/app/code/Magento/CatalogStorefrontApi/Proto/VariantServiceInterface.php @@ -46,4 +46,22 @@ public function getProductVariants(GRPC\ContextInterface $ctx, ProductVariantReq * @throws GRPC\Exception\InvokeException */ public function getVariantsMatch(GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse; + + /** + * @param GRPC\ContextInterface $ctx + * @param OptionSelectionRequest $in + * @return ProductVariantResponse + * + * @throws GRPC\Exception\InvokeException + */ + public function getVariantsExactlyMatch(GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse; + + /** + * @param GRPC\ContextInterface $ctx + * @param OptionSelectionRequest $in + * @return ProductVariantResponse + * + * @throws GRPC\Exception\InvokeException + */ + public function getVariantsInclude(GRPC\ContextInterface $ctx, OptionSelectionRequest $in): ProductVariantResponse; } diff --git a/magento.proto b/magento.proto index 76779c91f..80e3366a7 100755 --- a/magento.proto +++ b/magento.proto @@ -460,13 +460,10 @@ service VariantService { rpc deleteProductVariants (DeleteVariantsRequest) returns (DeleteVariantsResponse) {} // get all variants that belong to a product rpc getProductVariants (ProductVariantRequest) returns (ProductVariantResponse) {} - // match the variants which correspond, and do not contradict, the merchant selection (%like operation) 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) {} }