Skip to content

Commit e5c2eeb

Browse files
committed
Merge branch 'ACP2E-3475' of https://github.com/adobe-commerce-tier-4/magento2ce into PR-VK-2024-12-17-CE
2 parents 94a25ee + f9a690e commit e5c2eeb

File tree

2 files changed

+224
-7
lines changed

2 files changed

+224
-7
lines changed

app/code/Magento/CatalogImportExport/Model/Export/Product.php

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2012 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\CatalogImportExport\Model\Export;
77

@@ -373,6 +373,16 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
373373
*/
374374
private array $attributeFrontendTypes = [];
375375

376+
/**
377+
* @var int
378+
*/
379+
private int $currentMaxAllowedMemoryUsage = 0;
380+
381+
/**
382+
* @var int
383+
*/
384+
private int $currentMemoryUsage = 0;
385+
376386
/**
377387
* Product constructor.
378388
*
@@ -854,7 +864,10 @@ protected function _getEntityCollection($resetCollection = false)
854864
*/
855865
protected function getItemsPerPage()
856866
{
857-
if ($this->_itemsPerPage === null) {
867+
if ($this->_itemsPerPage === null ||
868+
$this->currentMemoryUsage < memory_get_usage(true) ||
869+
$this->currentMaxAllowedMemoryUsage < memory_get_usage(true)
870+
) {
858871
$memoryLimitConfigValue = trim(ini_get('memory_limit'));
859872
$lastMemoryLimitLetter = strtolower($memoryLimitConfigValue[strlen($memoryLimitConfigValue) - 1]);
860873
$memoryLimit = (int) $memoryLimitConfigValue;
@@ -884,9 +897,19 @@ protected function getItemsPerPage()
884897
// Maximal Products limit
885898
$maxProductsLimit = 5000;
886899

900+
$this->currentMaxAllowedMemoryUsage = (int)($memoryLimit * $memoryUsagePercent);
901+
$this->currentMemoryUsage = memory_get_usage(true);
902+
887903
$this->_itemsPerPage = (int)(
888-
($memoryLimit * $memoryUsagePercent - memory_get_usage(true)) / $memoryPerProduct
904+
($this->currentMaxAllowedMemoryUsage - $this->currentMemoryUsage) / $memoryPerProduct
889905
);
906+
907+
$this->_itemsPerPage = $this->adjustItemsPerPageByAttributeOptions(
908+
$this->_itemsPerPage,
909+
$this->currentMaxAllowedMemoryUsage,
910+
$this->currentMemoryUsage
911+
);
912+
890913
if ($this->_itemsPerPage < $minProductsLimit) {
891914
$this->_itemsPerPage = $minProductsLimit;
892915
}
@@ -897,6 +920,61 @@ protected function getItemsPerPage()
897920
return $this->_itemsPerPage;
898921
}
899922

923+
/**
924+
* Adjust items per page by attribute options
925+
*
926+
* @param int $initialItemsPerPage
927+
* @param int $memoryLimit
928+
* @param int $currentMemoryUsage
929+
* @return int
930+
*/
931+
private function adjustItemsPerPageByAttributeOptions(
932+
int $initialItemsPerPage,
933+
int $memoryLimit,
934+
int $currentMemoryUsage
935+
): int {
936+
$maxAttributeOptions = $this->getMaxAttributeValues();
937+
$minProductsLimit = 500;
938+
$maxProductsLimit = 5000;
939+
$memoryPerProduct = 500000;
940+
941+
if ($maxAttributeOptions > 5000) {
942+
$adjustedItemsPerPage = max(1000, (int)($initialItemsPerPage * 0.25));
943+
} elseif ($maxAttributeOptions > 2500) {
944+
$adjustedItemsPerPage = max(2500, (int)($initialItemsPerPage * 0.5));
945+
} elseif ($maxAttributeOptions > 1000) {
946+
$adjustedItemsPerPage = max(3500, (int)($initialItemsPerPage * 0.75));
947+
} else {
948+
$adjustedItemsPerPage = $initialItemsPerPage;
949+
}
950+
951+
$availableMemory = $memoryLimit - $currentMemoryUsage;
952+
$maxItemsByMemory = (int)($availableMemory / $memoryPerProduct);
953+
954+
$adjustedItemsPerPage = min($adjustedItemsPerPage, $maxItemsByMemory);
955+
$adjustedItemsPerPage = max($minProductsLimit, $adjustedItemsPerPage);
956+
$adjustedItemsPerPage = min($maxProductsLimit, $adjustedItemsPerPage);
957+
958+
return $adjustedItemsPerPage;
959+
}
960+
961+
/**
962+
* Get max attribute values
963+
*
964+
* @return int
965+
*/
966+
967+
private function getMaxAttributeValues(): int
968+
{
969+
$maxCount = 0;
970+
971+
foreach ($this->_attributeValues as $attributeValues) {
972+
$maxCount = max($maxCount, count($attributeValues));
973+
}
974+
975+
return $maxCount;
976+
}
977+
900978
/**
901979
* Set page and page size to collection
902980
*

app/code/Magento/CatalogImportExport/Test/Unit/Model/Export/ProductTest.php

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2013 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -11,20 +11,24 @@
1111
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory;
1212
use Magento\CatalogImportExport\Model\Export\Product;
1313
use Magento\CatalogImportExport\Model\Export\Product\Type\Factory;
14+
use Magento\CatalogImportExport\Model\Export\ProductFilterInterface;
1415
use Magento\CatalogImportExport\Model\Export\RowCustomizer\Composite;
16+
use Magento\CatalogInventory\Api\StockConfigurationInterface;
1517
use Magento\Eav\Model\Config;
1618
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
1719
use Magento\Eav\Model\Entity\Type;
1820
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory;
1921
use Magento\Framework\App\ResourceConnection;
2022
use Magento\Framework\EntityManager\MetadataPool;
23+
use Magento\Framework\Exception\LocalizedException;
2124
use Magento\Framework\Logger\Monolog;
2225
use Magento\Framework\Stdlib\DateTime\Timezone;
2326
use Magento\ImportExport\Model\Export\Adapter\AbstractAdapter;
2427
use Magento\ImportExport\Model\Export\ConfigInterface;
2528
use Magento\Store\Model\Store;
2629
use Magento\Store\Model\StoreManager;
2730
use Magento\Store\Model\StoreManagerInterface;
31+
use PHPUnit\Framework\MockObject\Exception;
2832
use PHPUnit\Framework\MockObject\MockObject;
2933
use PHPUnit\Framework\TestCase;
3034
use Psr\Log\LoggerInterface;
@@ -139,6 +143,21 @@ class ProductTest extends TestCase
139143
*/
140144
protected $object;
141145

146+
/**
147+
* @var ProductFilterInterface|MockObject
148+
*/
149+
private $filter;
150+
151+
/**
152+
* @var StockConfigurationInterface|MockObject
153+
*/
154+
private $stockConfiguration;
155+
156+
/**
157+
* @return void
158+
* @throws LocalizedException
159+
* @throws Exception
160+
*/
142161
protected function setUp(): void
143162
{
144163
$this->localeDate = $this->createMock(Timezone::class);
@@ -210,6 +229,9 @@ protected function setUp(): void
210229
'getContents',
211230
]);
212231

232+
$this->filter = $this->createMock(ProductFilterInterface::class);
233+
$this->stockConfiguration = $this->createMock(StockConfigurationInterface::class);
234+
213235
$constructorMethods = [
214236
'initTypeModels',
215237
'initAttributes',
@@ -255,7 +277,10 @@ protected function setUp(): void
255277
$this->attributeColFactory,
256278
$this->typeFactory,
257279
$this->linkTypeProvider,
258-
$this->rowCustomizer
280+
$this->rowCustomizer,
281+
[],
282+
$this->filter,
283+
$this->stockConfiguration
259284
);
260285
$this->setPropertyValue($this->product, 'metadataPool', $this->metadataPool);
261286

@@ -410,4 +435,118 @@ protected function setPropertyValue(&$object, $property, $value)
410435

411436
return $object;
412437
}
438+
439+
/**
440+
* Test for getItemsPerPage and adjustItemsPerPageByAttributeOptions methods
441+
*
442+
* @return void
443+
* @throws \ReflectionException
444+
*
445+
* @dataProvider getItemsPerPageDataProvider
446+
*/
447+
public function testGetItemsPerPage($scenarios)
448+
{
449+
450+
$reflection = new \ReflectionClass(get_class($this->object));
451+
$method = $reflection->getMethod('getItemsPerPage');
452+
453+
$currentMemoryLimit = ini_get('memory_limit');
454+
455+
foreach ($scenarios as $scenario) {
456+
if ($currentMemoryLimit !== "-1" && $currentMemoryLimit < $scenario['memory_limit']) {
457+
$this->markTestSkipped('Memory limit is too low for this test');
458+
}
459+
ini_set('memory_limit', $scenario['memory_limit']);
460+
$this->setPropertyValue(
461+
$this->product,
462+
'_attributeValues',
463+
['test_attribute' => $scenario['options'] ?? []]
464+
);
465+
$result = $method->invoke($this->product);
466+
$this->assertLessThanOrEqual(
467+
$scenario['expected_items_per_page'],
468+
$result,
469+
'Memory limit: ' . $scenario['memory_limit'] . ' Options count: ' . count($scenario['options'])
470+
);
471+
$this->setPropertyValue($this->product, '_itemsPerPage', null);
472+
ini_set('memory_limit', $currentMemoryLimit);
473+
}
474+
}
475+
476+
/**
477+
* @return array[]
478+
*/
479+
public static function getItemsPerPageDataProvider(): array
480+
{
481+
$options = [];
482+
483+
// Simulate different scenarios without attribute options
484+
$scenarios['Attribute options: ' . count($options)] = [[
485+
[
486+
'memory_limit' => '4G',
487+
'options' => $options,
488+
'expected_items_per_page' => 5000,
489+
],
490+
[
491+
'memory_limit' => '3G',
492+
'options' => $options,
493+
'expected_items_per_page' => 5000,
494+
],
495+
[
496+
'memory_limit' => '2G',
497+
'options' => $options,
498+
'expected_items_per_page' => 5000,
499+
]
500+
]];
501+
502+
$options = [];
503+
for ($i = 0; $i <= 5000; $i++) {
504+
$options[] = ['label' => 'Option ' . $i, 'value' => $i];
505+
}
506+
507+
// Simulate different scenarios with attribute options over 5000
508+
$scenarios['Attribute options: ' . count($options)] = [[
509+
[
510+
'memory_limit' => '4G',
511+
'options' => $options,
512+
'expected_items_per_page' => 1800,
513+
],
514+
[
515+
'memory_limit' => '3G',
516+
'options' => $options,
517+
'expected_items_per_page' => 1500,
518+
],
519+
[
520+
'memory_limit' => '2G',
521+
'options' => $options,
522+
'expected_items_per_page' => 1000,
523+
]
524+
]];
525+
526+
$options = [];
527+
for ($i = 0; $i <= 2500; $i++) {
528+
$options[] = ['label' => 'Option ' . $i, 'value' => $i];
529+
}
530+
531+
// Simulate different scenarios with attribute options over 2500
532+
$scenarios['Attribute options: ' . count($options)] = [[
533+
[
534+
'memory_limit' => '4G',
535+
'options' => $options,
536+
'expected_items_per_page' => 3500,
537+
],
538+
[
539+
'memory_limit' => '3G',
540+
'options' => $options,
541+
'expected_items_per_page' => 3000,
542+
],
543+
[
544+
'memory_limit' => '2G',
545+
'options' => $options,
546+
'expected_items_per_page' => 2500,
547+
]
548+
]];
549+
550+
return $scenarios;
551+
}
413552
}

0 commit comments

Comments
 (0)