Skip to content

Commit 6162e42

Browse files
committed
MC-42600: Category filter Graphql query adds category product ids in to the IN statement of the product select query
- Remove unnecessary category ids filter
1 parent 46679e7 commit 6162e42

File tree

3 files changed

+219
-18
lines changed

3 files changed

+219
-18
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public function getList(
116116
$this->getSortOrderArray($searchCriteriaForCollection)
117117
)->apply();
118118

119+
$collection->setFlag('search_resut_applied', true);
120+
119121
$collection->setVisibility($this->catalogProductVisibility->getVisibleInSiteIds());
120122
$this->collectionPreProcessor->process($collection, $searchCriteriaForCollection, $attributes, $context);
121123
$collection->load();

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchCriteria/CollectionProcessor/FilterProcessor/CategoryFilter.php

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use Magento\Catalog\Model\CategoryFactory;
1111
use Magento\Catalog\Model\ResourceModel\Category as CategoryResourceModel;
12+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
1213
use Magento\Framework\Api\Filter;
1314
use Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor\CustomFilterInterface;
1415
use Magento\Framework\Data\Collection\AbstractDb;
@@ -18,6 +19,36 @@
1819
*/
1920
class CategoryFilter implements CustomFilterInterface
2021
{
22+
/**
23+
* Equal
24+
*/
25+
private const CONDITION_TYPE_EQ = 'eq';
26+
27+
/**
28+
* Not Equal
29+
*/
30+
private const CONDITION_TYPE_NEQ = 'neq';
31+
32+
/**
33+
* In
34+
*/
35+
private const CONDITION_TYPE_IN = 'in';
36+
37+
/**
38+
* Not In
39+
*/
40+
private const CONDITION_TYPE_NIN = 'nin';
41+
42+
/**
43+
* Supported condition types
44+
*/
45+
private const CONDITION_TYPES = [
46+
self::CONDITION_TYPE_EQ,
47+
self::CONDITION_TYPE_NEQ,
48+
self::CONDITION_TYPE_IN,
49+
self::CONDITION_TYPE_NIN,
50+
];
51+
2152
/**
2253
* @var CategoryFactory
2354
*/
@@ -51,26 +82,46 @@ public function __construct(
5182
*/
5283
public function apply(Filter $filter, AbstractDb $collection)
5384
{
54-
$conditionType = $filter->getConditionType();
55-
if ($conditionType !== 'eq') {
56-
return true;
57-
}
58-
59-
$categoryIds = $filter->getValue();
60-
if (!is_array($categoryIds)) {
61-
$categoryIds = [$categoryIds];
62-
}
63-
64-
$categoryProducts = [];
65-
foreach ($categoryIds as $categoryId) {
66-
$category = $this->categoryFactory->create();
67-
$this->categoryResourceModel->load($category, $categoryId);
68-
$categoryProducts[$categoryId] = $category->getProductCollection()->getAllIds();
69-
$collection->addCategoryFilter($category);
85+
$conditionType = $filter->getConditionType() ?: self::CONDITION_TYPE_IN;
86+
$value = $filter->getValue();
87+
if ($value && in_array($conditionType, self::CONDITION_TYPES)) {
88+
if ($conditionType === self::CONDITION_TYPE_EQ) {
89+
$category = $this->getCategory((int) $value);
90+
/** @var Collection $collection */
91+
/** This filter adds ability to sort by position*/
92+
$collection->addCategoryFilter($category);
93+
} elseif (!$collection->getFlag('search_resut_applied')) {
94+
/** Prevent filtering duplication as the filter should be already applied to the search result */
95+
$values = is_array($value) ? $value : explode(',', (string) $value);
96+
$categoryIds = [];
97+
foreach ($values as $value) {
98+
$category = $this->getCategory((int) $value);
99+
$children = [];
100+
$childrenStr = $category->getIsAnchor() ? $category->getChildren(true) : '';
101+
if ($childrenStr) {
102+
$children = explode(',', $childrenStr);
103+
}
104+
array_push($categoryIds, $value, ...$children);
105+
}
106+
/** @var Collection $collection */
107+
$collection->addCategoriesFilter([$conditionType => array_map('intval', $categoryIds)]);
108+
}
70109
}
71110

72-
$categoryProductIds = array_unique(array_merge([], ...$categoryProducts));
73-
$collection->addIdFilter($categoryProductIds);
74111
return true;
75112
}
113+
114+
/**
115+
* Retrieve the category model by ID
116+
*
117+
* @param int $id
118+
* @return \Magento\Catalog\Model\Category
119+
*/
120+
private function getCategory(int $id): \Magento\Catalog\Model\Category
121+
{
122+
/** @var \Magento\Catalog\Model\Category $category */
123+
$category = $this->categoryFactory->create();
124+
$this->categoryResourceModel->load($category, $id);
125+
return $category;
126+
}
76127
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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\Test\Unit\Model\Resolver\Products\SearchCriteria\CollectionProcessor\FilterProcessor;
9+
10+
use Magento\Catalog\Model\CategoryFactory;
11+
use Magento\Catalog\Model\ResourceModel\Category;
12+
use Magento\CatalogGraphQl\Model\Resolver\Products\SearchCriteria\CollectionProcessor\FilterProcessor\CategoryFilter;
13+
use Magento\Framework\Api\Filter;
14+
use PHPUnit\Framework\MockObject\MockObject;
15+
use PHPUnit\Framework\TestCase;
16+
17+
/**
18+
* Test for Category filter
19+
*/
20+
class CategoryFilterTest extends TestCase
21+
{
22+
/**
23+
* @var CategoryFilter
24+
*/
25+
private $model;
26+
27+
/**
28+
* @var CategoryFactory|MockObject
29+
*/
30+
private $categoryFactory;
31+
32+
/**
33+
* @var Category|MockObject
34+
*/
35+
private $categoryResourceModel;
36+
37+
/**
38+
* @inheritdoc
39+
*/
40+
protected function setUp(): void
41+
{
42+
parent::setUp();
43+
$this->categoryFactory = $this->createMock(CategoryFactory::class);
44+
$this->categoryResourceModel = $this->createMock(Category::class);
45+
$this->model = new CategoryFilter(
46+
$this->categoryFactory,
47+
$this->categoryResourceModel
48+
);
49+
}
50+
51+
/**
52+
* Test that category filter works correctly wity condition type "eq"
53+
*/
54+
public function testApplyWithConditionTypeEq(): void
55+
{
56+
$filter = new Filter();
57+
$category = $this->createMock(\Magento\Catalog\Model\Category::class);
58+
$collection = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
59+
$filter->setConditionType('eq');
60+
$categoryId = 1;
61+
$filter->setValue($categoryId);
62+
$this->categoryFactory->expects($this->once())
63+
->method('create')
64+
->willReturn($category);
65+
$this->categoryResourceModel->expects($this->once())
66+
->method('load')
67+
->with($category, $categoryId);
68+
$collection->expects($this->once())
69+
->method('addCategoryFilter')
70+
->with($category);
71+
$this->model->apply($filter, $collection);
72+
}
73+
74+
/**
75+
* @param string $condition
76+
* @dataProvider applyWithOtherSupportedConditionTypesDataProvider
77+
*/
78+
public function testApplyWithOtherSupportedConditionTypes(string $condition): void
79+
{
80+
$filter = new Filter();
81+
$category = $this->getMockBuilder(\Magento\Catalog\Model\Category::class)
82+
->disableOriginalConstructor()
83+
->onlyMethods(['getChildren'])
84+
->addMethods(['getIsAnchor'])
85+
->getMock();
86+
$collection = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
87+
$filter->setConditionType($condition);
88+
$categoryId = 1;
89+
$filter->setValue($categoryId);
90+
$this->categoryFactory->expects($this->once())
91+
->method('create')
92+
->willReturn($category);
93+
$this->categoryResourceModel->expects($this->once())
94+
->method('load')
95+
->with($category, $categoryId);
96+
$collection->expects($this->never())
97+
->method('addCategoryFilter');
98+
$collection->expects($this->once())
99+
->method('addCategoriesFilter')
100+
->with([$condition => [1, 2]]);
101+
$category->expects($this->once())
102+
->method('getIsAnchor')
103+
->willReturn(true);
104+
$category->expects($this->once())
105+
->method('getChildren')
106+
->with(true)
107+
->willReturn('2');
108+
$this->model->apply($filter, $collection);
109+
}
110+
111+
/**
112+
* @return array
113+
*/
114+
public function applyWithOtherSupportedConditionTypesDataProvider(): array
115+
{
116+
return [['neq'], ['in'], ['nin'],];
117+
}
118+
119+
/**
120+
* @param string $condition
121+
* @dataProvider applyWithUnsupportedConditionTypesDataProvider
122+
*/
123+
public function testApplyWithUnsupportedConditionTypes(string $condition): void
124+
{
125+
$filter = new Filter();
126+
$collection = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
127+
$filter->setConditionType($condition);
128+
$categoryId = 1;
129+
$filter->setValue($categoryId);
130+
$this->categoryFactory->expects($this->never())
131+
->method('create');
132+
$this->categoryResourceModel->expects($this->never())
133+
->method('load');
134+
$collection->expects($this->never())
135+
->method('addCategoryFilter');
136+
$collection->expects($this->never())
137+
->method('addCategoriesFilter');
138+
$this->model->apply($filter, $collection);
139+
}
140+
141+
/**
142+
* @return array
143+
*/
144+
public function applyWithUnsupportedConditionTypesDataProvider(): array
145+
{
146+
return [['gteq'], ['lteq'], ['gt'], ['lt'], ['like'], ['nlike']];
147+
}
148+
}

0 commit comments

Comments
 (0)