diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php
similarity index 89%
rename from app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php
rename to app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php
index a17de7374534b..378e7cb4c3673 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php
@@ -20,15 +20,10 @@
use Magento\Framework\Reflection\DataObjectProcessor;
/**
- * Category field resolver, used for GraphQL request processing.
+ * Resolver for category objects the product is assigned to.
*/
-class Category implements ResolverInterface
+class Categories 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..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_ids']
+ array $additionalAttributes = []
) {
$this->config = $config;
- $this->additionalAttributes = $additionalAttributes;
+ $this->additionalAttributes = array_merge($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..e3b3588166163
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php
@@ -0,0 +1,71 @@
+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..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")
}
@@ -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..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;
@@ -254,7 +255,6 @@ public function testCategoryProducts()
default_group_id
is_default
}
-
}
}
}
@@ -281,6 +281,54 @@ public function testCategoryProducts()
$this->assertWebsites($firstProduct, $response['category']['products']['items'][0]['websites']);
}
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/categories.php
+ */
+ public function testAnchorCategory()
+ {
+ /** @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' => [
+ ['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