diff --git a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php index e7517ba0c8818..ad286bcd1f81e 100644 --- a/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php +++ b/app/code/Magento/Indexer/Console/Command/IndexerReindexCommand.php @@ -6,6 +6,7 @@ namespace Magento\Indexer\Console\Command; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ObjectManagerFactory; use Magento\Framework\Console\Cli; use Magento\Framework\Exception\LocalizedException; @@ -14,11 +15,13 @@ use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Indexer\StateInterface; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Command to run indexers + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class IndexerReindexCommand extends AbstractIndexerManageCommand { @@ -42,18 +45,26 @@ class IndexerReindexCommand extends AbstractIndexerManageCommand */ private $dependencyInfoProvider; + /** + * @var MakeSharedIndexValid|null + */ + private $makeSharedValid; + /** * @param ObjectManagerFactory $objectManagerFactory * @param IndexerRegistry|null $indexerRegistry * @param DependencyInfoProvider|null $dependencyInfoProvider + * @param MakeSharedIndexValid|null $makeSharedValid */ public function __construct( ObjectManagerFactory $objectManagerFactory, IndexerRegistry $indexerRegistry = null, - DependencyInfoProvider $dependencyInfoProvider = null + DependencyInfoProvider $dependencyInfoProvider = null, + MakeSharedIndexValid $makeSharedValid = null ) { $this->indexerRegistry = $indexerRegistry; $this->dependencyInfoProvider = $dependencyInfoProvider; + $this->makeSharedValid = $makeSharedValid; parent::__construct($objectManagerFactory); } @@ -88,8 +99,8 @@ protected function execute(InputInterface $input, OutputInterface $output) // Skip indexers having shared index that was already complete if (!in_array($sharedIndex, $this->sharedIndexesComplete)) { $indexer->reindexAll(); - if ($sharedIndex) { - $this->validateSharedIndex($sharedIndex); + if (!empty($sharedIndex) && $this->getMakeSharedValid()->execute($sharedIndex)) { + $this->sharedIndexesComplete[] = $sharedIndex; } } $resultTime = microtime(true) - $startTime; @@ -215,54 +226,6 @@ private function validateIndexerStatus(IndexerInterface $indexer) } } - /** - * Get indexer ids that have common shared index - * - * @param string $sharedIndex - * @return array - */ - private function getIndexerIdsBySharedIndex($sharedIndex) - { - $indexers = $this->getConfig()->getIndexers(); - $result = []; - foreach ($indexers as $indexerConfig) { - if ($indexerConfig['shared_index'] == $sharedIndex) { - $result[] = $indexerConfig['indexer_id']; - } - } - return $result; - } - - /** - * Validate indexers by shared index ID - * - * @param string $sharedIndex - * @return $this - */ - private function validateSharedIndex($sharedIndex) - { - if (empty($sharedIndex)) { - throw new \InvalidArgumentException( - 'The sharedIndex is an invalid shared index identifier. Verify the identifier and try again.' - ); - } - $indexerIds = $this->getIndexerIdsBySharedIndex($sharedIndex); - if (empty($indexerIds)) { - return $this; - } - foreach ($indexerIds as $indexerId) { - $indexer = $this->getIndexerRegistry()->get($indexerId); - /** @var \Magento\Indexer\Model\Indexer\State $state */ - $state = $indexer->getState(); - $state->setStatus(StateInterface::STATUS_WORKING); - $state->save(); - $state->setStatus(StateInterface::STATUS_VALID); - $state->save(); - } - $this->sharedIndexesComplete[] = $sharedIndex; - return $this; - } - /** * Get config * @@ -278,30 +241,30 @@ private function getConfig() } /** - * Get indexer registry + * Get dependency info provider * - * @return IndexerRegistry + * @return DependencyInfoProvider * @deprecated 100.2.0 */ - private function getIndexerRegistry() + private function getDependencyInfoProvider() { - if (!$this->indexerRegistry) { - $this->indexerRegistry = $this->getObjectManager()->get(IndexerRegistry::class); + if (!$this->dependencyInfoProvider) { + $this->dependencyInfoProvider = $this->getObjectManager()->get(DependencyInfoProvider::class); } - return $this->indexerRegistry; + return $this->dependencyInfoProvider; } /** - * Get dependency info provider + * Get MakeSharedIndexValid processor. * - * @return DependencyInfoProvider - * @deprecated 100.2.0 + * @return MakeSharedIndexValid */ - private function getDependencyInfoProvider() + private function getMakeSharedValid(): MakeSharedIndexValid { - if (!$this->dependencyInfoProvider) { - $this->dependencyInfoProvider = $this->getObjectManager()->get(DependencyInfoProvider::class); + if (!$this->makeSharedValid) { + $this->makeSharedValid = $this->getObjectManager()->get(MakeSharedIndexValid::class); } - return $this->dependencyInfoProvider; + + return $this->makeSharedValid; } } diff --git a/app/code/Magento/Indexer/Model/Processor.php b/app/code/Magento/Indexer/Model/Processor.php index 534ea805bb8fc..78b8fa070b155 100644 --- a/app/code/Magento/Indexer/Model/Processor.php +++ b/app/code/Magento/Indexer/Model/Processor.php @@ -5,16 +5,23 @@ */ namespace Magento\Indexer\Model; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Indexer\ConfigInterface; use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerInterfaceFactory; -use Magento\Framework\Indexer\StateInterface; +use Magento\Framework\Mview\ProcessorInterface; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; /** * Indexer processor */ class Processor { + /** + * @var array + */ + private $sharedIndexesComplete = []; + /** * @var ConfigInterface */ @@ -31,26 +38,34 @@ class Processor protected $indexersFactory; /** - * @var \Magento\Framework\Mview\ProcessorInterface + * @var ProcessorInterface */ protected $mviewProcessor; + /** + * @var MakeSharedIndexValid + */ + protected $makeSharedValid; + /** * @param ConfigInterface $config * @param IndexerInterfaceFactory $indexerFactory * @param Indexer\CollectionFactory $indexersFactory - * @param \Magento\Framework\Mview\ProcessorInterface $mviewProcessor + * @param ProcessorInterface $mviewProcessor + * @param MakeSharedIndexValid|null $makeSharedValid */ public function __construct( ConfigInterface $config, IndexerInterfaceFactory $indexerFactory, Indexer\CollectionFactory $indexersFactory, - \Magento\Framework\Mview\ProcessorInterface $mviewProcessor + ProcessorInterface $mviewProcessor, + MakeSharedIndexValid $makeSharedValid = null ) { $this->config = $config; $this->indexerFactory = $indexerFactory; $this->indexersFactory = $indexersFactory; $this->mviewProcessor = $mviewProcessor; + $this->makeSharedValid = $makeSharedValid ?: ObjectManager::getInstance()->get(MakeSharedIndexValid::class); } /** @@ -60,27 +75,21 @@ public function __construct( */ public function reindexAllInvalid() { - $sharedIndexesComplete = []; foreach (array_keys($this->config->getIndexers()) as $indexerId) { /** @var Indexer $indexer */ $indexer = $this->indexerFactory->create(); $indexer->load($indexerId); $indexerConfig = $this->config->getIndexer($indexerId); + if ($indexer->isInvalid()) { // Skip indexers having shared index that was already complete $sharedIndex = $indexerConfig['shared_index'] ?? null; - if (!in_array($sharedIndex, $sharedIndexesComplete)) { + if (!in_array($sharedIndex, $this->sharedIndexesComplete)) { $indexer->reindexAll(); - } else { - /** @var \Magento\Indexer\Model\Indexer\State $state */ - $state = $indexer->getState(); - $state->setStatus(StateInterface::STATUS_WORKING); - $state->save(); - $state->setStatus(StateInterface::STATUS_VALID); - $state->save(); - } - if ($sharedIndex) { - $sharedIndexesComplete[] = $sharedIndex; + + if (!empty($sharedIndex) && $this->makeSharedValid->execute($sharedIndex)) { + $this->sharedIndexesComplete[] = $sharedIndex; + } } } } diff --git a/app/code/Magento/Indexer/Model/Processor/MakeSharedIndexValid.php b/app/code/Magento/Indexer/Model/Processor/MakeSharedIndexValid.php new file mode 100644 index 0000000000000..338891589bf33 --- /dev/null +++ b/app/code/Magento/Indexer/Model/Processor/MakeSharedIndexValid.php @@ -0,0 +1,95 @@ +config = $config; + $this->indexerRegistry = $indexerRegistry; + } + + /** + * Validate indexers by shared index ID + * + * @param string $sharedIndex + * @return bool + * @throws \Exception + */ + public function execute(string $sharedIndex): bool + { + if (empty($sharedIndex)) { + throw new \InvalidArgumentException( + "The '{$sharedIndex}' is an invalid shared index identifier. Verify the identifier and try again.", + ); + } + + $indexerIds = $this->getIndexerIdsBySharedIndex($sharedIndex); + if (empty($indexerIds)) { + return false; + } + + foreach ($indexerIds as $indexerId) { + $indexer = $this->indexerRegistry->get($indexerId); + /** @var State $state */ + $state = $indexer->getState(); + $state->setStatus(StateInterface::STATUS_WORKING); + $state->save(); + $state->setStatus(StateInterface::STATUS_VALID); + $state->save(); + } + + return true; + } + + /** + * Get indexer ids that have common shared index + * + * @param string $sharedIndex + * @return array + */ + private function getIndexerIdsBySharedIndex(string $sharedIndex): array + { + $indexers = $this->config->getIndexers(); + + $result = []; + foreach ($indexers as $indexerConfig) { + if ($indexerConfig['shared_index'] == $sharedIndex) { + $result[] = $indexerConfig['indexer_id']; + } + } + + return $result; + } +} diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php index 6d96841bc3dab..8bdceb92b247b 100644 --- a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerReindexCommandTest.php @@ -18,6 +18,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Indexer\Console\Command\IndexerReindexCommand; use Magento\Indexer\Model\Config; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Console\Tester\CommandTester; @@ -49,6 +50,11 @@ class IndexerReindexCommandTest extends AbstractIndexerCommandCommonSetup */ private $dependencyInfoProviderMock; + /** + * @var MakeSharedIndexValid|MockObject + */ + private $makeSharedValidMock; + /** * @var ObjectManagerHelper */ @@ -64,12 +70,12 @@ protected function setUp(): void $this->indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) ->disableOriginalConstructor() ->getMock(); - $this->dependencyInfoProviderMock = $this->objectManagerHelper->getObject( - DependencyInfoProvider::class, - [ - 'config' => $this->configMock, - ] - ); + $this->makeSharedValidMock = $this->getMockBuilder(MakeSharedIndexValid::class) + ->disableOriginalConstructor() + ->getMock(); + $this->dependencyInfoProviderMock = $this->objectManagerHelper->getObject(DependencyInfoProvider::class, [ + 'config' => $this->configMock, + ]); parent::setUp(); } @@ -174,11 +180,17 @@ public function testExecuteWithIndex( $emptyIndexer->method('getState') ->willReturn($this->getStateMock(['setStatus', 'save'])); + $this->makeSharedValidMock = $this->objectManagerHelper->getObject(MakeSharedIndexValid::class, [ + 'config' => $this->configMock, + 'indexerRegistry' => $this->indexerRegistryMock + ]); $this->configureAdminArea(); $this->command = new IndexerReindexCommand( $this->objectManagerFactory, - $this->indexerRegistryMock + $this->indexerRegistryMock, + $this->dependencyInfoProviderMock, + $this->makeSharedValidMock ); $commandTester = new CommandTester($this->command); diff --git a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php index 7a06fb745ba89..9f9b4c2157bb7 100644 --- a/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Model/ProcessorTest.php @@ -9,6 +9,7 @@ use Magento\Framework\Indexer\ConfigInterface; use Magento\Framework\Indexer\IndexerInterfaceFactory; +use Magento\Framework\Indexer\IndexerRegistry; use Magento\Framework\Indexer\StateInterface; use Magento\Framework\Mview\ProcessorInterface; use Magento\Indexer\Model\Indexer; @@ -16,6 +17,7 @@ use Magento\Indexer\Model\Indexer\CollectionFactory; use Magento\Indexer\Model\Indexer\State; use Magento\Indexer\Model\Processor; +use Magento\Indexer\Model\Processor\MakeSharedIndexValid; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -79,7 +81,10 @@ protected function setUp(): void ); } - public function testReindexAllInvalid() + /** + * @return void + */ + public function testReindexAllInvalid(): void { $indexers = ['indexer1' => [], 'indexer2' => []]; @@ -121,7 +126,68 @@ public function testReindexAllInvalid() $this->model->reindexAllInvalid(); } - public function testReindexAll() + /** + * @dataProvider sharedIndexDataProvider + * @param array $indexers + * @param array $indexerStates + * @param array $expectedReindexAllCalls + * @param array $executedSharedIndexers + */ + public function testReindexAllInvalidWithSharedIndex( + array $indexers, + array $indexerStates, + array $expectedReindexAllCalls, + array $executedSharedIndexers + ): void { + $this->configMock->expects($this->any())->method('getIndexers')->willReturn($indexers); + $this->configMock + ->method('getIndexer') + ->willReturnMap( + array_map( + function ($elem) { + return [$elem['indexer_id'], $elem]; + }, + $indexers + ) + ); + $indexerMocks = []; + foreach ($indexers as $indexerData) { + $stateMock = $this->createPartialMock(State::class, ['getStatus', '__wakeup']); + $stateMock->expects($this->any()) + ->method('getStatus') + ->willReturn($indexerStates[$indexerData['indexer_id']]); + $indexerMock = $this->createPartialMock(Indexer::class, ['load', 'getState', 'reindexAll']); + $indexerMock->expects($this->any())->method('getState')->willReturn($stateMock); + $indexerMock->expects($expectedReindexAllCalls[$indexerData['indexer_id']])->method('reindexAll'); + + $this->indexerFactoryMock->expects($this->at(count($indexerMocks))) + ->method('create') + ->willReturn($indexerMock); + + $indexerMocks[] = $indexerMock; + } + $indexerRegistryMock = $this->getIndexRegistryMock($executedSharedIndexers); + + $makeSharedValidMock = new MakeSharedIndexValid( + $this->configMock, + $indexerRegistryMock + ); + $model = new Processor( + $this->configMock, + $this->indexerFactoryMock, + $this->indexersFactoryMock, + $this->viewProcessorMock, + $makeSharedValidMock + ); + $model->reindexAllInvalid(); + } + + /** + * Reindex all test + * + * return void + */ + public function testReindexAll(): void { $indexerMock = $this->createMock(Indexer::class); $indexerMock->expects($this->exactly(2))->method('reindexAll'); @@ -134,15 +200,136 @@ public function testReindexAll() $this->model->reindexAll(); } + /** + * Update mview test + * + * @return void + */ public function testUpdateMview() { $this->viewProcessorMock->expects($this->once())->method('update')->with('indexer')->willReturnSelf(); $this->model->updateMview(); } + /** + * Clear change log test + * + * @return void + */ public function testClearChangelog() { $this->viewProcessorMock->expects($this->once())->method('clearChangelog')->with('indexer')->willReturnSelf(); $this->model->clearChangelog(); } + + /** + * @return array + */ + public function sharedIndexDataProvider() + { + return [ + 'Without dependencies' => [ + 'indexers' => [ + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexer_1', + 'shared_index' => null, + 'dependencies' => [], + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexer_2', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexer_3', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + ], + 'indexer_states' => [ + 'indexer_1' => StateInterface::STATUS_INVALID, + 'indexer_2' => StateInterface::STATUS_VALID, + 'indexer_3' => StateInterface::STATUS_VALID, + ], + 'expected_reindex_all_calls' => [ + 'indexer_1' => $this->once(), + 'indexer_2' => $this->never(), + 'indexer_3' => $this->never(), + ], + 'executed_shared_indexers' => [], + ], + 'With dependencies and some indexers is invalid' => [ + 'indexers' => [ + 'indexer_1' => [ + 'indexer_id' => 'indexer_1', + 'title' => 'Title_indexer_1', + 'shared_index' => null, + 'dependencies' => ['indexer_2', 'indexer_3'], + ], + 'indexer_2' => [ + 'indexer_id' => 'indexer_2', + 'title' => 'Title_indexer_2', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_3' => [ + 'indexer_id' => 'indexer_3', + 'title' => 'Title_indexer_3', + 'shared_index' => 'with_indexer_3', + 'dependencies' => [], + ], + 'indexer_4' => [ + 'indexer_id' => 'indexer_4', + 'title' => 'Title_indexer_4', + 'shared_index' => null, + 'dependencies' => ['indexer_1'], + ], + ], + 'indexer_states' => [ + 'indexer_1' => StateInterface::STATUS_INVALID, + 'indexer_2' => StateInterface::STATUS_VALID, + 'indexer_3' => StateInterface::STATUS_INVALID, + 'indexer_4' => StateInterface::STATUS_VALID, + ], + 'expected_reindex_all_calls' => [ + 'indexer_1' => $this->once(), + 'indexer_2' => $this->never(), + 'indexer_3' => $this->once(), + 'indexer_4' => $this->never(), + ], + 'executed_shared_indexers' => [['indexer_2'], ['indexer_3']], + ], + ]; + } + + /** + * @param array $executedSharedIndexers + * @return IndexerRegistry|MockObject + */ + private function getIndexRegistryMock(array $executedSharedIndexers) + { + /** @var MockObject|IndexerRegistry $indexerRegistryMock */ + $indexerRegistryMock = $this->getMockBuilder(IndexerRegistry::class) + ->disableOriginalConstructor() + ->getMock(); + $emptyIndexer = $this->createPartialMock(Indexer::class, ['load', 'getState', 'reindexAll']); + /** @var MockObject|StateInterface $state */ + $state = $this->getMockBuilder(StateInterface::class) + ->setMethods(['setStatus', 'save']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $state->method('getStatus') + ->willReturn(StateInterface::STATUS_INVALID); + $emptyIndexer->method('getState')->willReturn($state); + $indexerRegistryMock + ->expects($this->exactly(count($executedSharedIndexers))) + ->method('get') + ->withConsecutive(...$executedSharedIndexers) + ->willReturn($emptyIndexer); + + return $indexerRegistryMock; + } }