From e3460a2a6cd7508591d23ca58c74a64d6efaced3 Mon Sep 17 00:00:00 2001 From: Alex Paliarush Date: Wed, 13 Jun 2018 14:03:52 -0500 Subject: [PATCH 1/3] MAGETWO-92773: [GraphQL] Products cannot be fetched in parent/anchor category #89 --- .../Model/Resolver/Category.php | 16 ++--- .../Model/Resolver/Category/Products.php | 2 +- .../Products/DataProvider/Product.php | 6 +- .../ProductEntityAttributesForAst.php | 2 +- .../FilterProcessor/CategoryFilter.php | 69 +++++++++++++++++++ .../Magento/CatalogGraphQl/etc/graphql/di.xml | 2 +- .../CatalogGraphQl/etc/schema.graphqls | 2 +- .../Magento/GraphQl/Catalog/CategoryTest.php | 36 +++++++++- .../GraphQl/Catalog/ProductSearchTest.php | 2 +- 9 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php index a17de7374534b..7a4bd098f0129 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php @@ -20,15 +20,10 @@ use Magento\Framework\Reflection\DataObjectProcessor; /** - * Category field resolver, used for GraphQL request processing. + * Resolver for categoriy objects the product is assigned to. */ class Category implements ResolverInterface { - /** - * Product category ids - */ - const PRODUCT_CATEGORY_IDS_KEY = 'category_ids'; - /** * @var Collection */ @@ -89,10 +84,13 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) : Value { - $this->categoryIds = array_merge($this->categoryIds, $value[self::PRODUCT_CATEGORY_IDS_KEY]); + /** @var \Magento\Catalog\Model\Product $product */ + $product = $value['model']; + $categoryIds = $product->getCategoryIds(); + $this->categoryIds = array_merge($this->categoryIds, $categoryIds); $that = $this; - return $this->valueFactory->create(function () use ($that, $value, $info) { + return $this->valueFactory->create(function () use ($that, $categoryIds, $info) { $categories = []; if (empty($that->categoryIds)) { return []; @@ -104,7 +102,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } /** @var CategoryInterface | \Magento\Catalog\Model\Category $item */ foreach ($this->collection as $item) { - if (in_array($item->getId(), $value[$that::PRODUCT_CATEGORY_IDS_KEY])) { + if (in_array($item->getId(), $categoryIds)) { $categories[$item->getId()] = $this->dataObjectProcessor->buildOutputDataArray( $item, CategoryInterface::class diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php index 5927e747c2238..406b4173e68e1 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php @@ -63,7 +63,7 @@ public function resolve( array $args = null ): Value { $args['filter'] = [ - 'category_ids' => [ + 'category_id' => [ 'eq' => $value['id'] ] ]; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php index f2020cbeca88e..2c73d7a079170 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php @@ -78,14 +78,14 @@ public function getList( $this->collectionProcessor->process($collection, $searchCriteria, $attributes); if (!$isChildSearch) { - $visibilityIds - = $isSearch ? $this->visibility->getVisibleInSearchIds() : $this->visibility->getVisibleInCatalogIds(); + $visibilityIds = $isSearch + ? $this->visibility->getVisibleInSearchIds() + : $this->visibility->getVisibleInCatalogIds(); $collection->setVisibility($visibilityIds); } $collection->load(); // Methods that perform extra fetches post-load - $collection->addCategoryIds(); $collection->addMediaGalleryData(); $collection->addOptionsToResult(); diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php index 96bef3ffc09c4..c09b23e90d44e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php @@ -33,7 +33,7 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface */ public function __construct( ConfigInterface $config, - array $additionalAttributes = ['min_price', 'max_price', 'category_ids'] + array $additionalAttributes = ['min_price', 'max_price', 'category_id'] ) { $this->config = $config; $this->additionalAttributes = $additionalAttributes; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php new file mode 100644 index 0000000000000..cdbc8046d773b --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php @@ -0,0 +1,69 @@ +categoryFactory = $categoryFactory; + $this->categoryResourceModel = $categoryResourceModel; + } + + /** + * Apply filter by 'category_id' to product collection. + * + * For anchor categories, the products from all children categories will be present in the result. + * + * @param Filter $filter + * @param AbstractDb $collection + * @return bool Whether the filter is applied + * @throws LocalizedException + */ + public function apply(Filter $filter, AbstractDb $collection) + { + $conditionType = $filter->getConditionType(); + + if ($conditionType !== 'eq') { + throw new LocalizedException(__("'category_id' only supports 'eq' condition type.")); + } + + $categoryId = $filter->getValue(); + /** @var Collection $collection */ + $category = $this->categoryFactory->create(); + $this->categoryResourceModel->load($category, $categoryId); + $collection->addCategoryFilter($category); + + return true; + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index 03631d049dafe..68a292ede6b4a 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -69,7 +69,7 @@ Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductPriceFilter Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductPriceFilter Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductPriceFilter - Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductCategoryFilter + Magento\CatalogGraphQl\Model\Resolver\Products\SearchCriteria\CollectionProcessor\FilterProcessor\CategoryFilter diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index f9df24e8ff731..7c89e308cac8a 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -441,7 +441,7 @@ input ProductFilterInput @doc(description: "ProductFilterInput defines the filte min_price: FilterTypeInput @doc(description:"The numeric minimal price of the product. Do not include the currency code.") max_price: FilterTypeInput @doc(description:"The numeric maximal price of the product. Do not include the currency code.") special_price: FilterTypeInput @doc(description:"The numeric special price of the product. Do not include the currency code.") - category_ids: FilterTypeInput @doc(description: "An array of category IDs the product belongs to") + category_id: FilterTypeInput @doc(description: "Category ID the product belongs to") options_container: FilterTypeInput @doc(description: "If the product has multiple options, determines where they appear on the product page") required_options: FilterTypeInput @doc(description: "Indicates whether the product has required options") has_options: FilterTypeInput @doc(description: "Indicates whether additional attributes have been created for the product") diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index dca3bf9abd182..3d6a373f842aa 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -254,7 +254,6 @@ public function testCategoryProducts() default_group_id is_default } - } } } @@ -281,6 +280,41 @@ public function testCategoryProducts() $this->assertWebsites($firstProduct, $response['category']['products']['items'][0]['websites']); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + */ + public function testAnchorCategory() + { + $categoryId = 3; + $query = <<graphQlQuery($query); + $expectedResponse = [ + 'category' => [ + 'products' => [ + 'total_count' => 3, + 'items' => [ + ['sku' => '12345'], + ['sku' => 'simple'], + ['sku' => 'simple-4'] + ] + ] + ] + ]; + $this->assertEquals($expectedResponse, $response); + } + /** * @param ProductInterface $product * @param array $actualResponse diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 65e044a5f005b..dc5a66fbb34ab 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -679,7 +679,7 @@ public function testFilterProductsByCategoryIds() products( filter: { - category_ids:{eq:"{$queryCategoryId}"} + category_id:{eq:"{$queryCategoryId}"} } pageSize:2 From 19dbdde998c136c557b14df1149c0ce48ff7060b Mon Sep 17 00:00:00 2001 From: Alex Paliarush Date: Thu, 14 Jun 2018 17:44:53 -0500 Subject: [PATCH 2/3] MAGETWO-92773: [GraphQL] Products cannot be fetched in parent/anchor category #89 --- .../Model/Resolver/{Category.php => Categories.php} | 4 ++-- .../FilterArgument/ProductEntityAttributesForAst.php | 6 +++--- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename app/code/Magento/CatalogGraphQl/Model/Resolver/{Category.php => Categories.php} (97%) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php similarity index 97% rename from app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php rename to app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php index 7a4bd098f0129..378e7cb4c3673 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php @@ -20,9 +20,9 @@ use Magento\Framework\Reflection\DataObjectProcessor; /** - * Resolver for categoriy objects the product is assigned to. + * Resolver for category objects the product is assigned to. */ -class Category implements ResolverInterface +class Categories implements ResolverInterface { /** * @var Collection diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php index c09b23e90d44e..a547f63b217fe 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php @@ -25,7 +25,7 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface /** * @var array */ - private $additionalAttributes; + private $additionalAttributes = ['min_price', 'max_price', 'category_id']; /** * @param ConfigInterface $config @@ -33,10 +33,10 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface */ public function __construct( ConfigInterface $config, - array $additionalAttributes = ['min_price', 'max_price', 'category_id'] + array $additionalAttributes = [] ) { $this->config = $config; - $this->additionalAttributes = $additionalAttributes; + $this->additionalAttributes = array_merge($this->additionalAttributes, $additionalAttributes); } /** diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 7c89e308cac8a..762861de94e67 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -278,7 +278,7 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\ price: ProductPrices @doc(description: "A ProductPrices object, indicating the price of an item") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Price") gift_message_available: String @doc(description: "Indicates whether a gift message is available") manufacturer: Int @doc(description: "A number representing the product's manufacturer") - categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category") + categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories") canonical_url: String @doc(description: "Canonical URL") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl") } From a60fd3ee6b337a9973589a482cfeb3f29d962741 Mon Sep 17 00:00:00 2001 From: Alex Paliarush Date: Wed, 20 Jun 2018 20:49:34 -0500 Subject: [PATCH 3/3] MAGETWO-92773: [GraphQL] Products cannot be fetched in parent/anchor category #89 --- .../Magento/GraphQl/Catalog/CategoryTest.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 3d6a373f842aa..0133b87e757bd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -7,6 +7,7 @@ namespace Magento\GraphQl\Catalog; +use Magento\Catalog\Api\Data\CategoryInterface; use Magento\Framework\DataObject; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Catalog\Api\Data\ProductInterface; @@ -285,10 +286,22 @@ public function testCategoryProducts() */ public function testAnchorCategory() { - $categoryId = 3; + /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */ + $categoryCollection = $this->objectManager->create( + \Magento\Catalog\Model\ResourceModel\Category\Collection::class + ); + $categoryCollection->addFieldToFilter('name', 'Category 1'); + $category = $categoryCollection->getFirstItem(); + /** @var \Magento\Framework\EntityManager\MetadataPool $entityManagerMetadataPool */ + $entityManagerMetadataPool = $this->objectManager->create(\Magento\Framework\EntityManager\MetadataPool::class); + $categoryLinkField = $entityManagerMetadataPool->getMetadata(CategoryInterface::class)->getLinkField(); + $categoryId = $category->getData($categoryLinkField); + $this->assertNotEmpty($categoryId, "Preconditions failed: category is not available."); + $query = <<graphQlQuery($query); $expectedResponse = [ 'category' => [ + 'name' => 'Category 1', 'products' => [ 'total_count' => 3, 'items' => [