Skip to content

Commit 790c7ff

Browse files
authored
Merge pull request #418 from magento-performance/ACPT-1643
ACPT-1643: Fix Inventory salable performance issue
2 parents 3faac9c + 98e9013 commit 790c7ff

File tree

20 files changed

+1131
-65
lines changed

20 files changed

+1131
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel;
9+
10+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
11+
use Magento\Framework\Exception\NoSuchEntityException;
12+
use Magento\InventoryCatalog\Model\Cache\LegacyStockStatusStorage;
13+
use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface;
14+
use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface;
15+
use Magento\InventoryIndexer\Model\ResourceModel\GetStockItemsData;
16+
use Magento\InventorySalesApi\Model\GetStockItemDataInterface;
17+
18+
/**
19+
* Retrieve legacy stock item data from stock registry by bulk operation.
20+
*/
21+
class GetBulkLegacyStockStatusDataFromStockRegistry
22+
{
23+
/**
24+
* @var StockConfigurationInterface
25+
*/
26+
private StockConfigurationInterface $stockConfiguration;
27+
28+
/**
29+
* @var GetProductIdsBySkusInterface
30+
*/
31+
private GetProductIdsBySkusInterface $getProductIdsBySkus;
32+
33+
/**
34+
* @var DefaultStockProviderInterface
35+
*/
36+
private DefaultStockProviderInterface $defaultStockProvider;
37+
38+
/**
39+
* @var LegacyStockStatusStorage
40+
*/
41+
private LegacyStockStatusStorage $legacyStockStatusStorage;
42+
43+
/**
44+
* @param StockConfigurationInterface $stockConfiguration
45+
* @param LegacyStockStatusStorage $legacyStockStatusStorage
46+
* @param GetProductIdsBySkusInterface $getProductIdsBySkus
47+
* @param DefaultStockProviderInterface $defaultStockProvider
48+
*/
49+
public function __construct(
50+
StockConfigurationInterface $stockConfiguration,
51+
LegacyStockStatusStorage $legacyStockStatusStorage,
52+
GetProductIdsBySkusInterface $getProductIdsBySkus,
53+
DefaultStockProviderInterface $defaultStockProvider
54+
) {
55+
$this->stockConfiguration = $stockConfiguration;
56+
$this->legacyStockStatusStorage = $legacyStockStatusStorage;
57+
$this->getProductIdsBySkus = $getProductIdsBySkus;
58+
$this->defaultStockProvider = $defaultStockProvider;
59+
}
60+
61+
/**
62+
* Retrieve legacy stock item data from stock registry by bulk operation
63+
*
64+
* @param GetStockItemsData $subject
65+
* @param callable $proceed
66+
* @param array $skus
67+
* @param int $stockId
68+
* @return array
69+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
70+
*/
71+
public function aroundExecute(GetStockItemsData $subject, callable $proceed, array $skus, int $stockId): array
72+
{
73+
$results = [];
74+
75+
if ($this->defaultStockProvider->getId() === $stockId) {
76+
try {
77+
$productIds = $this->getProductIdsBySkus->execute($skus);
78+
} catch (NoSuchEntityException $e) {
79+
return $proceed($skus, $stockId);
80+
}
81+
82+
foreach ($skus as $sku) {
83+
$productId = $productIds[$sku] ?? null;
84+
85+
if ($productId !== null) {
86+
$stockItem = $this->legacyStockStatusStorage->get(
87+
(int) $productId,
88+
$this->stockConfiguration->getDefaultScopeId()
89+
);
90+
91+
if ($stockItem !== null) {
92+
$results[$sku] = [
93+
GetStockItemDataInterface::QUANTITY => $stockItem->getQty(),
94+
GetStockItemDataInterface::IS_SALABLE => $stockItem->getStockStatus(),
95+
];
96+
}
97+
}
98+
}
99+
}
100+
101+
$originalResults = $proceed($skus, $stockId);
102+
103+
// Merging custom results with the original method results
104+
foreach ($skus as $sku) {
105+
$results[$sku] = $results[$sku] ?? $originalResults[$sku] ?? null;
106+
}
107+
108+
return $results;
109+
}
110+
}

InventoryCatalog/etc/frontend/di.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
<plugin name="inventory_catalog_get_legacy_stock_item_data_from_stock_registry"
1111
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetLegacyStockStatusDataFromStockRegistry"/>
1212
</type>
13+
<type name="Magento\InventoryIndexer\Model\ResourceModel\GetStockItemsData">
14+
<plugin name="inventory_catalog_get_bulk_legacy_stock_item_data_from_stock_registry"
15+
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetBulkLegacyStockStatusDataFromStockRegistry"/>
16+
</type>
1317
<type name="Magento\InventoryConfiguration\Model\GetLegacyStockItem">
1418
<plugin name="inventory_catalog_get_legacy_stock_item_from_stock_registry"
1519
type="\Magento\InventoryCatalog\Plugin\InventoryConfiguration\Model\GetLegacyStockItemFromStockRegistry"/>

InventoryCatalog/etc/graphql/di.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
<plugin name="inventory_catalog_get_legacy_stock_item_data_from_stock_registry"
1111
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetLegacyStockStatusDataFromStockRegistry"/>
1212
</type>
13+
<type name="Magento\InventoryIndexer\Model\ResourceModel\GetStockItemsData">
14+
<plugin name="inventory_catalog_get_bulk_legacy_stock_item_data_from_stock_registry"
15+
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetBulkLegacyStockStatusDataFromStockRegistry"/>
16+
</type>
1317
<type name="Magento\InventoryConfiguration\Model\GetLegacyStockItem">
1418
<plugin name="inventory_catalog_get_legacy_stock_item_from_stock_registry"
1519
type="\Magento\InventoryCatalog\Plugin\InventoryConfiguration\Model\GetLegacyStockItemFromStockRegistry"/>

InventoryConfigurableProduct/Plugin/Model/Product/Type/Configurable/IsSalableOptionPlugin.php

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1111
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
12+
use Magento\Framework\Exception\LocalizedException;
13+
use Magento\Framework\Exception\NoSuchEntityException;
1214
use Magento\InventorySalesApi\Api\AreProductsSalableInterface;
1315
use Magento\InventorySalesApi\Api\Data\IsProductSalableResultInterface;
1416
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
@@ -69,33 +71,42 @@ public function __construct(
6971
* @param Configurable $subject
7072
* @param array $products
7173
* @return array
74+
* @throws LocalizedException
75+
* @throws NoSuchEntityException
7276
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
7377
*/
7478
public function afterGetUsedProducts(Configurable $subject, array $products): array
7579
{
76-
$skus = [];
77-
foreach ($products as $product) {
78-
foreach ($this->productsSalableStatuses as $isProductSalableResult) {
79-
if ($isProductSalableResult->getSku() === $product->getSku()) {
80-
continue 2;
81-
}
82-
}
83-
$skus[] = $product->getSku();
84-
}
80+
// Use associative array for fast SKU lookup
81+
$salableSkus = array_flip(array_map(function ($status) {
82+
return $status->getSku();
83+
}, $this->productsSalableStatuses));
8584

85+
// Collect SKUs not already in $this->productsSalableStatuses
86+
$skus = array_filter(array_map(function ($product) use ($salableSkus) {
87+
$sku = $product->getSku();
88+
return isset($salableSkus[$sku]) ? null : $sku; // Return null if SKU exists, SKU otherwise
89+
}, $products));
90+
91+
// If there are no new SKUs to process, filter products and return
8692
if (empty($skus)) {
8793
$this->filterProducts($products, $this->productsSalableStatuses);
8894
return $products;
8995
}
9096

97+
// Only now do we need the website and stock information
9198
$website = $this->storeManager->getWebsite();
9299
$stock = $this->stockResolver->execute(SalesChannelInterface::TYPE_WEBSITE, $website->getCode());
93100

101+
// Update products salable statuses with new salable information
94102
$this->productsSalableStatuses = array_merge(
95103
$this->productsSalableStatuses,
96104
$this->areProductsSalable->execute($skus, $stock->getStockId())
97105
);
106+
107+
// Filter products once all updates are made
98108
$this->filterProducts($products, $this->productsSalableStatuses);
109+
99110
return $products;
100111
}
101112

@@ -106,15 +117,22 @@ public function afterGetUsedProducts(Configurable $subject, array $products): ar
106117
* @param array $isSalableResults
107118
* @return void
108119
*/
109-
private function filterProducts(array $products, array $isSalableResults) : void
120+
private function filterProducts(array &$products, array $isSalableResults) : void
110121
{
122+
// Transform $isSalableResults into an associative array with SKU as the key
123+
$salabilityBySku = [];
124+
foreach ($isSalableResults as $result) {
125+
$salabilityBySku[$result->getSku()] = $result->isSalable();
126+
}
127+
111128
foreach ($products as $key => $product) {
112-
foreach ($isSalableResults as $result) {
113-
if ($result->getSku() === $product->getSku() && !$result->isSalable()) {
114-
$product->setIsSalable(0);
115-
if (!$this->stockConfiguration->isShowOutOfStock()) {
116-
unset($products[$key]);
117-
}
129+
$sku = $product->getSku();
130+
131+
// Check if the SKU exists in the salability results and if it's not salable
132+
if (isset($salabilityBySku[$sku]) && !$salabilityBySku[$sku]) {
133+
$product->setIsSalable(0);
134+
if (!$this->stockConfiguration->isShowOutOfStock()) {
135+
unset($products[$key]);
118136
}
119137
}
120138
}

0 commit comments

Comments
 (0)