Skip to content

Commit

Permalink
MAGETWO-70346: Configurable product shows on frontend after deleting …
Browse files Browse the repository at this point in the history
…child products

 - Add plugin around delete to update all indexers for child products
 - Add class to update all indexers related to product
 - Unit Tests
 - Functional test
  • Loading branch information
Eric Bohanon committed Sep 5, 2017
1 parent 9223ab3 commit e6bbe78
Show file tree
Hide file tree
Showing 11 changed files with 524 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
namespace Magento\Catalog\Controller\Adminhtml\Product;

use Magento\Framework\Controller\ResultFactory;
use Magento\Catalog\Controller\Adminhtml\Product\Builder;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;

class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product
{
Expand All @@ -26,20 +26,29 @@ class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product
*/
protected $collectionFactory;

/**
* @var ProductRepositoryInterface
*/
private $productRepository;

/**
* @param Context $context
* @param Builder $productBuilder
* @param Filter $filter
* @param CollectionFactory $collectionFactory
* @param ProductRepositoryInterface $productRepository
*/
public function __construct(
Context $context,
Builder $productBuilder,
Filter $filter,
CollectionFactory $collectionFactory
CollectionFactory $collectionFactory,
ProductRepositoryInterface $productRepository = null
) {
$this->filter = $filter;
$this->collectionFactory = $collectionFactory;
$this->productRepository = $productRepository
?: \Magento\Framework\App\ObjectManager::getInstance()->create(ProductRepositoryInterface::class);
parent::__construct($context, $productBuilder);
}

Expand All @@ -50,8 +59,9 @@ public function execute()
{
$collection = $this->filter->getCollection($this->collectionFactory->create());
$productDeleted = 0;
/** @var \Magento\Catalog\Model\Product $product */
foreach ($collection->getItems() as $product) {
$product->delete();
$this->productRepository->delete($product);
$productDeleted++;
}
$this->messageManager->addSuccess(
Expand Down
101 changes: 101 additions & 0 deletions app/code/Magento/Catalog/Model/Indexer/Product/Full.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Catalog\Model\Indexer\Product;

use Magento\Framework\Indexer\ActionInterface;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\PageCache\Model\Config;
use Magento\Framework\App\Cache\TypeListInterface;

/**
* Reindex all relevant product indexers
*/
class Full implements ActionInterface
{
/**
* @var IndexerRegistry
*/
private $indexerRegistry;

/**
* @var Config
*/
private $pageCacheConfig;

/**
* @var TypeListInterface
*/
private $cacheTypeList;

/**
* @var string[]
*/
private $indexerList;

/**
* Initialize dependencies
*
* @param IndexerRegistry $indexerRegistry
* @param Config $pageCacheConfig
* @param TypeListInterface $cacheTypeList
* @param string[] $indexerList
*/
public function __construct(
IndexerRegistry $indexerRegistry,
Config $pageCacheConfig,
TypeListInterface $cacheTypeList,
array $indexerList
) {
$this->indexerRegistry = $indexerRegistry;
$this->pageCacheConfig = $pageCacheConfig;
$this->cacheTypeList = $cacheTypeList;
$this->indexerList = $indexerList;
}

/**
* {@inheritdoc}
*/
public function executeFull()
{
foreach ($this->indexerList as $indexerName) {
$indexer = $this->indexerRegistry->get($indexerName);
if (!$indexer->isScheduled()) {
$indexer->reindexAll();
}
}
}

/**
* {@inheritdoc}
*/
public function executeList(array $ids)
{
if (!empty($ids)) {
foreach ($this->indexerList as $indexerName) {
$indexer = $this->indexerRegistry->get($indexerName);
if (!$indexer->isScheduled()) {
$indexer->reindexList($ids);
}
}
}
}

/**
* {@inheritDoc}
*/
public function executeRow($id)
{
if (!empty($id)) {
foreach ($this->indexerList as $indexerName) {
$indexer = $this->indexerRegistry->get($indexerName);
if (!$indexer->isScheduled()) {
$indexer->reindexRow($id);
}
}
}
}
}
132 changes: 132 additions & 0 deletions app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/FullTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Catalog\Test\Unit\Model\Indexer\Product;

use Magento\Catalog\Model\Indexer\Product\Full;
use Magento\Framework\Indexer\IndexerInterface;
use PHPUnit\Framework\TestCase;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\PageCache\Model\Config;
use Magento\Framework\App\Cache\TypeListInterface;

class FullTest extends TestCase
{
/**
* @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
*/
private $objectManager;

/**
* @var IndexerRegistry|\PHPUnit_Framework_MockObject_MockObject
*/
private $indexerRegistryMock;

/**
* @var Config|\PHPUnit_Framework_MockObject_MockObject
*/
private $configMock;

/**
* @var TypeListInterface|\PHPUnit_Framework_MockObject_MockObject
*/
private $typeListMock;

/**
* @var Full
*/
private $full;

public function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->indexerRegistryMock = $this->createMock(IndexerRegistry::class);
$this->configMock = $this->createMock(Config::class);
$this->typeListMock = $this->getMockForAbstractClass(TypeListInterface::class, [], "", false);

$this->full = $this->objectManager->getObject(
Full::class,
[
'indexerRegistry' => $this->indexerRegistryMock,
'pageCacheConfig' => $this->configMock,
'cacheTypeList' => $this->typeListMock,
'indexerList' => ['catalog_indexer', 'product_indexer', 'stock_indexer', 'search_indexer']
]
);
}

public function testExecuteFull()
{
$indexerMock = $this->getMockForAbstractClass(IndexerInterface::class, [], "", false);
$indexerMock->expects($this->exactly(4))->method('isScheduled')->willReturn(false);
$indexerMock->expects($this->exactly(4))->method('reindexAll');
$this->indexerRegistryMock->expects($this->exactly(4))->method('get')->willReturn($indexerMock);
$this->configMock->expects($this->once())->method('isEnabled')->willReturn(true);
$this->typeListMock->expects($this->once())->method('invalidate')->with('full_page');

$this->full->executeFull();
}

public function testExecuteFullPageCacheDisabled()
{
$indexerMock = $this->getMockForAbstractClass(IndexerInterface::class, [], "", false);
$indexerMock->expects($this->exactly(4))->method('isScheduled')->willReturn(false);
$indexerMock->expects($this->exactly(4))->method('reindexAll');
$this->indexerRegistryMock->expects($this->exactly(4))->method('get')->willReturn($indexerMock);
$this->configMock->expects($this->once())->method('isEnabled')->willReturn(false);
$this->typeListMock->expects($this->never())->method('invalidate');

$this->full->executeFull();
}

public function testExecuteList()
{
$indexerMock = $this->getMockForAbstractClass(IndexerInterface::class, [], "", false);
$indexerMock->expects($this->exactly(4))->method('isScheduled')->willReturn(false);
$indexerMock->expects($this->exactly(4))->method('reindexList')->with([1, 2]);
$this->indexerRegistryMock->expects($this->exactly(4))->method('get')->willReturn($indexerMock);
$this->configMock->expects($this->once())->method('isEnabled')->willReturn(true);
$this->typeListMock->expects($this->once())->method('invalidate')->with('full_page');

$this->full->executeList([1, 2]);
}

public function testExecuteListPageCacheDisabled()
{
$indexerMock = $this->getMockForAbstractClass(IndexerInterface::class, [], "", false);
$indexerMock->expects($this->exactly(4))->method('isScheduled')->willReturn(false);
$indexerMock->expects($this->exactly(4))->method('reindexList')->with([1, 2]);
$this->indexerRegistryMock->expects($this->exactly(4))->method('get')->willReturn($indexerMock);
$this->configMock->expects($this->once())->method('isEnabled')->willReturn(false);
$this->typeListMock->expects($this->never())->method('invalidate');

$this->full->executeList([1, 2]);
}

public function testExecuteRow()
{
$indexerMock = $this->getMockForAbstractClass(IndexerInterface::class, [], "", false);
$indexerMock->expects($this->exactly(4))->method('isScheduled')->willReturn(false);
$indexerMock->expects($this->exactly(4))->method('reindexRow')->with(1);
$this->indexerRegistryMock->expects($this->exactly(4))->method('get')->willReturn($indexerMock);
$this->configMock->expects($this->once())->method('isEnabled')->willReturn(true);
$this->typeListMock->expects($this->once())->method('invalidate')->with('full_page');

$this->full->executeRow(1);
}

public function testExecuteRowPageCacheDisabled()
{
$indexerMock = $this->getMockForAbstractClass(IndexerInterface::class, [], "", false);
$indexerMock->expects($this->exactly(4))->method('isScheduled')->willReturn(false);
$indexerMock->expects($this->exactly(4))->method('reindexRow')->with(1);
$this->indexerRegistryMock->expects($this->exactly(4))->method('get')->willReturn($indexerMock);
$this->configMock->expects($this->once())->method('isEnabled')->willReturn(false);
$this->typeListMock->expects($this->never())->method('invalidate');

$this->full->executeRow(1);
}
}
10 changes: 10 additions & 0 deletions app/code/Magento/Catalog/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@
<argument name="changelog" xsi:type="object" shared="false">Magento\Framework\Mview\View\ChangelogInterface</argument>
</arguments>
</type>
<type name="Magento\Catalog\Model\Indexer\Product\Full">
<arguments>
<argument name="indexerList" xsi:type="array">
<item name="catalog_category_product" xsi:type="const">Magento\Catalog\Model\Indexer\Category\Product::INDEXER_ID</item>
<item name="catalog_product_category" xsi:type="const">Magento\Catalog\Model\Indexer\Product\Category::INDEXER_ID</item>
<item name="catalog_product_price" xsi:type="const">Magento\Catalog\Model\Indexer\Product\Price\Processor::INDEXER_ID</item>
<item name="catalog_product_attribute" xsi:type="const">Magento\Catalog\Model\Indexer\Product\Eav\Processor::INDEXER_ID</item>
</argument>
</arguments>
</type>
<type name="Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool">
<arguments>
<argument name="mediaGalleryEntryConvertersCollection" xsi:type="array">
Expand Down
7 changes: 7 additions & 0 deletions app/code/Magento/CatalogInventory/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@
<type name="Magento\Store\Model\ResourceModel\Group">
<plugin name="storeGroupResourceAroundBeforeSave" type="Magento\CatalogInventory\Model\Indexer\Stock\Plugin\StoreGroup"/>
</type>
<type name="Magento\Catalog\Model\Indexer\Product\Full">
<arguments>
<argument name="indexerList" xsi:type="array">
<item name="cataloginventory_stock" xsi:type="const">Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID</item>
</argument>
</arguments>
</type>
<type name="Magento\Catalog\Block\Product\View">
<plugin name="quantityValidators" type="Magento\CatalogInventory\Block\Plugin\ProductView" />
</type>
Expand Down
7 changes: 7 additions & 0 deletions app/code/Magento/CatalogSearch/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@
</arguments>
</type>
<preference for="Magento\Framework\Indexer\IndexStructureInterface" type="Magento\CatalogSearch\Model\Indexer\IndexStructureProxy" />
<type name="Magento\Catalog\Model\Indexer\Product\Full">
<arguments>
<argument name="indexerList" xsi:type="array">
<item name="catalogsearch_fulltext" xsi:type="const">Magento\CatalogSearch\Model\Indexer\Fulltext::INDEXER_ID</item>
</argument>
</arguments>
</type>
<type name="Magento\Framework\Search\Adapter\Mysql\Aggregation\DataProviderContainer">
<arguments>
<argument name="dataProviders" xsi:type="array">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,34 @@
namespace Magento\ConfigurableProduct\Plugin\Model\ResourceModel;

use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\Indexer\ActionInterface;

class Product
{
/**
* @var Configurable
*/
private $configurable;

/**
* @var ActionInterface
*/
private $productIndexer;

/**
* Initialize Product dependencies.
*
* @param Configurable $configurable
* @param ActionInterface $productIndexer
*/
public function __construct(
Configurable $configurable,
ActionInterface $productIndexer
) {
$this->configurable = $configurable;
$this->productIndexer = $productIndexer;
}

/**
* We need reset attribute set id to attribute after related simple product was saved
*
Expand All @@ -28,4 +53,25 @@ public function beforeSave(
$object->getTypeInstance()->getSetAttributes($object);
}
}

/**
* Gather configurable parent ids of product being deleted and reindex after delete is complete.
*
* @param \Magento\Catalog\Model\ResourceModel\Product $subject
* @param \Closure $proceed
* @param \Magento\Catalog\Model\Product $product
* @return \Magento\Catalog\Model\ResourceModel\Product
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function aroundDelete(
\Magento\Catalog\Model\ResourceModel\Product $subject,
\Closure $proceed,
\Magento\Catalog\Model\Product $product
) {
$configurableProductIds = $this->configurable->getParentIdsByChild($product->getId());
$result = $proceed($product);
$this->productIndexer->executeList($configurableProductIds);

return $result;
}
}
Loading

0 comments on commit e6bbe78

Please sign in to comment.