Skip to content

Commit 8eb668b

Browse files
authored
Merge pull request #2706 from magento-architects/MAGETWO-92773-Anchor-Category
[architects] MAGETWO-92773: [GraphQL] Products cannot be fetched in parent/anchor category #89
2 parents c2c4f29 + c47e1c9 commit 8eb668b

File tree

9 files changed

+139
-22
lines changed

9 files changed

+139
-22
lines changed

app/code/Magento/CatalogGraphQl/Model/Resolver/Category.php renamed to app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,10 @@
2020
use Magento\Framework\Reflection\DataObjectProcessor;
2121

2222
/**
23-
* Category field resolver, used for GraphQL request processing.
23+
* Resolver for category objects the product is assigned to.
2424
*/
25-
class Category implements ResolverInterface
25+
class Categories implements ResolverInterface
2626
{
27-
/**
28-
* Product category ids
29-
*/
30-
const PRODUCT_CATEGORY_IDS_KEY = 'category_ids';
31-
3227
/**
3328
* @var Collection
3429
*/
@@ -89,10 +84,13 @@ public function __construct(
8984
*/
9085
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) : Value
9186
{
92-
$this->categoryIds = array_merge($this->categoryIds, $value[self::PRODUCT_CATEGORY_IDS_KEY]);
87+
/** @var \Magento\Catalog\Model\Product $product */
88+
$product = $value['model'];
89+
$categoryIds = $product->getCategoryIds();
90+
$this->categoryIds = array_merge($this->categoryIds, $categoryIds);
9391
$that = $this;
9492

95-
return $this->valueFactory->create(function () use ($that, $value, $info) {
93+
return $this->valueFactory->create(function () use ($that, $categoryIds, $info) {
9694
$categories = [];
9795
if (empty($that->categoryIds)) {
9896
return [];
@@ -104,7 +102,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
104102
}
105103
/** @var CategoryInterface | \Magento\Catalog\Model\Category $item */
106104
foreach ($this->collection as $item) {
107-
if (in_array($item->getId(), $value[$that::PRODUCT_CATEGORY_IDS_KEY])) {
105+
if (in_array($item->getId(), $categoryIds)) {
108106
$categories[$item->getId()] = $this->dataObjectProcessor->buildOutputDataArray(
109107
$item,
110108
CategoryInterface::class

app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function resolve(
6363
array $args = null
6464
): Value {
6565
$args['filter'] = [
66-
'category_ids' => [
66+
'category_id' => [
6767
'eq' => $value['id']
6868
]
6969
];

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,14 @@ public function getList(
7878
$this->collectionProcessor->process($collection, $searchCriteria, $attributes);
7979

8080
if (!$isChildSearch) {
81-
$visibilityIds
82-
= $isSearch ? $this->visibility->getVisibleInSearchIds() : $this->visibility->getVisibleInCatalogIds();
81+
$visibilityIds = $isSearch
82+
? $this->visibility->getVisibleInSearchIds()
83+
: $this->visibility->getVisibleInCatalogIds();
8384
$collection->setVisibility($visibilityIds);
8485
}
8586
$collection->load();
8687

8788
// Methods that perform extra fetches post-load
88-
$collection->addCategoryIds();
8989
$collection->addMediaGalleryData();
9090
$collection->addOptionsToResult();
9191

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,18 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface
2525
/**
2626
* @var array
2727
*/
28-
private $additionalAttributes;
28+
private $additionalAttributes = ['min_price', 'max_price', 'category_id'];
2929

3030
/**
3131
* @param ConfigInterface $config
3232
* @param array $additionalAttributes
3333
*/
3434
public function __construct(
3535
ConfigInterface $config,
36-
array $additionalAttributes = ['min_price', 'max_price', 'category_ids']
36+
array $additionalAttributes = []
3737
) {
3838
$this->config = $config;
39-
$this->additionalAttributes = $additionalAttributes;
39+
$this->additionalAttributes = array_merge($this->additionalAttributes, $additionalAttributes);
4040
}
4141

4242
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogGraphQl\Model\Resolver\Products\SearchCriteria\CollectionProcessor\FilterProcessor;
9+
10+
use Magento\Catalog\Model\CategoryFactory;
11+
use Magento\Catalog\Model\ResourceModel\Category as CategoryResourceModel;
12+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
13+
use Magento\Framework\Api\Filter;
14+
use Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor\CustomFilterInterface;
15+
use Magento\Framework\Data\Collection\AbstractDb;
16+
use Magento\Framework\Exception\LocalizedException;
17+
18+
/**
19+
* Category filter allows to filter products collection using 'category_id' filter from search criteria.
20+
*/
21+
class CategoryFilter implements CustomFilterInterface
22+
{
23+
/**
24+
* @var CategoryFactory
25+
*/
26+
private $categoryFactory;
27+
28+
/**
29+
* @var CategoryResourceModel
30+
*/
31+
private $categoryResourceModel;
32+
33+
/**
34+
* @param CategoryFactory $categoryFactory
35+
* @param CategoryResourceModel $categoryResourceModel
36+
*/
37+
public function __construct(
38+
CategoryFactory $categoryFactory,
39+
CategoryResourceModel $categoryResourceModel
40+
) {
41+
$this->categoryFactory = $categoryFactory;
42+
$this->categoryResourceModel = $categoryResourceModel;
43+
}
44+
45+
/**
46+
* Apply filter by 'category_id' to product collection.
47+
*
48+
* For anchor categories, the products from all children categories will be present in the result.
49+
*
50+
* @param Filter $filter
51+
* @param AbstractDb $collection
52+
* @return bool Whether the filter is applied
53+
* @throws LocalizedException
54+
*/
55+
public function apply(Filter $filter, AbstractDb $collection)
56+
{
57+
$conditionType = $filter->getConditionType();
58+
59+
if ($conditionType !== 'eq') {
60+
throw new LocalizedException(__("'category_id' only supports 'eq' condition type."));
61+
}
62+
63+
$categoryId = $filter->getValue();
64+
/** @var Collection $collection */
65+
$category = $this->categoryFactory->create();
66+
$this->categoryResourceModel->load($category, $categoryId);
67+
$collection->addCategoryFilter($category);
68+
69+
return true;
70+
}
71+
}

app/code/Magento/CatalogGraphQl/etc/graphql/di.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
<item name="price" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductPriceFilter</item>
7070
<item name="min_price" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductPriceFilter</item>
7171
<item name="max_price" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductPriceFilter</item>
72-
<item name="category_ids" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\CollectionProcessor\FilterProcessor\ProductCategoryFilter</item>
72+
<item name="category_id" xsi:type="object">Magento\CatalogGraphQl\Model\Resolver\Products\SearchCriteria\CollectionProcessor\FilterProcessor\CategoryFilter</item>
7373
</argument>
7474
</arguments>
7575
</virtualType>

app/code/Magento/CatalogGraphQl/etc/schema.graphqls

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\
278278
price: ProductPrices @doc(description: "A ProductPrices object, indicating the price of an item") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Price")
279279
gift_message_available: String @doc(description: "Indicates whether a gift message is available")
280280
manufacturer: Int @doc(description: "A number representing the product's manufacturer")
281-
categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category")
281+
categories: [CategoryInterface] @doc(description: "The categories assigned to a product") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Categories")
282282
canonical_url: String @doc(description: "Canonical URL") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CanonicalUrl")
283283
}
284284

@@ -441,7 +441,7 @@ input ProductFilterInput @doc(description: "ProductFilterInput defines the filte
441441
min_price: FilterTypeInput @doc(description:"The numeric minimal price of the product. Do not include the currency code.")
442442
max_price: FilterTypeInput @doc(description:"The numeric maximal price of the product. Do not include the currency code.")
443443
special_price: FilterTypeInput @doc(description:"The numeric special price of the product. Do not include the currency code.")
444-
category_ids: FilterTypeInput @doc(description: "An array of category IDs the product belongs to")
444+
category_id: FilterTypeInput @doc(description: "Category ID the product belongs to")
445445
options_container: FilterTypeInput @doc(description: "If the product has multiple options, determines where they appear on the product page")
446446
required_options: FilterTypeInput @doc(description: "Indicates whether the product has required options")
447447
has_options: FilterTypeInput @doc(description: "Indicates whether additional attributes have been created for the product")

dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\GraphQl\Catalog;
99

10+
use Magento\Catalog\Api\Data\CategoryInterface;
1011
use Magento\Framework\DataObject;
1112
use Magento\TestFramework\TestCase\GraphQlAbstract;
1213
use Magento\Catalog\Api\Data\ProductInterface;
@@ -254,7 +255,6 @@ public function testCategoryProducts()
254255
default_group_id
255256
is_default
256257
}
257-
258258
}
259259
}
260260
}
@@ -281,6 +281,54 @@ public function testCategoryProducts()
281281
$this->assertWebsites($firstProduct, $response['category']['products']['items'][0]['websites']);
282282
}
283283

284+
/**
285+
* @magentoApiDataFixture Magento/Catalog/_files/categories.php
286+
*/
287+
public function testAnchorCategory()
288+
{
289+
/** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */
290+
$categoryCollection = $this->objectManager->create(
291+
\Magento\Catalog\Model\ResourceModel\Category\Collection::class
292+
);
293+
$categoryCollection->addFieldToFilter('name', 'Category 1');
294+
$category = $categoryCollection->getFirstItem();
295+
/** @var \Magento\Framework\EntityManager\MetadataPool $entityManagerMetadataPool */
296+
$entityManagerMetadataPool = $this->objectManager->create(\Magento\Framework\EntityManager\MetadataPool::class);
297+
$categoryLinkField = $entityManagerMetadataPool->getMetadata(CategoryInterface::class)->getLinkField();
298+
$categoryId = $category->getData($categoryLinkField);
299+
$this->assertNotEmpty($categoryId, "Preconditions failed: category is not available.");
300+
301+
$query = <<<QUERY
302+
{
303+
category(id: {$categoryId}) {
304+
name
305+
products(sort: {sku: ASC}) {
306+
total_count
307+
items {
308+
sku
309+
}
310+
}
311+
}
312+
}
313+
QUERY;
314+
315+
$response = $this->graphQlQuery($query);
316+
$expectedResponse = [
317+
'category' => [
318+
'name' => 'Category 1',
319+
'products' => [
320+
'total_count' => 3,
321+
'items' => [
322+
['sku' => '12345'],
323+
['sku' => 'simple'],
324+
['sku' => 'simple-4']
325+
]
326+
]
327+
]
328+
];
329+
$this->assertEquals($expectedResponse, $response);
330+
}
331+
284332
/**
285333
* @param ProductInterface $product
286334
* @param array $actualResponse

dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ public function testFilterProductsByCategoryIds()
679679
products(
680680
filter:
681681
{
682-
category_ids:{eq:"{$queryCategoryId}"}
682+
category_id:{eq:"{$queryCategoryId}"}
683683
}
684684
pageSize:2
685685

0 commit comments

Comments
 (0)