IndexBuilder.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\CatalogRule\Model\Indexer;
  7. use Magento\Catalog\Model\Product;
  8. use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory as RuleCollectionFactory;
  9. use Magento\CatalogRule\Model\Rule;
  10. use Magento\Framework\App\ObjectManager;
  11. use Magento\Framework\Pricing\PriceCurrencyInterface;
  12. use Magento\CatalogRule\Model\Indexer\IndexBuilder\ProductLoader;
  13. use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
  14. /**
  15. * @api
  16. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  17. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  18. * @SuppressWarnings(PHPMD.TooManyFields)
  19. * @since 100.0.2
  20. */
  21. class IndexBuilder
  22. {
  23. const SECONDS_IN_DAY = 86400;
  24. /**
  25. * @var \Magento\Framework\EntityManager\MetadataPool
  26. * @deprecated 101.0.0
  27. * @since 100.1.0
  28. */
  29. protected $metadataPool;
  30. /**
  31. * CatalogRuleGroupWebsite columns list
  32. *
  33. * This array contain list of CatalogRuleGroupWebsite table columns
  34. *
  35. * @var array
  36. * @deprecated 101.0.0
  37. */
  38. protected $_catalogRuleGroupWebsiteColumnsList = ['rule_id', 'customer_group_id', 'website_id'];
  39. /**
  40. * @var \Magento\Framework\App\ResourceConnection
  41. */
  42. protected $resource;
  43. /**
  44. * @var \Magento\Store\Model\StoreManagerInterface
  45. */
  46. protected $storeManager;
  47. /**
  48. * @var RuleCollectionFactory
  49. */
  50. protected $ruleCollectionFactory;
  51. /**
  52. * @var \Psr\Log\LoggerInterface
  53. */
  54. protected $logger;
  55. /**
  56. * @var PriceCurrencyInterface
  57. */
  58. protected $priceCurrency;
  59. /**
  60. * @var \Magento\Eav\Model\Config
  61. */
  62. protected $eavConfig;
  63. /**
  64. * @var \Magento\Framework\Stdlib\DateTime
  65. */
  66. protected $dateFormat;
  67. /**
  68. * @var \Magento\Framework\Stdlib\DateTime\DateTime
  69. */
  70. protected $dateTime;
  71. /**
  72. * @var \Magento\Catalog\Model\ProductFactory
  73. */
  74. protected $productFactory;
  75. /**
  76. * @var Product[]
  77. */
  78. protected $loadedProducts;
  79. /**
  80. * @var int
  81. */
  82. protected $batchCount;
  83. /**
  84. * @var \Magento\Framework\DB\Adapter\AdapterInterface
  85. */
  86. protected $connection;
  87. /**
  88. * @var ProductPriceCalculator
  89. */
  90. private $productPriceCalculator;
  91. /**
  92. * @var ReindexRuleProduct
  93. */
  94. private $reindexRuleProduct;
  95. /**
  96. * @var ReindexRuleGroupWebsite
  97. */
  98. private $reindexRuleGroupWebsite;
  99. /**
  100. * @var RuleProductsSelectBuilder
  101. */
  102. private $ruleProductsSelectBuilder;
  103. /**
  104. * @var ReindexRuleProductPrice
  105. */
  106. private $reindexRuleProductPrice;
  107. /**
  108. * @var RuleProductPricesPersistor
  109. */
  110. private $pricesPersistor;
  111. /**
  112. * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher
  113. */
  114. private $activeTableSwitcher;
  115. /**
  116. * @var TableSwapper
  117. */
  118. private $tableSwapper;
  119. /**
  120. * @var ProductLoader
  121. */
  122. private $productLoader;
  123. /**
  124. * @param RuleCollectionFactory $ruleCollectionFactory
  125. * @param PriceCurrencyInterface $priceCurrency
  126. * @param \Magento\Framework\App\ResourceConnection $resource
  127. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  128. * @param \Psr\Log\LoggerInterface $logger
  129. * @param \Magento\Eav\Model\Config $eavConfig
  130. * @param \Magento\Framework\Stdlib\DateTime $dateFormat
  131. * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime
  132. * @param \Magento\Catalog\Model\ProductFactory $productFactory
  133. * @param int $batchCount
  134. * @param ProductPriceCalculator|null $productPriceCalculator
  135. * @param ReindexRuleProduct|null $reindexRuleProduct
  136. * @param ReindexRuleGroupWebsite|null $reindexRuleGroupWebsite
  137. * @param RuleProductsSelectBuilder|null $ruleProductsSelectBuilder
  138. * @param ReindexRuleProductPrice|null $reindexRuleProductPrice
  139. * @param RuleProductPricesPersistor|null $pricesPersistor
  140. * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher
  141. * @param ProductLoader|null $productLoader
  142. * @param TableSwapper|null $tableSwapper
  143. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  144. */
  145. public function __construct(
  146. RuleCollectionFactory $ruleCollectionFactory,
  147. PriceCurrencyInterface $priceCurrency,
  148. \Magento\Framework\App\ResourceConnection $resource,
  149. \Magento\Store\Model\StoreManagerInterface $storeManager,
  150. \Psr\Log\LoggerInterface $logger,
  151. \Magento\Eav\Model\Config $eavConfig,
  152. \Magento\Framework\Stdlib\DateTime $dateFormat,
  153. \Magento\Framework\Stdlib\DateTime\DateTime $dateTime,
  154. \Magento\Catalog\Model\ProductFactory $productFactory,
  155. $batchCount = 1000,
  156. ProductPriceCalculator $productPriceCalculator = null,
  157. ReindexRuleProduct $reindexRuleProduct = null,
  158. ReindexRuleGroupWebsite $reindexRuleGroupWebsite = null,
  159. RuleProductsSelectBuilder $ruleProductsSelectBuilder = null,
  160. ReindexRuleProductPrice $reindexRuleProductPrice = null,
  161. RuleProductPricesPersistor $pricesPersistor = null,
  162. \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null,
  163. ProductLoader $productLoader = null,
  164. TableSwapper $tableSwapper = null
  165. ) {
  166. $this->resource = $resource;
  167. $this->connection = $resource->getConnection();
  168. $this->storeManager = $storeManager;
  169. $this->ruleCollectionFactory = $ruleCollectionFactory;
  170. $this->logger = $logger;
  171. $this->priceCurrency = $priceCurrency;
  172. $this->eavConfig = $eavConfig;
  173. $this->dateFormat = $dateFormat;
  174. $this->dateTime = $dateTime;
  175. $this->productFactory = $productFactory;
  176. $this->batchCount = $batchCount;
  177. $this->productPriceCalculator = $productPriceCalculator ?? ObjectManager::getInstance()->get(
  178. ProductPriceCalculator::class
  179. );
  180. $this->reindexRuleProduct = $reindexRuleProduct ?? ObjectManager::getInstance()->get(
  181. ReindexRuleProduct::class
  182. );
  183. $this->reindexRuleGroupWebsite = $reindexRuleGroupWebsite ?? ObjectManager::getInstance()->get(
  184. ReindexRuleGroupWebsite::class
  185. );
  186. $this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder ?? ObjectManager::getInstance()->get(
  187. RuleProductsSelectBuilder::class
  188. );
  189. $this->reindexRuleProductPrice = $reindexRuleProductPrice ?? ObjectManager::getInstance()->get(
  190. ReindexRuleProductPrice::class
  191. );
  192. $this->pricesPersistor = $pricesPersistor ?? ObjectManager::getInstance()->get(
  193. RuleProductPricesPersistor::class
  194. );
  195. $this->activeTableSwitcher = $activeTableSwitcher ?? ObjectManager::getInstance()->get(
  196. \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class
  197. );
  198. $this->productLoader = $productLoader ?? ObjectManager::getInstance()->get(
  199. ProductLoader::class
  200. );
  201. $this->tableSwapper = $tableSwapper ??
  202. ObjectManager::getInstance()->get(TableSwapper::class);
  203. }
  204. /**
  205. * Reindex by id
  206. *
  207. * @param int $id
  208. * @return void
  209. * @api
  210. */
  211. public function reindexById($id)
  212. {
  213. $this->reindexByIds([$id]);
  214. }
  215. /**
  216. * Reindex by ids
  217. *
  218. * @param array $ids
  219. * @throws \Magento\Framework\Exception\LocalizedException
  220. * @return void
  221. * @api
  222. */
  223. public function reindexByIds(array $ids)
  224. {
  225. try {
  226. $this->doReindexByIds($ids);
  227. } catch (\Exception $e) {
  228. $this->critical($e);
  229. throw new \Magento\Framework\Exception\LocalizedException(
  230. __("Catalog rule indexing failed. See details in exception log.")
  231. );
  232. }
  233. }
  234. /**
  235. * Reindex by ids. Template method
  236. *
  237. * @param array $ids
  238. * @return void
  239. */
  240. protected function doReindexByIds($ids)
  241. {
  242. $this->cleanByIds($ids);
  243. $products = $this->productLoader->getProducts($ids);
  244. foreach ($this->getActiveRules() as $rule) {
  245. foreach ($products as $product) {
  246. $this->applyRule($rule, $product);
  247. }
  248. }
  249. }
  250. /**
  251. * Full reindex
  252. *
  253. * @throws \Magento\Framework\Exception\LocalizedException
  254. * @return void
  255. * @api
  256. */
  257. public function reindexFull()
  258. {
  259. try {
  260. $this->doReindexFull();
  261. } catch (\Exception $e) {
  262. $this->critical($e);
  263. throw new \Magento\Framework\Exception\LocalizedException(
  264. __("Catalog rule indexing failed. See details in exception log.")
  265. );
  266. }
  267. }
  268. /**
  269. * Full reindex Template method
  270. *
  271. * @return void
  272. */
  273. protected function doReindexFull()
  274. {
  275. foreach ($this->getAllRules() as $rule) {
  276. $this->reindexRuleProduct->execute($rule, $this->batchCount, true);
  277. }
  278. $this->reindexRuleProductPrice->execute($this->batchCount, null, true);
  279. $this->reindexRuleGroupWebsite->execute(true);
  280. $this->tableSwapper->swapIndexTables(
  281. [
  282. $this->getTable('catalogrule_product'),
  283. $this->getTable('catalogrule_product_price'),
  284. $this->getTable('catalogrule_group_website')
  285. ]
  286. );
  287. }
  288. /**
  289. * Clean by product ids
  290. *
  291. * @param array $productIds
  292. * @return void
  293. */
  294. protected function cleanByIds($productIds)
  295. {
  296. $query = $this->connection->deleteFromSelect(
  297. $this->connection
  298. ->select()
  299. ->from($this->resource->getTableName('catalogrule_product'), 'product_id')
  300. ->distinct()
  301. ->where('product_id IN (?)', $productIds),
  302. $this->resource->getTableName('catalogrule_product')
  303. );
  304. $this->connection->query($query);
  305. $query = $this->connection->deleteFromSelect(
  306. $this->connection->select()
  307. ->from($this->resource->getTableName('catalogrule_product_price'), 'product_id')
  308. ->distinct()
  309. ->where('product_id IN (?)', $productIds),
  310. $this->resource->getTableName('catalogrule_product_price')
  311. );
  312. $this->connection->query($query);
  313. }
  314. /**
  315. * @param Rule $rule
  316. * @param Product $product
  317. * @return $this
  318. * @throws \Exception
  319. * @SuppressWarnings(PHPMD.NPathComplexity)
  320. */
  321. protected function applyRule(Rule $rule, $product)
  322. {
  323. $ruleId = $rule->getId();
  324. $productEntityId = $product->getId();
  325. $websiteIds = array_intersect($product->getWebsiteIds(), $rule->getWebsiteIds());
  326. if (!$rule->validate($product)) {
  327. return $this;
  328. }
  329. $this->connection->delete(
  330. $this->resource->getTableName('catalogrule_product'),
  331. [
  332. $this->connection->quoteInto('rule_id = ?', $ruleId),
  333. $this->connection->quoteInto('product_id = ?', $productEntityId)
  334. ]
  335. );
  336. $customerGroupIds = $rule->getCustomerGroupIds();
  337. $fromTime = strtotime($rule->getFromDate());
  338. $toTime = strtotime($rule->getToDate());
  339. $toTime = $toTime ? $toTime + self::SECONDS_IN_DAY - 1 : 0;
  340. $sortOrder = (int)$rule->getSortOrder();
  341. $actionOperator = $rule->getSimpleAction();
  342. $actionAmount = $rule->getDiscountAmount();
  343. $actionStop = $rule->getStopRulesProcessing();
  344. $rows = [];
  345. try {
  346. foreach ($websiteIds as $websiteId) {
  347. foreach ($customerGroupIds as $customerGroupId) {
  348. $rows[] = [
  349. 'rule_id' => $ruleId,
  350. 'from_time' => $fromTime,
  351. 'to_time' => $toTime,
  352. 'website_id' => $websiteId,
  353. 'customer_group_id' => $customerGroupId,
  354. 'product_id' => $productEntityId,
  355. 'action_operator' => $actionOperator,
  356. 'action_amount' => $actionAmount,
  357. 'action_stop' => $actionStop,
  358. 'sort_order' => $sortOrder,
  359. ];
  360. if (count($rows) == $this->batchCount) {
  361. $this->connection->insertMultiple($this->getTable('catalogrule_product'), $rows);
  362. $rows = [];
  363. }
  364. }
  365. }
  366. if (!empty($rows)) {
  367. $this->connection->insertMultiple($this->resource->getTableName('catalogrule_product'), $rows);
  368. }
  369. } catch (\Exception $e) {
  370. throw $e;
  371. }
  372. $this->reindexRuleProductPrice->execute($this->batchCount, $product);
  373. $this->reindexRuleGroupWebsite->execute();
  374. return $this;
  375. }
  376. /**
  377. * @param string $tableName
  378. * @return string
  379. */
  380. protected function getTable($tableName)
  381. {
  382. return $this->resource->getTableName($tableName);
  383. }
  384. /**
  385. * @param Rule $rule
  386. * @return $this
  387. * @deprecated 101.0.0
  388. * @see ReindexRuleProduct::execute
  389. */
  390. protected function updateRuleProductData(Rule $rule)
  391. {
  392. $ruleId = $rule->getId();
  393. if ($rule->getProductsFilter()) {
  394. $this->connection->delete(
  395. $this->getTable('catalogrule_product'),
  396. ['rule_id=?' => $ruleId, 'product_id IN (?)' => $rule->getProductsFilter()]
  397. );
  398. } else {
  399. $this->connection->delete(
  400. $this->getTable('catalogrule_product'),
  401. $this->connection->quoteInto('rule_id=?', $ruleId)
  402. );
  403. }
  404. $this->reindexRuleProduct->execute($rule, $this->batchCount);
  405. return $this;
  406. }
  407. /**
  408. * @param Product|null $product
  409. * @throws \Exception
  410. * @return $this
  411. * @deprecated 101.0.0
  412. * @see ReindexRuleProductPrice::execute
  413. * @see ReindexRuleGroupWebsite::execute
  414. */
  415. protected function applyAllRules(Product $product = null)
  416. {
  417. $this->reindexRuleProductPrice->execute($this->batchCount, $product);
  418. $this->reindexRuleGroupWebsite->execute();
  419. return $this;
  420. }
  421. /**
  422. * Update CatalogRuleGroupWebsite data
  423. *
  424. * @return $this
  425. * @deprecated 101.0.0
  426. * @see ReindexRuleGroupWebsite::execute
  427. */
  428. protected function updateCatalogRuleGroupWebsiteData()
  429. {
  430. $this->reindexRuleGroupWebsite->execute();
  431. return $this;
  432. }
  433. /**
  434. * Clean rule price index
  435. *
  436. * @return $this
  437. */
  438. protected function deleteOldData()
  439. {
  440. $this->connection->delete($this->getTable('catalogrule_product_price'));
  441. return $this;
  442. }
  443. /**
  444. * @param array $ruleData
  445. * @param null $productData
  446. * @return float
  447. * @deprecated 101.0.0
  448. * @see ProductPriceCalculator::calculate
  449. */
  450. protected function calcRuleProductPrice($ruleData, $productData = null)
  451. {
  452. return $this->productPriceCalculator->calculate($ruleData, $productData);
  453. }
  454. /**
  455. * @param int $websiteId
  456. * @param Product|null $product
  457. * @return \Zend_Db_Statement_Interface
  458. * @throws \Magento\Framework\Exception\LocalizedException
  459. * @deprecated 101.0.0
  460. * @see RuleProductsSelectBuilder::build
  461. */
  462. protected function getRuleProductsStmt($websiteId, Product $product = null)
  463. {
  464. return $this->ruleProductsSelectBuilder->build($websiteId, $product);
  465. }
  466. /**
  467. * @param array $arrData
  468. * @return $this
  469. * @throws \Exception
  470. * @deprecated 101.0.0
  471. * @see RuleProductPricesPersistor::execute
  472. */
  473. protected function saveRuleProductPrices($arrData)
  474. {
  475. $this->pricesPersistor->execute($arrData);
  476. return $this;
  477. }
  478. /**
  479. * Get active rules
  480. *
  481. * @return array
  482. */
  483. protected function getActiveRules()
  484. {
  485. return $this->ruleCollectionFactory->create()->addFieldToFilter('is_active', 1);
  486. }
  487. /**
  488. * Get active rules
  489. *
  490. * @return array
  491. */
  492. protected function getAllRules()
  493. {
  494. return $this->ruleCollectionFactory->create();
  495. }
  496. /**
  497. * @param int $productId
  498. * @return Product
  499. */
  500. protected function getProduct($productId)
  501. {
  502. if (!isset($this->loadedProducts[$productId])) {
  503. $this->loadedProducts[$productId] = $this->productFactory->create()->load($productId);
  504. }
  505. return $this->loadedProducts[$productId];
  506. }
  507. /**
  508. * @param \Exception $e
  509. * @return void
  510. */
  511. protected function critical($e)
  512. {
  513. $this->logger->critical($e);
  514. }
  515. }