| Current Path : /home/rtorresani/www/vendor/magento/module-catalog/Model/Indexer/Product/Price/Action/ |
| Current File : //home/rtorresani/www/vendor/magento/module-catalog/Model/Indexer/Product/Price/Action/Full.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);
namespace Magento\Catalog\Model\Indexer\Product\Price\Action;
use Magento\Catalog\Model\Indexer\Product\Price\AbstractAction;
use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory;
use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer;
use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Query\BatchIterator;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\EntityMetadataInterface;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Indexer\BatchProviderInterface;
use Magento\Framework\Indexer\DimensionalIndexerInterface;
use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Indexer\Model\ProcessManager;
use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
use Magento\Store\Model\StoreManagerInterface;
/**
* Class Full reindex action
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Full extends AbstractAction
{
/**
* @var MetadataPool
*/
private $metadataPool;
/**
* @var BatchSizeCalculator
*/
private $batchSizeCalculator;
/**
* @var BatchProviderInterface
*/
private $batchProvider;
/**
* @var ActiveTableSwitcher
*/
private $activeTableSwitcher;
/**
* @var EntityMetadataInterface
*/
private $productMetaDataCached;
/**
* @var DimensionCollectionFactory
*/
private $dimensionCollectionFactory;
/**
* @var TableMaintainer
*/
private $dimensionTableMaintainer;
/**
* @var ProcessManager
*/
private $processManager;
/**
* @var QueryGenerator|null
*/
private $batchQueryGenerator;
/**
* @param ScopeConfigInterface $config
* @param StoreManagerInterface $storeManager
* @param CurrencyFactory $currencyFactory
* @param TimezoneInterface $localeDate
* @param DateTime $dateTime
* @param Type $catalogProductType
* @param Factory $indexerPriceFactory
* @param DefaultPrice $defaultIndexerResource
* @param MetadataPool|null $metadataPool
* @param BatchSizeCalculator|null $batchSizeCalculator
* @param BatchProviderInterface|null $batchProvider
* @param ActiveTableSwitcher|null $activeTableSwitcher
* @param DimensionCollectionFactory|null $dimensionCollectionFactory
* @param TableMaintainer|null $dimensionTableMaintainer
* @param ProcessManager $processManager
* @param QueryGenerator|null $batchQueryGenerator
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
ScopeConfigInterface $config,
StoreManagerInterface $storeManager,
CurrencyFactory $currencyFactory,
TimezoneInterface $localeDate,
DateTime $dateTime,
Type $catalogProductType,
Factory $indexerPriceFactory,
DefaultPrice $defaultIndexerResource,
MetadataPool $metadataPool = null,
BatchSizeCalculator $batchSizeCalculator = null,
BatchProviderInterface $batchProvider = null,
ActiveTableSwitcher $activeTableSwitcher = null,
DimensionCollectionFactory $dimensionCollectionFactory = null,
TableMaintainer $dimensionTableMaintainer = null,
ProcessManager $processManager = null,
QueryGenerator $batchQueryGenerator = null
) {
parent::__construct(
$config,
$storeManager,
$currencyFactory,
$localeDate,
$dateTime,
$catalogProductType,
$indexerPriceFactory,
$defaultIndexerResource
);
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(
MetadataPool::class
);
$this->batchSizeCalculator = $batchSizeCalculator ?: ObjectManager::getInstance()->get(
BatchSizeCalculator::class
);
$this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get(
BatchProviderInterface::class
);
$this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get(
ActiveTableSwitcher::class
);
$this->dimensionCollectionFactory = $dimensionCollectionFactory ?: ObjectManager::getInstance()->get(
DimensionCollectionFactory::class
);
$this->dimensionTableMaintainer = $dimensionTableMaintainer ?: ObjectManager::getInstance()->get(
TableMaintainer::class
);
$this->processManager = $processManager ?: ObjectManager::getInstance()->get(
ProcessManager::class
);
$this->batchQueryGenerator = $batchQueryGenerator ?? ObjectManager::getInstance()->get(QueryGenerator::class);
}
/**
* Execute Full reindex
*
* @param array|int|null $ids
* @return void
* @throws \Exception
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function execute($ids = null): void
{
try {
//Prepare indexer tables before full reindex
$this->prepareTables();
/** @var DefaultPrice $indexer */
foreach ($this->getTypeIndexers(true) as $typeId => $priceIndexer) {
if ($priceIndexer instanceof DimensionalIndexerInterface) {
//New price reindex mechanism
$this->reindexProductTypeWithDimensions($priceIndexer, $typeId);
continue;
}
$priceIndexer->getTableStrategy()->setUseIdxTable(false);
//Old price reindex mechanism
$this->reindexProductType($priceIndexer, $typeId);
}
//Final replacement of tables from replica to main
$this->switchTables();
} catch (\Exception $e) {
throw new LocalizedException(__($e->getMessage()), $e);
}
}
/**
* Prepare indexer tables before full reindex
*
* @return void
* @throws \Exception
*/
private function prepareTables(): void
{
$this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false);
$this->_prepareWebsiteDateTable();
$this->truncateReplicaTables();
}
/**
* Truncate replica tables by dimensions
*
* @return void
* @throws \Exception
*/
private function truncateReplicaTables(): void
{
foreach ($this->dimensionCollectionFactory->create() as $dimension) {
$dimensionTable = $this->dimensionTableMaintainer->getMainReplicaTable($dimension);
$this->_defaultIndexerResource->getConnection()->truncateTable($dimensionTable);
}
}
/**
* Reindex new 'Dimensional' price indexer by product type
*
* @param DimensionalIndexerInterface $priceIndexer
* @param string $typeId
*
* @return void
* @throws \Exception
*/
private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId): void
{
$userFunctions = [];
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$userFunctions[] = function () use ($priceIndexer, $dimensions, $typeId) {
$this->reindexByBatches($priceIndexer, $dimensions, $typeId);
};
}
$this->processManager->execute($userFunctions);
}
/**
* Reindex new 'Dimensional' price indexer by batches
*
* @param DimensionalIndexerInterface $priceIndexer
* @param array $dimensions
* @param string $typeId
*
* @return void
* @throws \Exception
*/
private function reindexByBatches(
DimensionalIndexerInterface $priceIndexer,
array $dimensions,
string $typeId
): void {
foreach ($this->getBatchesForIndexer($typeId) as $batch) {
$this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions);
}
}
/**
* Get batches for new 'Dimensional' price indexer
*
* @param string $typeId
*
* @return BatchIterator
* @throws \Exception
*/
private function getBatchesForIndexer(string $typeId): BatchIterator
{
$connection = $this->_defaultIndexerResource->getConnection();
$entityMetadata = $this->getProductMetaData();
$select = $connection->select();
$select->distinct(true);
$select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
$select->where('type_id = ?', $typeId);
return $this->batchQueryGenerator->generate(
$this->getProductMetaData()->getIdentifierField(),
$select,
$this->batchSizeCalculator->estimateBatchSize(
$connection,
$typeId
)
);
}
/**
* Reindex by batch for new 'Dimensional' price indexer
*
* @param DimensionalIndexerInterface $priceIndexer
* @param Select $batchQuery
* @param array $dimensions
*
* @return void
* @throws \Exception
*/
private function reindexByBatchWithDimensions(
DimensionalIndexerInterface $priceIndexer,
Select $batchQuery,
array $dimensions
): void {
$entityIds = $this->getEntityIdsFromBatch($batchQuery);
if (!empty($entityIds)) {
$this->dimensionTableMaintainer->createMainTmpTable($dimensions);
$temporaryTable = $this->dimensionTableMaintainer->getMainTmpTable($dimensions);
$this->_emptyTable($temporaryTable);
$priceIndexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false));
// Sync data from temp table to index table
$this->_insertFromTable(
$temporaryTable,
$this->dimensionTableMaintainer->getMainReplicaTable($dimensions)
);
}
}
/**
* Reindex old price indexer by product type
*
* @param PriceInterface $priceIndexer
* @param string $typeId
*
* @return void
* @throws \Exception
*/
private function reindexProductType(PriceInterface $priceIndexer, string $typeId): void
{
foreach ($this->getBatchesForIndexer($typeId) as $batch) {
$this->reindexBatch($priceIndexer, $batch);
}
}
/**
* Reindex by batch for old price indexer
*
* @param PriceInterface $priceIndexer
* @param Select $batch
* @return void
* @throws \Exception
*/
private function reindexBatch(PriceInterface $priceIndexer, Select $batch): void
{
$entityIds = $this->getEntityIdsFromBatch($batch);
if (!empty($entityIds)) {
// Temporary table will created if not exists
$idxTableName = $this->_defaultIndexerResource->getIdxTable();
$this->_emptyTable($idxTableName);
if ($priceIndexer->getIsComposite()) {
$this->_copyRelationIndexData($entityIds);
}
// Reindex entities by id
$priceIndexer->reindexEntity($entityIds);
// Sync data from temp table to index table
$this->_insertFromTable($idxTableName, $this->getReplicaTable());
// Drop temporary index table
$this->_defaultIndexerResource->getConnection()->dropTable($idxTableName);
}
}
/**
* Get Entity Ids from batch
*
* @param Select $batch
* @return array
* @throws \Exception
*/
private function getEntityIdsFromBatch(Select $batch): array
{
$connection = $this->_defaultIndexerResource->getConnection();
return $connection->fetchCol($batch);
}
/**
* Get product meta data
*
* @return EntityMetadataInterface
* @throws \Exception
*/
private function getProductMetaData(): EntityMetadataInterface
{
if ($this->productMetaDataCached === null) {
$this->productMetaDataCached = $this->metadataPool->getMetadata(ProductInterface::class);
}
return $this->productMetaDataCached;
}
/**
* Get replica table
*
* @return string
* @throws \Exception
*/
private function getReplicaTable(): string
{
return $this->activeTableSwitcher->getAdditionalTableName(
$this->_defaultIndexerResource->getMainTable()
);
}
/**
* Replacement of tables from replica to main
*
* @return void
* @throws \Zend_Db_Statement_Exception
*/
private function switchTables(): void
{
// Switch dimension tables
$mainTablesByDimension = [];
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$mainTablesByDimension[] = $this->dimensionTableMaintainer->getMainTableByDimensions($dimensions);
//Move data from indexers with old realisation
$this->moveDataFromReplicaTableToReplicaTables($dimensions);
}
if (count($mainTablesByDimension) > 0) {
$this->activeTableSwitcher->switchTable(
$this->_defaultIndexerResource->getConnection(),
$mainTablesByDimension
);
}
}
/**
* Move data from old price indexer mechanism to new indexer mechanism by dimensions.
*
* Used only for backward compatibility
*
* @param array $dimensions
* @return void
* @throws \Zend_Db_Statement_Exception
*/
private function moveDataFromReplicaTableToReplicaTables(array $dimensions): void
{
if (!$dimensions) {
return;
}
$select = $this->dimensionTableMaintainer->getConnection()->select()->from(
$this->dimensionTableMaintainer->getMainReplicaTable([])
);
$check = clone $select;
$check->reset('columns')->columns('count(*)');
if (!$this->dimensionTableMaintainer->getConnection()->query($check)->fetchColumn()) {
return;
}
$replicaTablesByDimension = $this->dimensionTableMaintainer->getMainReplicaTable($dimensions);
foreach ($dimensions as $dimension) {
if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) {
$select->where('website_id = ?', $dimension->getValue());
}
if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) {
$select->where('customer_group_id = ?', $dimension->getValue());
}
}
$this->dimensionTableMaintainer->getConnection()->query(
$this->dimensionTableMaintainer->getConnection()->insertFromSelect(
$select,
$replicaTablesByDimension,
[],
AdapterInterface::INSERT_ON_DUPLICATE
)
);
}
/**
* Retrieves the index table that should be used
*
* @deprecated 102.0.6
*/
protected function getIndexTargetTable(): string
{
return $this->activeTableSwitcher->getAdditionalTableName($this->_defaultIndexerResource->getMainTable());
}
}