Skip to content

Commit 264f5b0

Browse files
committed
MC-38518: B2B - Qty update on cart - Page load time is high
- Fix repetitive database queries in checkout process
1 parent 9a655bb commit 264f5b0

File tree

10 files changed

+296
-99
lines changed

10 files changed

+296
-99
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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\CatalogInventory\Model;
9+
10+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
11+
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
12+
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
13+
use Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory;
14+
use Magento\CatalogInventory\Api\StockStatusRepositoryInterface;
15+
16+
/**
17+
* Preload stock data into stock registry
18+
*/
19+
class StockRegistryPreloader
20+
{
21+
/**
22+
* @var StockItemRepositoryInterface
23+
*/
24+
private $stockItemRepository;
25+
/**
26+
* @var StockConfigurationInterface
27+
*/
28+
private $stockConfiguration;
29+
/**
30+
* @var StockRegistryStorage
31+
*/
32+
private $stockRegistryStorage;
33+
/**
34+
* @var StockItemCriteriaInterfaceFactory
35+
*/
36+
private $stockItemCriteriaFactory;
37+
/**
38+
* @var StockStatusCriteriaInterfaceFactory
39+
*/
40+
private $stockStatusCriteriaFactory;
41+
/**
42+
* @var StockStatusRepositoryInterface
43+
*/
44+
private $stockStatusRepository;
45+
46+
/**
47+
* @param StockItemRepositoryInterface $stockItemRepository
48+
* @param StockStatusRepositoryInterface $stockStatusRepository
49+
* @param StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory
50+
* @param StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory
51+
* @param StockConfigurationInterface $stockConfiguration
52+
* @param StockRegistryStorage $stockRegistryStorage
53+
*/
54+
public function __construct(
55+
StockItemRepositoryInterface $stockItemRepository,
56+
StockStatusRepositoryInterface $stockStatusRepository,
57+
StockItemCriteriaInterfaceFactory $stockItemCriteriaFactory,
58+
StockStatusCriteriaInterfaceFactory $stockStatusCriteriaFactory,
59+
StockConfigurationInterface $stockConfiguration,
60+
StockRegistryStorage $stockRegistryStorage
61+
) {
62+
$this->stockItemRepository = $stockItemRepository;
63+
$this->stockStatusRepository = $stockStatusRepository;
64+
$this->stockItemCriteriaFactory = $stockItemCriteriaFactory;
65+
$this->stockStatusCriteriaFactory = $stockStatusCriteriaFactory;
66+
$this->stockConfiguration = $stockConfiguration;
67+
$this->stockRegistryStorage = $stockRegistryStorage;
68+
}
69+
70+
/**
71+
* Preload stock item into stock registry
72+
*
73+
* @param array $productIds
74+
* @param int|null $scopeId
75+
* @return \Magento\CatalogInventory\Api\Data\StockItemInterface[]
76+
*/
77+
public function preloadStockItems(array $productIds, ?int $scopeId = null): array
78+
{
79+
$scopeId = $scopeId ?? $this->stockConfiguration->getDefaultScopeId();
80+
$criteria = $this->stockItemCriteriaFactory->create();
81+
$criteria->setProductsFilter($productIds);
82+
$criteria->setScopeFilter($scopeId);
83+
$collection = $this->stockItemRepository->getList($criteria);
84+
$this->setStockItems($collection->getItems(), $scopeId);
85+
return $collection->getItems();
86+
}
87+
88+
/**
89+
* Saves stock items into registry
90+
*
91+
* @param \Magento\CatalogInventory\Api\Data\StockItemInterface[] $stockItems
92+
* @param int $scopeId
93+
*/
94+
public function setStockItems(array $stockItems, int $scopeId): void
95+
{
96+
foreach ($stockItems as $item) {
97+
$this->stockRegistryStorage->setStockItem($item->getProductId(), $scopeId, $item);
98+
}
99+
}
100+
101+
/**
102+
* Preload stock status into stock registry
103+
*
104+
* @param array $productIds
105+
* @param int|null $scopeId
106+
* @return \Magento\CatalogInventory\Api\Data\StockStatusInterface[]
107+
*/
108+
public function preloadStockStatuses(array $productIds, ?int $scopeId = null): array
109+
{
110+
$scopeId = $scopeId ?? $this->stockConfiguration->getDefaultScopeId();
111+
$criteria = $this->stockStatusCriteriaFactory->create();
112+
$criteria->setProductsFilter($productIds);
113+
$criteria->setScopeFilter($scopeId);
114+
$collection = $this->stockStatusRepository->getList($criteria);
115+
$this->setStockStatuses($collection->getItems(), $scopeId);
116+
return $collection->getItems();
117+
}
118+
119+
/**
120+
* Saves stock statuses into registry
121+
*
122+
* @param \Magento\CatalogInventory\Api\Data\StockStatusInterface[] $stockStatuses
123+
* @param int $scopeId
124+
*/
125+
public function setStockStatuses(array $stockStatuses, int $scopeId): void
126+
{
127+
foreach ($stockStatuses as $item) {
128+
$this->stockRegistryStorage->setStockStatus($item->getProductId(), $scopeId, $item);
129+
}
130+
}
131+
}

app/code/Magento/CatalogInventory/Observer/AddStockItemsObserver.php

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Magento\Catalog\Model\ResourceModel\Product\Collection;
1111
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1212
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
13-
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
13+
use Magento\CatalogInventory\Model\StockRegistryPreloader;
1414
use Magento\Framework\Event\Observer;
1515
use Magento\Framework\Event\ObserverInterface;
1616

@@ -19,36 +19,27 @@
1919
*/
2020
class AddStockItemsObserver implements ObserverInterface
2121
{
22-
/**
23-
* @var StockItemCriteriaInterfaceFactory
24-
*/
25-
private $criteriaInterfaceFactory;
26-
27-
/**
28-
* @var StockItemRepositoryInterface
29-
*/
30-
private $stockItemRepository;
31-
3222
/**
3323
* @var StockConfigurationInterface
3424
*/
3525
private $stockConfiguration;
26+
/**
27+
* @var StockRegistryPreloader
28+
*/
29+
private $stockRegistryPreloader;
3630

3731
/**
3832
* AddStockItemsObserver constructor.
3933
*
40-
* @param StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory
41-
* @param StockItemRepositoryInterface $stockItemRepository
4234
* @param StockConfigurationInterface $stockConfiguration
35+
* @param StockRegistryPreloader $stockRegistryPreloader
4336
*/
4437
public function __construct(
45-
StockItemCriteriaInterfaceFactory $criteriaInterfaceFactory,
46-
StockItemRepositoryInterface $stockItemRepository,
47-
StockConfigurationInterface $stockConfiguration
38+
StockConfigurationInterface $stockConfiguration,
39+
StockRegistryPreloader $stockRegistryPreloader
4840
) {
49-
$this->criteriaInterfaceFactory = $criteriaInterfaceFactory;
50-
$this->stockItemRepository = $stockItemRepository;
5141
$this->stockConfiguration = $stockConfiguration;
42+
$this->stockRegistryPreloader = $stockRegistryPreloader;
5243
}
5344

5445
/**
@@ -62,11 +53,13 @@ public function execute(Observer $observer)
6253
/** @var Collection $productCollection */
6354
$productCollection = $observer->getData('collection');
6455
$productIds = array_keys($productCollection->getItems());
65-
$criteria = $this->criteriaInterfaceFactory->create();
66-
$criteria->setProductsFilter($productIds);
67-
$criteria->setScopeFilter($this->stockConfiguration->getDefaultScopeId());
68-
$stockItemCollection = $this->stockItemRepository->getList($criteria);
69-
foreach ($stockItemCollection->getItems() as $item) {
56+
$scopeId = $this->stockConfiguration->getDefaultScopeId();
57+
$stockItems = [];
58+
if ($productIds) {
59+
$stockItems = $this->stockRegistryPreloader->preloadStockItems($productIds, $scopeId);
60+
$this->stockRegistryPreloader->preloadStockStatuses($productIds, $scopeId);
61+
}
62+
foreach ($stockItems as $item) {
7063
/** @var Product $product */
7164
$product = $productCollection->getItemById($item->getProductId());
7265
$productExtension = $product->getExtensionAttributes();

app/code/Magento/CatalogInventory/Test/Unit/Observer/AddStockItemsObserverTest.php

Lines changed: 20 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,12 @@
1010
use Magento\Catalog\Api\Data\ProductExtensionInterface;
1111
use Magento\Catalog\Model\Product;
1212
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
13-
use Magento\CatalogInventory\Api\Data\StockItemCollectionInterface;
1413
use Magento\CatalogInventory\Api\Data\StockItemInterface;
1514
use Magento\CatalogInventory\Api\StockConfigurationInterface;
16-
use Magento\CatalogInventory\Api\StockItemCriteriaInterface;
1715
use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory;
18-
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
16+
use Magento\CatalogInventory\Model\StockRegistryPreloader;
1917
use Magento\CatalogInventory\Observer\AddStockItemsObserver;
2018
use Magento\Framework\Event\Observer;
21-
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
2219
use PHPUnit\Framework\MockObject\MockObject;
2320
use PHPUnit\Framework\TestCase;
2421

@@ -33,46 +30,29 @@ class AddStockItemsObserverTest extends TestCase
3330
* @var AddStockItemsObserver
3431
*/
3532
private $subject;
36-
/**
37-
* @var StockItemCriteriaInterfaceFactory|MockObject
38-
*/
39-
private $criteriaInterfaceFactoryMock;
40-
41-
/**
42-
* @var StockItemRepositoryInterface|MockObject
43-
*/
44-
private $stockItemRepositoryMock;
4533

4634
/**
4735
* @var StockConfigurationInterface|MockObject
4836
*/
4937
private $stockConfigurationMock;
38+
/**
39+
* @var StockRegistryPreloader|MockObject
40+
*/
41+
private $stockRegistryPreloader;
5042

5143
/**
5244
* @inheritdoc
5345
*/
5446
protected function setUp(): void
5547
{
56-
$objectManager = new ObjectManager($this);
57-
$this->criteriaInterfaceFactoryMock = $this->getMockBuilder(StockItemCriteriaInterfaceFactory::class)
58-
->setMethods(['create'])
59-
->disableOriginalConstructor()
60-
->getMockForAbstractClass();
61-
$this->stockItemRepositoryMock = $this->getMockBuilder(StockItemRepositoryInterface::class)
62-
->setMethods(['getList'])
63-
->disableOriginalConstructor()
64-
->getMockForAbstractClass();
6548
$this->stockConfigurationMock = $this->getMockBuilder(StockConfigurationInterface::class)
6649
->setMethods(['getDefaultScopeId'])
6750
->disableOriginalConstructor()
6851
->getMockForAbstractClass();
69-
$this->subject = $objectManager->getObject(
70-
AddStockItemsObserver::class,
71-
[
72-
'criteriaInterfaceFactory' => $this->criteriaInterfaceFactoryMock,
73-
'stockItemRepository' => $this->stockItemRepositoryMock,
74-
'stockConfiguration' => $this->stockConfigurationMock
75-
]
52+
$this->stockRegistryPreloader = $this->createMock(StockRegistryPreloader::class);
53+
$this->subject = new AddStockItemsObserver(
54+
$this->stockConfigurationMock,
55+
$this->stockRegistryPreloader,
7656
);
7757
}
7858

@@ -84,26 +64,6 @@ public function testExecute()
8464
$productId = 1;
8565
$defaultScopeId = 0;
8666

87-
$criteria = $this->getMockBuilder(StockItemCriteriaInterface::class)
88-
->setMethods(['setProductsFilter', 'setScopeFilter'])
89-
->disableOriginalConstructor()
90-
->getMockForAbstractClass();
91-
$criteria->expects(self::once())
92-
->method('setProductsFilter')
93-
->with(self::identicalTo([$productId]))
94-
->willReturn(true);
95-
$criteria->expects(self::once())
96-
->method('setScopeFilter')
97-
->with(self::identicalTo($defaultScopeId))
98-
->willReturn(true);
99-
100-
$this->criteriaInterfaceFactoryMock->expects(self::once())
101-
->method('create')
102-
->willReturn($criteria);
103-
$stockItemCollection = $this->getMockBuilder(StockItemCollectionInterface::class)
104-
->setMethods(['getItems'])
105-
->disableOriginalConstructor()
106-
->getMockForAbstractClass();
10767
$stockItem = $this->getMockBuilder(StockItemInterface::class)
10868
->setMethods(['getProductId'])
10969
->disableOriginalConstructor()
@@ -112,14 +72,19 @@ public function testExecute()
11272
->method('getProductId')
11373
->willReturn($productId);
11474

115-
$stockItemCollection->expects(self::once())
116-
->method('getItems')
75+
$this->stockRegistryPreloader->expects(self::once())
76+
->method('preloadStockItems')
77+
->with([$productId])
11778
->willReturn([$stockItem]);
11879

119-
$this->stockItemRepositoryMock->expects(self::once())
120-
->method('getList')
121-
->with(self::identicalTo($criteria))
122-
->willReturn($stockItemCollection);
80+
$this->stockRegistryPreloader->expects(self::once())
81+
->method('preloadStockStatuses')
82+
->with([$productId])
83+
->willReturn([]);
84+
85+
$this->stockRegistryPreloader->expects(self::once())
86+
->method('preloadStockItems')
87+
->willReturn([$stockItem]);
12388

12489
$this->stockConfigurationMock->expects(self::once())
12590
->method('getDefaultScopeId')

app/code/Magento/Quote/Model/QuoteRepository.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
use Magento\Store\Model\StoreManagerInterface;
2626

2727
/**
28-
* Quote repository.
28+
* Repository for quote entity.
2929
*
3030
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
3131
*/
@@ -146,10 +146,16 @@ public function get($cartId, array $sharedStoreIds = [])
146146
public function getForCustomer($customerId, array $sharedStoreIds = [])
147147
{
148148
if (!isset($this->quotesByCustomerId[$customerId])) {
149-
$quote = $this->loadQuote('loadByCustomer', 'customerId', $customerId, $sharedStoreIds);
150-
$this->getLoadHandler()->load($quote);
151-
$this->quotesById[$quote->getId()] = $quote;
152-
$this->quotesByCustomerId[$customerId] = $quote;
149+
$customerQuote = $this->loadQuote('loadByCustomer', 'customerId', $customerId, $sharedStoreIds);
150+
$customerQuoteId = $customerQuote->getId();
151+
//prevent loading quote items for same quote
152+
if (isset($this->quotesById[$customerQuoteId])) {
153+
$customerQuote = $this->quotesById[$customerQuoteId];
154+
} else {
155+
$this->getLoadHandler()->load($customerQuote);
156+
}
157+
$this->quotesById[$customerQuoteId] = $customerQuote;
158+
$this->quotesByCustomerId[$customerId] = $customerQuote;
153159
}
154160
return $this->quotesByCustomerId[$customerId];
155161
}

0 commit comments

Comments
 (0)