diff --git a/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php b/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php index 259399ece827b..10b97385c858c 100644 --- a/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php +++ b/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php @@ -11,6 +11,8 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\DB\Select; use Magento\Store\Model\ScopeInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Ddl\Table; /** * Category resource collection @@ -21,6 +23,8 @@ */ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection { + private const BULK_PROCESSING_LIMIT = 400; + /** * Event prefix name * @@ -282,6 +286,7 @@ protected function _loadProductCount() * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.NPathComplexity) * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function loadProductCount($items, $countRegular = true, $countAnchor = true) @@ -337,16 +342,151 @@ public function loadProductCount($items, $countRegular = true, $countAnchor = tr $categoryIds = array_keys($anchor); $countSelect = $this->getProductsCountQuery($categoryIds, (bool)$websiteId); $categoryProductsCount = $this->_conn->fetchPairs($countSelect); + $countFromCategoryTable = []; + if (count($categoryIds) > self::BULK_PROCESSING_LIMIT) { + $countFromCategoryTable = $this->getCountFromCategoryTableBulk($categoryIds, (int)$websiteId); + } + foreach ($anchor as $item) { - $productsCount = isset($categoryProductsCount[$item->getId()]) - ? (int)$categoryProductsCount[$item->getId()] - : $this->getProductsCountFromCategoryTable($item, $websiteId); + $productsCount = 0; + if (count($categoryIds) > self::BULK_PROCESSING_LIMIT) { + if (isset($categoryProductsCount[$item->getId()])) { + $productsCount = (int)$categoryProductsCount[$item->getId()]; + } elseif (isset($countFromCategoryTable[$item->getId()])) { + $productsCount = (int)$countFromCategoryTable[$item->getId()]; + } + } else { + $productsCount = isset($categoryProductsCount[$item->getId()]) + ? (int)$categoryProductsCount[$item->getId()] + : $this->getProductsCountFromCategoryTable($item, $websiteId); + } $item->setProductCount($productsCount); } } return $this; } + /** + * Get products number for each category with bulk query + * + * @param array $categoryIds + * @param int $websiteId + * @return array + * @throws \Zend_Db_Exception + */ + private function getCountFromCategoryTableBulk( + array $categoryIds, + int $websiteId + ) : array { + $connection = $this->_conn; + $tempTableName = 'temp_category_descendants_' . uniqid(); + $tempTable = $connection->newTable($tempTableName) + ->addColumn( + 'category_id', + Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false], + 'Category ID' + ) + ->addColumn( + 'descendant_id', + Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false], + 'Descendant ID' + ) + ->addIndex( + $connection->getIndexName($tempTableName, ['category_id', 'descendant_id']), + ['category_id', 'descendant_id'], + ['type' => AdapterInterface::INDEX_TYPE_PRIMARY] + ); + $connection->createTemporaryTable($tempTable); + $selectDescendants = $connection->select() + ->from( + ['ce' => $this->getTable('catalog_category_entity')], + ['category_id' => 'ce.entity_id', 'descendant_id' => 'ce2.entity_id'] + ) + ->joinInner( + ['ce2' => $this->getTable('catalog_category_entity')], + 'ce2.path LIKE CONCAT(ce.path, \'/%\')', + [] + ) + ->where('ce.entity_id IN (?)', $categoryIds); + + $connection->query( + $connection->insertFromSelect( + $selectDescendants, + $tempTableName, + ['category_id', 'descendant_id'] + ) + ); + $select = $connection->select() + ->from( + ['t' => $tempTableName], + ['category_id' => 't.category_id'] + ) + ->joinLeft( + ['cp' => $this->getTable('catalog_category_product')], + 'cp.category_id = t.descendant_id', + ['product_count' => 'COUNT(DISTINCT cp.product_id)'] + ); + if ($websiteId) { + $select->join( + ['w' => $this->getProductWebsiteTable()], + 'cp.product_id = w.product_id', + [] + )->where('w.website_id = ?', $websiteId); + } + $select->group('t.category_id'); + $result = $connection->fetchPairs($select); + $connection->dropTemporaryTable($tempTableName); + $counts = array_fill_keys($categoryIds, 0); + foreach ($result as $categoryId => $count) { + $counts[$categoryId] = (int)$count; + } + + return $counts; + } + + /** + * Get products count using catalog_category_entity table + * + * @param Category $item + * @param string $websiteId + * @return int + */ + private function getProductsCountFromCategoryTable(Category $item, string $websiteId): int + { + $productCount = 0; + + if ($item->getAllChildren()) { + $bind = ['entity_id' => $item->getId(), 'c_path' => $item->getPath() . '/%']; + $select = $this->_conn->select(); + $select->from( + ['main_table' => $this->getProductTable()], + new \Zend_Db_Expr('COUNT(DISTINCT main_table.product_id)') + )->joinInner( + ['e' => $this->getTable('catalog_category_entity')], + 'main_table.category_id=e.entity_id', + [] + )->where( + '(e.entity_id = :entity_id OR e.path LIKE :c_path)' + ); + if ($websiteId) { + $select->join( + ['w' => $this->getProductWebsiteTable()], + 'main_table.product_id = w.product_id', + [] + )->where( + 'w.website_id = ?', + $websiteId + ); + } + $productCount = (int)$this->_conn->fetchOne($select, $bind); + } + return $productCount; + } + /** * Add category path filter * @@ -519,45 +659,6 @@ public function getProductTable() return $this->_productTable; } - /** - * Get products count using catalog_category_entity table - * - * @param Category $item - * @param string $websiteId - * @return int - */ - private function getProductsCountFromCategoryTable(Category $item, string $websiteId): int - { - $productCount = 0; - - if ($item->getAllChildren()) { - $bind = ['entity_id' => $item->getId(), 'c_path' => $item->getPath() . '/%']; - $select = $this->_conn->select(); - $select->from( - ['main_table' => $this->getProductTable()], - new \Zend_Db_Expr('COUNT(DISTINCT main_table.product_id)') - )->joinInner( - ['e' => $this->getTable('catalog_category_entity')], - 'main_table.category_id=e.entity_id', - [] - )->where( - '(e.entity_id = :entity_id OR e.path LIKE :c_path)' - ); - if ($websiteId) { - $select->join( - ['w' => $this->getProductWebsiteTable()], - 'main_table.product_id = w.product_id', - [] - )->where( - 'w.website_id = ?', - $websiteId - ); - } - $productCount = (int)$this->_conn->fetchOne($select, $bind); - } - return $productCount; - } - /** * Get query for retrieve count of products per category *