Skip to content

Commit 7bdd10b

Browse files
committed
Merge remote-tracking branch 'adobe-commerce-tier-4/ACP2E-3892' into PR_2025_06_25_muntianu
2 parents 9be517f + b46cfc0 commit 7bdd10b

File tree

18 files changed

+633
-161
lines changed

18 files changed

+633
-161
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\AdvancedSearch\Model\Client;
9+
10+
class ClientException extends \Exception
11+
{
12+
13+
}

app/code/Magento/Catalog/Block/Product/ListProduct.php

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Magento\Framework\Url\Helper\Data;
2929
use Magento\Framework\App\ObjectManager;
3030
use Magento\Catalog\Helper\Output as OutputHelper;
31+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
3132

3233
/**
3334
* Product list
@@ -79,6 +80,11 @@ class ListProduct extends AbstractProduct implements IdentityInterface
7980
*/
8081
private ?array $specialPriceMap = null;
8182

83+
/**
84+
* @var CollectionFactory
85+
*/
86+
private CollectionFactory $productCollectionFactory;
87+
8288
/**
8389
* @param Context $context
8490
* @param PostHelper $postDataHelper
@@ -88,6 +94,7 @@ class ListProduct extends AbstractProduct implements IdentityInterface
8894
* @param array $data
8995
* @param OutputHelper|null $outputHelper
9096
* @param SpecialPriceBulkResolverInterface|null $specialPriceBulkResolver
97+
* @param CollectionFactory|null $collectionFactory
9198
*/
9299
public function __construct(
93100
Context $context,
@@ -97,7 +104,8 @@ public function __construct(
97104
Data $urlHelper,
98105
array $data = [],
99106
?OutputHelper $outputHelper = null,
100-
?SpecialPriceBulkResolverInterface $specialPriceBulkResolver = null
107+
?SpecialPriceBulkResolverInterface $specialPriceBulkResolver = null,
108+
?CollectionFactory $collectionFactory = null
101109
) {
102110
$this->_catalogLayer = $layerResolver->get();
103111
$this->_postDataHelper = $postDataHelper;
@@ -106,6 +114,8 @@ public function __construct(
106114
$this->specialPriceBulkResolver = $specialPriceBulkResolver ??
107115
ObjectManager::getInstance()->get(SpecialPriceBulkResolverInterface::class);
108116
$data['outputHelper'] = $outputHelper ?? ObjectManager::getInstance()->get(OutputHelper::class);
117+
$this->productCollectionFactory = $collectionFactory ??
118+
ObjectManager::getInstance()->get(CollectionFactory::class);
109119
parent::__construct(
110120
$context,
111121
$data
@@ -208,14 +218,18 @@ protected function _beforeToHtml()
208218
$this->addToolbarBlock($collection);
209219

210220
if (!$collection->isLoaded()) {
211-
$collection->load();
212-
}
213-
214-
$categoryId = $this->getLayer()->getCurrentCategory()->getId();
215-
216-
if ($categoryId) {
217-
foreach ($collection as $product) {
218-
$product->setData('category_id', $categoryId);
221+
try {
222+
$products = $collection->getItems();
223+
if ($categoryId = $this->getLayer()->getCurrentCategory()->getId()) {
224+
foreach ($products as $product) {
225+
$product->setData('category_id', $categoryId);
226+
}
227+
}
228+
} catch (\Throwable) {
229+
$this->setData('has_error', true);
230+
$collection = $this->productCollectionFactory->create();
231+
$collection->addFieldToFilter('entity_id', ['in' => []]);
232+
$this->_productCollection = $collection;
219233
}
220234
}
221235

app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@
1616
use Magento\Catalog\Model\Product\Type\Simple;
1717
use Magento\Catalog\Model\ResourceModel\Category as CategoryResourceModel;
1818
use Magento\Catalog\Model\ResourceModel\Category\Collection;
19+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
1920
use Magento\Checkout\Helper\Cart;
21+
use Magento\Framework\App\Config\ScopeConfigInterface;
2022
use Magento\Framework\Data\Helper\PostHelper;
2123
use Magento\Framework\Event\ManagerInterface;
2224
use Magento\Framework\Pricing\Render;
2325
use Magento\Framework\Registry;
2426
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
27+
use Magento\Framework\Translate\Inline\StateInterface;
2528
use Magento\Framework\Url\Helper\Data;
2629
use Magento\Framework\View\LayoutInterface;
2730
use Magento\Store\Api\Data\StoreInterface;
2831
use Magento\Store\Model\StoreManagerInterface;
32+
use PHPUnit\Framework\MockObject\Exception;
2933
use PHPUnit\Framework\MockObject\MockObject;
3034
use PHPUnit\Framework\TestCase;
3135

@@ -104,6 +108,11 @@ class ListProductTest extends TestCase
104108
*/
105109
private $renderer;
106110

111+
/**
112+
* @var CollectionFactory|MockObject
113+
*/
114+
private CollectionFactory $collectionFactory;
115+
107116
protected function setUp(): void
108117
{
109118
$objectManager = new ObjectManager($this);
@@ -145,6 +154,11 @@ protected function setUp(): void
145154
$store = $this->createMock(StoreInterface::class);
146155
$storeManager->expects($this->any())->method('getStore')->willReturn($store);
147156
$this->context->expects($this->any())->method('getStoreManager')->willReturn($storeManager);
157+
$scopeConfig = $this->createMock(ScopeConfigInterface::class);
158+
$this->context->expects($this->any())->method('getScopeConfig')->willReturn($scopeConfig);
159+
$inlineTranslation = $this->createMock(StateInterface::class);
160+
$this->context->expects($this->any())->method('getInlineTranslation')->willReturn($inlineTranslation);
161+
$this->collectionFactory = $this->createMock(CollectionFactory::class);
148162

149163
$this->block = $objectManager->getObject(
150164
ListProduct::class,
@@ -155,6 +169,7 @@ protected function setUp(): void
155169
'cartHelper' => $this->cartHelperMock,
156170
'postDataHelper' => $this->postDataHelperMock,
157171
'urlHelper' => $this->urlHelperMock,
172+
'collectionFactory' => $this->collectionFactory
158173
]
159174
);
160175
$this->block->setToolbarBlockName('mock');
@@ -165,6 +180,32 @@ protected function tearDown(): void
165180
$this->block = null;
166181
}
167182

183+
/**
184+
* @return void
185+
* @throws Exception
186+
*/
187+
public function testEmptyCollection(): void
188+
{
189+
$this->block->setData('translate_inline', true);
190+
$this->prodCollectionMock->expects($this->any())
191+
->method('getItems')
192+
->willThrowException(new \Exception('No items found.'));
193+
$this->layerMock->expects($this->once())
194+
->method('getProductCollection')
195+
->willReturn($this->prodCollectionMock);
196+
$collection = $this->createMock(Collection::class);
197+
$this->collectionFactory->expects($this->once())->method('create')->willReturn($collection);
198+
$collection->expects($this->once())->method('addFieldToFilter');
199+
$currentCategory = $this->createMock(\Magento\Catalog\Model\Category::class);
200+
$currentCategory->expects($this->any())
201+
->method('getId')
202+
->willReturn('1');
203+
$this->layerMock->expects($this->any())
204+
->method('getCurrentCategory')
205+
->willReturn($currentCategory);
206+
$this->block->toHtml();
207+
}
208+
168209
public function testGetIdentities()
169210
{
170211
$productTag = 'cat_p_1';

app/code/Magento/Catalog/view/frontend/templates/product/list.phtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $_productCollection = $block->getLoadedProductCollection();
2020
/** @var \Magento\Catalog\Helper\Output $_helper */
2121
$_helper = $block->getData('outputHelper');
2222
?>
23-
<?php if (!$_productCollection->count()): ?>
23+
<?php if ($block->getData('has_error') || !$_productCollection->count()): ?>
2424
<div class="message info empty">
2525
<div><?= $escaper->escapeHtml(__('We can\'t find products matching the selection.')) ?></div>
2626
</div>

app/code/Magento/CatalogGraphQl/Model/Resolver/Layer/DataProvider/Filters.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Magento\Catalog\Model\Layer\Filter\AbstractFilter;
1111
use Magento\CatalogGraphQl\Model\Resolver\Layer\FiltersProvider;
1212
use Magento\Catalog\Model\Layer\Filter\Item;
13+
use Magento\Framework\Exception\LocalizedException;
1314

1415
/**
1516
* Layered navigation filters data provider.
@@ -76,11 +77,11 @@ public function getData(string $layerType, ?array $attributesToFilter = null) :
7677
* Check for adding filter to the list
7778
*
7879
* @param AbstractFilter $filter
79-
* @param array $attributesToFilter
80+
* @param array|null $attributesToFilter
8081
* @return bool
81-
* @throws \Magento\Framework\Exception\LocalizedException
82+
* @throws LocalizedException
8283
*/
83-
private function isNeedToAddFilter(AbstractFilter $filter, array $attributesToFilter): bool
84+
private function isNeedToAddFilter(AbstractFilter $filter, ?array $attributesToFilter = null): bool
8485
{
8586
if ($attributesToFilter === null) {
8687
$result = (bool)$filter->getItemsCount();

app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function resolve(
5757
private function prepareAttributesResults(array $value): ?array
5858
{
5959
$attributes = [];
60-
if (!empty($value['search_result'])) {
60+
if (!empty($value['search_result']) && $value['search_result']->getSearchAggregation()) {
6161
$buckets = $value['search_result']->getSearchAggregation()->getBuckets();
6262
foreach ($buckets as $bucket) {
6363
if (!empty($bucket->getValues())) {

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query;
99

10+
use Magento\AdvancedSearch\Model\Client\ClientException;
1011
use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder;
1112
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch;
1213
use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search\QueryPopularity;
@@ -29,42 +30,42 @@ class Search implements ProductQueryInterface
2930
/**
3031
* @var SearchInterface
3132
*/
32-
private $search;
33+
private SearchInterface $search;
3334

3435
/**
3536
* @var SearchResultFactory
3637
*/
37-
private $searchResultFactory;
38+
private SearchResultFactory $searchResultFactory;
3839

3940
/**
4041
* @var FieldSelection
4142
*/
42-
private $fieldSelection;
43+
private FieldSelection $fieldSelection;
4344

4445
/**
4546
* @var ArgumentsProcessorInterface
4647
*/
47-
private $argsSelection;
48+
private ArgumentsProcessorInterface $argsSelection;
4849

4950
/**
5051
* @var ProductSearch
5152
*/
52-
private $productsProvider;
53+
private ProductSearch $productsProvider;
5354

5455
/**
5556
* @var SearchCriteriaBuilder
5657
*/
57-
private $searchCriteriaBuilder;
58+
private SearchCriteriaBuilder $searchCriteriaBuilder;
5859

5960
/**
6061
* @var Suggestions
6162
*/
62-
private $suggestions;
63+
private Suggestions $suggestions;
6364

6465
/**
6566
* @var QueryPopularity
6667
*/
67-
private $queryPopularity;
68+
private QueryPopularity $queryPopularity;
6869

6970
/**
7071
* @param SearchInterface $search
@@ -111,47 +112,61 @@ public function getResult(
111112
ContextInterface $context
112113
): SearchResult {
113114
$searchCriteria = $this->buildSearchCriteria($args, $info);
114-
$itemsResults = $this->search->search($searchCriteria);
115-
$searchResults = $this->productsProvider->getList(
116-
$searchCriteria,
117-
$itemsResults,
118-
$this->fieldSelection->getProductsFieldSelection($info),
119-
$context
120-
);
121-
122-
$totalPages = $searchCriteria->getPageSize()
123-
? ((int)ceil($searchResults->getTotalCount() / $searchCriteria->getPageSize()))
124-
: 0;
125-
126-
// add query statistics data
127-
if (!empty($args['search'])) {
128-
$this->queryPopularity->execute($context, $args['search'], (int) $searchResults->getTotalCount());
115+
try {
116+
$itemsResults = $this->search->search($searchCriteria);
117+
$searchResults = $this->productsProvider->getList(
118+
$searchCriteria,
119+
$itemsResults,
120+
$this->fieldSelection->getProductsFieldSelection($info),
121+
$context
122+
);
123+
124+
$totalPages = $searchCriteria->getPageSize()
125+
? ((int)ceil($searchResults->getTotalCount() / $searchCriteria->getPageSize()))
126+
: 0;
127+
128+
// add query statistics data
129+
if (!empty($args['search'])) {
130+
$this->queryPopularity->execute($context, $args['search'], (int) $searchResults->getTotalCount());
131+
}
132+
133+
$productArray = [];
134+
/** @var \Magento\Catalog\Model\Product $product */
135+
foreach ($searchResults->getItems() as $product) {
136+
$productArray[$product->getId()] = $product->getData();
137+
$productArray[$product->getId()]['model'] = $product;
138+
}
139+
140+
$suggestions = [];
141+
$totalCount = (int) $searchResults->getTotalCount();
142+
if ($totalCount === 0 && !empty($args['search'])) {
143+
$suggestions = $this->suggestions->execute($context, $args['search']);
144+
}
145+
146+
return $this->searchResultFactory->create(
147+
[
148+
'totalCount' => $totalCount,
149+
'productsSearchResult' => $productArray,
150+
'searchAggregation' => $itemsResults->getAggregations(),
151+
'pageSize' => $args['pageSize'],
152+
'currentPage' => $args['currentPage'],
153+
'totalPages' => $totalPages,
154+
'suggestions' => $suggestions,
155+
]
156+
);
157+
} catch (\InvalidArgumentException|ClientException) {
158+
return $this->searchResultFactory->create(
159+
[
160+
'totalCount' => 0,
161+
'productsSearchResult' => [],
162+
'searchAggregation' => null,
163+
'pageSize' => $args['pageSize'],
164+
'currentPage' => $args['currentPage'],
165+
'totalPages' => 0,
166+
'suggestions' => [],
167+
]
168+
);
129169
}
130-
131-
$productArray = [];
132-
/** @var \Magento\Catalog\Model\Product $product */
133-
foreach ($searchResults->getItems() as $product) {
134-
$productArray[$product->getId()] = $product->getData();
135-
$productArray[$product->getId()]['model'] = $product;
136-
}
137-
138-
$suggestions = [];
139-
$totalCount = (int) $searchResults->getTotalCount();
140-
if ($totalCount === 0 && !empty($args['search'])) {
141-
$suggestions = $this->suggestions->execute($context, $args['search']);
142-
}
143-
144-
return $this->searchResultFactory->create(
145-
[
146-
'totalCount' => $totalCount,
147-
'productsSearchResult' => $productArray,
148-
'searchAggregation' => $itemsResults->getAggregations(),
149-
'pageSize' => $args['pageSize'],
150-
'currentPage' => $args['currentPage'],
151-
'totalPages' => $totalPages,
152-
'suggestions' => $suggestions,
153-
]
154-
);
155170
}
156171

157172
/**

0 commit comments

Comments
 (0)