Skip to content

Commit

Permalink
Merge pull request #418 from magento-performance/ACPT-1643
Browse files Browse the repository at this point in the history
ACPT-1643: Fix Inventory salable performance issue
  • Loading branch information
adifucan authored Nov 16, 2023
2 parents 3faac9c + 98e9013 commit 790c7ff
Show file tree
Hide file tree
Showing 20 changed files with 1,131 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel;

use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\InventoryCatalog\Model\Cache\LegacyStockStatusStorage;
use Magento\InventoryCatalogApi\Api\DefaultStockProviderInterface;
use Magento\InventoryCatalogApi\Model\GetProductIdsBySkusInterface;
use Magento\InventoryIndexer\Model\ResourceModel\GetStockItemsData;
use Magento\InventorySalesApi\Model\GetStockItemDataInterface;

/**
* Retrieve legacy stock item data from stock registry by bulk operation.
*/
class GetBulkLegacyStockStatusDataFromStockRegistry
{
/**
* @var StockConfigurationInterface
*/
private StockConfigurationInterface $stockConfiguration;

/**
* @var GetProductIdsBySkusInterface
*/
private GetProductIdsBySkusInterface $getProductIdsBySkus;

/**
* @var DefaultStockProviderInterface
*/
private DefaultStockProviderInterface $defaultStockProvider;

/**
* @var LegacyStockStatusStorage
*/
private LegacyStockStatusStorage $legacyStockStatusStorage;

/**
* @param StockConfigurationInterface $stockConfiguration
* @param LegacyStockStatusStorage $legacyStockStatusStorage
* @param GetProductIdsBySkusInterface $getProductIdsBySkus
* @param DefaultStockProviderInterface $defaultStockProvider
*/
public function __construct(
StockConfigurationInterface $stockConfiguration,
LegacyStockStatusStorage $legacyStockStatusStorage,
GetProductIdsBySkusInterface $getProductIdsBySkus,
DefaultStockProviderInterface $defaultStockProvider
) {
$this->stockConfiguration = $stockConfiguration;
$this->legacyStockStatusStorage = $legacyStockStatusStorage;
$this->getProductIdsBySkus = $getProductIdsBySkus;
$this->defaultStockProvider = $defaultStockProvider;
}

/**
* Retrieve legacy stock item data from stock registry by bulk operation
*
* @param GetStockItemsData $subject
* @param callable $proceed
* @param array $skus
* @param int $stockId
* @return array
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function aroundExecute(GetStockItemsData $subject, callable $proceed, array $skus, int $stockId): array
{
$results = [];

if ($this->defaultStockProvider->getId() === $stockId) {
try {
$productIds = $this->getProductIdsBySkus->execute($skus);
} catch (NoSuchEntityException $e) {
return $proceed($skus, $stockId);
}

foreach ($skus as $sku) {
$productId = $productIds[$sku] ?? null;

if ($productId !== null) {
$stockItem = $this->legacyStockStatusStorage->get(
(int) $productId,
$this->stockConfiguration->getDefaultScopeId()
);

if ($stockItem !== null) {
$results[$sku] = [
GetStockItemDataInterface::QUANTITY => $stockItem->getQty(),
GetStockItemDataInterface::IS_SALABLE => $stockItem->getStockStatus(),
];
}
}
}
}

$originalResults = $proceed($skus, $stockId);

// Merging custom results with the original method results
foreach ($skus as $sku) {
$results[$sku] = $results[$sku] ?? $originalResults[$sku] ?? null;
}

return $results;
}
}
4 changes: 4 additions & 0 deletions InventoryCatalog/etc/frontend/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
<plugin name="inventory_catalog_get_legacy_stock_item_data_from_stock_registry"
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetLegacyStockStatusDataFromStockRegistry"/>
</type>
<type name="Magento\InventoryIndexer\Model\ResourceModel\GetStockItemsData">
<plugin name="inventory_catalog_get_bulk_legacy_stock_item_data_from_stock_registry"
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetBulkLegacyStockStatusDataFromStockRegistry"/>
</type>
<type name="Magento\InventoryConfiguration\Model\GetLegacyStockItem">
<plugin name="inventory_catalog_get_legacy_stock_item_from_stock_registry"
type="\Magento\InventoryCatalog\Plugin\InventoryConfiguration\Model\GetLegacyStockItemFromStockRegistry"/>
Expand Down
4 changes: 4 additions & 0 deletions InventoryCatalog/etc/graphql/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
<plugin name="inventory_catalog_get_legacy_stock_item_data_from_stock_registry"
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetLegacyStockStatusDataFromStockRegistry"/>
</type>
<type name="Magento\InventoryIndexer\Model\ResourceModel\GetStockItemsData">
<plugin name="inventory_catalog_get_bulk_legacy_stock_item_data_from_stock_registry"
type="Magento\InventoryCatalog\Plugin\InventoryIndexer\Model\ResourceModel\GetBulkLegacyStockStatusDataFromStockRegistry"/>
</type>
<type name="Magento\InventoryConfiguration\Model\GetLegacyStockItem">
<plugin name="inventory_catalog_get_legacy_stock_item_from_stock_registry"
type="\Magento\InventoryCatalog\Plugin\InventoryConfiguration\Model\GetLegacyStockItemFromStockRegistry"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\InventorySalesApi\Api\AreProductsSalableInterface;
use Magento\InventorySalesApi\Api\Data\IsProductSalableResultInterface;
use Magento\InventorySalesApi\Api\Data\SalesChannelInterface;
Expand Down Expand Up @@ -69,33 +71,42 @@ public function __construct(
* @param Configurable $subject
* @param array $products
* @return array
* @throws LocalizedException
* @throws NoSuchEntityException
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterGetUsedProducts(Configurable $subject, array $products): array
{
$skus = [];
foreach ($products as $product) {
foreach ($this->productsSalableStatuses as $isProductSalableResult) {
if ($isProductSalableResult->getSku() === $product->getSku()) {
continue 2;
}
}
$skus[] = $product->getSku();
}
// Use associative array for fast SKU lookup
$salableSkus = array_flip(array_map(function ($status) {
return $status->getSku();
}, $this->productsSalableStatuses));

// Collect SKUs not already in $this->productsSalableStatuses
$skus = array_filter(array_map(function ($product) use ($salableSkus) {
$sku = $product->getSku();
return isset($salableSkus[$sku]) ? null : $sku; // Return null if SKU exists, SKU otherwise
}, $products));

// If there are no new SKUs to process, filter products and return
if (empty($skus)) {
$this->filterProducts($products, $this->productsSalableStatuses);
return $products;
}

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

// Update products salable statuses with new salable information
$this->productsSalableStatuses = array_merge(
$this->productsSalableStatuses,
$this->areProductsSalable->execute($skus, $stock->getStockId())
);

// Filter products once all updates are made
$this->filterProducts($products, $this->productsSalableStatuses);

return $products;
}

Expand All @@ -106,15 +117,22 @@ public function afterGetUsedProducts(Configurable $subject, array $products): ar
* @param array $isSalableResults
* @return void
*/
private function filterProducts(array $products, array $isSalableResults) : void
private function filterProducts(array &$products, array $isSalableResults) : void
{
// Transform $isSalableResults into an associative array with SKU as the key
$salabilityBySku = [];
foreach ($isSalableResults as $result) {
$salabilityBySku[$result->getSku()] = $result->isSalable();
}

foreach ($products as $key => $product) {
foreach ($isSalableResults as $result) {
if ($result->getSku() === $product->getSku() && !$result->isSalable()) {
$product->setIsSalable(0);
if (!$this->stockConfiguration->isShowOutOfStock()) {
unset($products[$key]);
}
$sku = $product->getSku();

// Check if the SKU exists in the salability results and if it's not salable
if (isset($salabilityBySku[$sku]) && !$salabilityBySku[$sku]) {
$product->setIsSalable(0);
if (!$this->stockConfiguration->isShowOutOfStock()) {
unset($products[$key]);
}
}
}
Expand Down
Loading

0 comments on commit 790c7ff

Please sign in to comment.