Skip to content

Commit

Permalink
ENGCOM-7193: Clean expired quotes - Fix out of memory on huge quotes …
Browse files Browse the repository at this point in the history
…list #27260
  • Loading branch information
slavvka authored May 29, 2020
2 parents a8e469d + c439a16 commit 8ba6b59
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 22 deletions.
63 changes: 57 additions & 6 deletions app/code/Magento/Sales/Cron/CleanExpiredQuotes.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
*/
namespace Magento\Sales\Cron;

use Magento\Quote\Model\ResourceModel\Quote\Collection;
use Exception;
use Magento\Quote\Model\QuoteRepository;
use Magento\Quote\Model\ResourceModel\Quote\Collection as QuoteCollection;
use Magento\Sales\Model\ResourceModel\Collection\ExpiredQuotesCollection;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;

/**
* Class CleanExpiredQuotes
* Cron job for cleaning expired Quotes
*/
class CleanExpiredQuotes
{
Expand All @@ -24,16 +27,32 @@ class CleanExpiredQuotes
*/
private $storeManager;

/**
* @var QuoteRepository
*/
private $quoteRepository;

/**
* @var LoggerInterface
*/
private $logger;

/**
* @param StoreManagerInterface $storeManager
* @param ExpiredQuotesCollection $expiredQuotesCollection
* @param QuoteRepository $quoteRepository
* @param LoggerInterface $logger
*/
public function __construct(
StoreManagerInterface $storeManager,
ExpiredQuotesCollection $expiredQuotesCollection
ExpiredQuotesCollection $expiredQuotesCollection,
QuoteRepository $quoteRepository,
LoggerInterface $logger
) {
$this->storeManager = $storeManager;
$this->expiredQuotesCollection = $expiredQuotesCollection;
$this->quoteRepository = $quoteRepository;
$this->logger = $logger;
}

/**
Expand All @@ -45,9 +64,41 @@ public function execute()
{
$stores = $this->storeManager->getStores(true);
foreach ($stores as $store) {
/** @var $quotes Collection */
$quotes = $this->expiredQuotesCollection->getExpiredQuotes($store);
$quotes->walk('delete');
/** @var $quoteCollection QuoteCollection */
$quoteCollection = $this->expiredQuotesCollection->getExpiredQuotes($store);
$quoteCollection->setPageSize(50);

// Last page returns 1 even when we don't have any results
$lastPage = $quoteCollection->getSize() ? $quoteCollection->getLastPageNumber() : 0;

for ($currentPage = $lastPage; $currentPage >= 1; $currentPage--) {
$quoteCollection->setCurPage($currentPage);

$this->deleteQuotes($quoteCollection);
}
}
}

/**
* Deletes all quotes in collection
*
* @param QuoteCollection $quoteCollection
*/
private function deleteQuotes(QuoteCollection $quoteCollection): void
{
foreach ($quoteCollection as $quote) {
try {
$this->quoteRepository->delete($quote);
} catch (Exception $e) {
$message = sprintf(
'Unable to delete expired quote (ID: %s): %s',
$quote->getId(),
(string)$e
);
$this->logger->error($message);
}
}

$quoteCollection->clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Quote\Model\QuoteRepository;
use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory;
use Magento\TestFramework\Helper\Bootstrap;

/**
Expand All @@ -25,14 +26,9 @@ class CleanExpiredQuotesTest extends \PHPUnit\Framework\TestCase
private $cleanExpiredQuotes;

/**
* @var QuoteRepository
* @var QuoteCollectionFactory
*/
private $quoteRepository;

/**
* @var SearchCriteriaBuilder
*/
private $searchCriteriaBuilder;
private $quoteCollectionFactory;

/**
* @inheritdoc
Expand All @@ -41,8 +37,7 @@ protected function setUp(): void
{
$objectManager = Bootstrap::getObjectManager();
$this->cleanExpiredQuotes = $objectManager->get(CleanExpiredQuotes::class);
$this->quoteRepository = $objectManager->get(QuoteRepository::class);
$this->searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class);
$this->quoteCollectionFactory = $objectManager->get(QuoteCollectionFactory::class);
}

/**
Expand All @@ -53,17 +48,43 @@ protected function setUp(): void
*/
public function testExecute()
{
$searchCriteria = $this->searchCriteriaBuilder->create();
//Initial count - should be equal to stores number.
$this->assertEquals(2, $this->quoteRepository->getList($searchCriteria)->getTotalCount());
$this->assertQuotesCount(2);

//Deleting expired quotes
$this->cleanExpiredQuotes->execute();
$totalCount = $this->quoteRepository->getList($searchCriteria)->getTotalCount();

//Only 1 will be deleted for the store that has all of them expired by config (default_store)
$this->assertEquals(
1,
$totalCount
);
$this->assertQuotesCount(1);
}

/**
* Check if outdated quotes are deleted.
*
* @magentoConfigFixture default_store checkout/cart/delete_quote_after -365
* @magentoDataFixture Magento/Sales/_files/quotes_big_amount.php
*/
public function testExecuteWithBigAmountOfQuotes()
{
//Initial count - should be equal to 1000
$this->assertQuotesCount(1000);

//Deleting expired quotes
$this->cleanExpiredQuotes->execute();

//There should be no quotes anymore
$this->assertQuotesCount(0);
}

/**
* Optimized assert quotes count
* Uses collection getSize in order to get quick result
*
* @param int $expected
*/
private function assertQuotesCount(int $expected): void
{
$totalCount = $this->quoteCollectionFactory->create()->getSize();
$this->assertEquals($expected, $totalCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

use Magento\Framework\App\Config;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\QuoteFactory;
use Magento\Quote\Model\QuoteRepository;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreRepository;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\ObjectManager;

/** @var $objectManager ObjectManager */
$objectManager = Bootstrap::getObjectManager();
/** @var QuoteFactory $quoteFactory */
$quoteFactory = $objectManager->get(QuoteFactory::class);
/** @var QuoteRepository $quoteRepository */
$quoteRepository = $objectManager->get(QuoteRepository::class);
/** @var StoreRepository $storeRepository */
$storeRepository = $objectManager->get(StoreRepository::class);
/** @var Config $appConfig */
$appConfig = $objectManager->get(Config::class);
$appConfig->clean();

/** @var Store $defaultStore */
$defaultStore = $storeRepository->getActiveStoreByCode('default');

for ($i = 0; $i < 1000; $i++) {
/** @var Quote $quote */
$quote = $quoteFactory->create();
$quote->setStoreId($defaultStore->getId());
$quoteRepository->save($quote);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Registry;
use Magento\Quote\Model\QuoteRepository;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\ObjectManager;

/** @var ObjectManager $objectManager */
$objectManager = Bootstrap::getObjectManager();

/** @var Registry $registry */
$registry = $objectManager->get(Registry::class);
$registry->unregister('isSecureArea');
$registry->register('isSecureArea', true);

/** @var QuoteRepository $quoteRepository */
$quoteRepository = $objectManager->get(QuoteRepository::class);
/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class);
$searchCriteria = $searchCriteriaBuilder->create();
$items = $quoteRepository->getList($searchCriteria)
->getItems();
foreach ($items as $item) {
$quoteRepository->delete($item);
}

$registry->unregister('isSecureArea');
$registry->register('isSecureArea', false);

0 comments on commit 8ba6b59

Please sign in to comment.