88
99namespace Magento \Catalog \Model \Indexer \Category \Product ;
1010
11- use Magento \Framework \DB \Query \Generator as QueryGenerator ;
11+ use Magento \Catalog \Api \Data \ProductInterface ;
12+ use Magento \Catalog \Model \Product ;
13+ use Magento \Framework \App \ObjectManager ;
1214use Magento \Framework \App \ResourceConnection ;
15+ use Magento \Framework \DB \Query \Generator as QueryGenerator ;
16+ use Magento \Framework \DB \Select ;
1317use Magento \Framework \EntityManager \MetadataPool ;
18+ use Magento \Store \Model \Store ;
1419
1520/**
1621 * Class AbstractAction
@@ -45,21 +50,21 @@ abstract class AbstractAction
4550 /**
4651 * Cached non anchor categories select by store id
4752 *
48- * @var \Magento\Framework\DB\ Select[]
53+ * @var Select[]
4954 */
5055 protected $ nonAnchorSelects = [];
5156
5257 /**
5358 * Cached anchor categories select by store id
5459 *
55- * @var \Magento\Framework\DB\ Select[]
60+ * @var Select[]
5661 */
5762 protected $ anchorSelects = [];
5863
5964 /**
6065 * Cached all product select by store id
6166 *
62- * @var \Magento\Framework\DB\ Select[]
67+ * @var Select[]
6368 */
6469 protected $ productsSelects = [];
6570
@@ -119,19 +124,21 @@ abstract class AbstractAction
119124 * @param \Magento\Store\Model\StoreManagerInterface $storeManager
120125 * @param \Magento\Catalog\Model\Config $config
121126 * @param QueryGenerator $queryGenerator
127+ * @param MetadataPool|null $metadataPool
122128 */
123129 public function __construct (
124130 \Magento \Framework \App \ResourceConnection $ resource ,
125131 \Magento \Store \Model \StoreManagerInterface $ storeManager ,
126132 \Magento \Catalog \Model \Config $ config ,
127- QueryGenerator $ queryGenerator = null
133+ QueryGenerator $ queryGenerator = null ,
134+ MetadataPool $ metadataPool = null
128135 ) {
129136 $ this ->resource = $ resource ;
130137 $ this ->connection = $ resource ->getConnection ();
131138 $ this ->storeManager = $ storeManager ;
132139 $ this ->config = $ config ;
133- $ this ->queryGenerator = $ queryGenerator ?: \ Magento \ Framework \ App \ ObjectManager::getInstance ()
134- ->get (QueryGenerator ::class);
140+ $ this ->queryGenerator = $ queryGenerator ?: ObjectManager::getInstance ()-> get (QueryGenerator::class);
141+ $ this -> metadataPool = $ metadataPool ?: ObjectManager:: getInstance () ->get (MetadataPool ::class);
135142 }
136143
137144 /**
@@ -188,9 +195,9 @@ protected function getMainTable()
188195 */
189196 protected function getMainTmpTable ()
190197 {
191- return $ this ->useTempTable ? $ this -> getTable (
192- self ::MAIN_INDEX_TABLE . self ::TEMPORARY_TABLE_SUFFIX
193- ) : $ this ->getMainTable ();
198+ return $ this ->useTempTable
199+ ? $ this -> getTable ( self ::MAIN_INDEX_TABLE . self ::TEMPORARY_TABLE_SUFFIX )
200+ : $ this ->getMainTable ();
194201 }
195202
196203 /**
@@ -218,24 +225,25 @@ protected function getPathFromCategoryId($categoryId)
218225 /**
219226 * Retrieve select for reindex products of non anchor categories
220227 *
221- * @param \Magento\Store\Model\Store $store
222- * @return \Magento\Framework\DB\Select
228+ * @param Store $store
229+ * @return Select
230+ * @throws \Exception when metadata not found for ProductInterface
223231 */
224- protected function getNonAnchorCategoriesSelect (\ Magento \ Store \ Model \ Store $ store )
232+ protected function getNonAnchorCategoriesSelect (Store $ store )
225233 {
226234 if (!isset ($ this ->nonAnchorSelects [$ store ->getId ()])) {
227235 $ statusAttributeId = $ this ->config ->getAttribute (
228- \ Magento \ Catalog \ Model \ Product::ENTITY ,
236+ Product::ENTITY ,
229237 'status '
230238 )->getId ();
231239 $ visibilityAttributeId = $ this ->config ->getAttribute (
232- \ Magento \ Catalog \ Model \ Product::ENTITY ,
240+ Product::ENTITY ,
233241 'visibility '
234242 )->getId ();
235243
236244 $ rootPath = $ this ->getPathFromCategoryId ($ store ->getRootCategoryId ());
237245
238- $ metadata = $ this ->getMetadataPool () ->getMetadata (\ Magento \ Catalog \ Api \ Data \ ProductInterface::class);
246+ $ metadata = $ this ->metadataPool ->getMetadata (ProductInterface::class);
239247 $ linkField = $ metadata ->getLinkField ();
240248 $ select = $ this ->connection ->select ()->from (
241249 ['cc ' => $ this ->getTable ('catalog_category_entity ' )],
@@ -304,12 +312,65 @@ protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $stor
304312 ]
305313 );
306314
315+ $ this ->addFilteringByChildProductsToSelect ($ select , $ store );
316+
307317 $ this ->nonAnchorSelects [$ store ->getId ()] = $ select ;
308318 }
309319
310320 return $ this ->nonAnchorSelects [$ store ->getId ()];
311321 }
312322
323+ /**
324+ * Add filtering by child products to select
325+ *
326+ * It's used for correct handling of composite products.
327+ * This method makes assumption that select already joins `catalog_product_entity` as `cpe`.
328+ *
329+ * @param Select $select
330+ * @param Store $store
331+ * @return void
332+ * @throws \Exception when metadata not found for ProductInterface
333+ */
334+ private function addFilteringByChildProductsToSelect (Select $ select , Store $ store )
335+ {
336+ $ metadata = $ this ->metadataPool ->getMetadata (ProductInterface::class);
337+ $ linkField = $ metadata ->getLinkField ();
338+
339+ $ statusAttributeId = $ this ->config ->getAttribute (Product::ENTITY , 'status ' )->getId ();
340+
341+ $ select ->joinLeft (
342+ ['relation ' => $ this ->getTable ('catalog_product_relation ' )],
343+ 'cpe. ' . $ linkField . ' = relation.parent_id ' ,
344+ []
345+ )->joinLeft (
346+ ['relation_product_entity ' => $ this ->getTable ('catalog_product_entity ' )],
347+ 'relation.child_id = relation_product_entity.entity_id ' ,
348+ []
349+ )->joinLeft (
350+ ['child_cpsd ' => $ this ->getTable ('catalog_product_entity_int ' )],
351+ 'child_cpsd. ' . $ linkField . ' = ' . 'relation_product_entity. ' . $ linkField
352+ . ' AND child_cpsd.store_id = 0 '
353+ . ' AND child_cpsd.attribute_id = ' . $ statusAttributeId ,
354+ []
355+ )->joinLeft (
356+ ['child_cpss ' => $ this ->getTable ('catalog_product_entity_int ' )],
357+ 'child_cpss. ' . $ linkField . ' = ' . 'relation_product_entity. ' . $ linkField . ''
358+ . ' AND child_cpss.attribute_id = child_cpsd.attribute_id '
359+ . ' AND child_cpss.store_id = ' . $ store ->getId (),
360+ []
361+ )->where (
362+ 'relation.child_id IS NULL OR '
363+ . $ this ->connection ->getIfNullSql ('child_cpss.value ' , 'child_cpsd.value ' ) . ' = ? ' ,
364+ \Magento \Catalog \Model \Product \Attribute \Source \Status::STATUS_ENABLED
365+ )->group (
366+ [
367+ 'cc.entity_id ' ,
368+ 'ccp.product_id ' ,
369+ 'visibility '
370+ ]
371+ );
372+ }
373+
313374 /**
314375 * Check whether select ranging is needed
315376 *
@@ -323,16 +384,13 @@ protected function isRangingNeeded()
323384 /**
324385 * Return selects cut by min and max
325386 *
326- * @param \Magento\Framework\DB\ Select $select
387+ * @param Select $select
327388 * @param string $field
328389 * @param int $range
329- * @return \Magento\Framework\DB\ Select[]
390+ * @return Select[]
330391 */
331- protected function prepareSelectsByRange (
332- \Magento \Framework \DB \Select $ select ,
333- $ field ,
334- $ range = self ::RANGE_CATEGORY_STEP
335- ) {
392+ protected function prepareSelectsByRange (Select $ select , $ field , $ range = self ::RANGE_CATEGORY_STEP )
393+ {
336394 if ($ this ->isRangingNeeded ()) {
337395 $ iterator = $ this ->queryGenerator ->generate (
338396 $ field ,
@@ -353,10 +411,10 @@ protected function prepareSelectsByRange(
353411 /**
354412 * Reindex products of non anchor categories
355413 *
356- * @param \Magento\Store\Model\ Store $store
414+ * @param Store $store
357415 * @return void
358416 */
359- protected function reindexNonAnchorCategories (\ Magento \ Store \ Model \ Store $ store )
417+ protected function reindexNonAnchorCategories (Store $ store )
360418 {
361419 $ selects = $ this ->prepareSelectsByRange ($ this ->getNonAnchorCategoriesSelect ($ store ), 'entity_id ' );
362420 foreach ($ selects as $ select ) {
@@ -374,43 +432,44 @@ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store)
374432 /**
375433 * Check if anchor select isset
376434 *
377- * @param \Magento\Store\Model\ Store $store
435+ * @param Store $store
378436 * @return bool
379437 */
380- protected function hasAnchorSelect (\ Magento \ Store \ Model \ Store $ store )
438+ protected function hasAnchorSelect (Store $ store )
381439 {
382440 return isset ($ this ->anchorSelects [$ store ->getId ()]);
383441 }
384442
385443 /**
386444 * Create anchor select
387445 *
388- * @param \Magento\Store\Model\Store $store
389- * @return \Magento\Framework\DB\Select
446+ * @param Store $store
447+ * @return Select
448+ * @throws \Exception when metadata not found for ProductInterface or CategoryInterface
390449 * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
391450 */
392- protected function createAnchorSelect (\ Magento \ Store \ Model \ Store $ store )
451+ protected function createAnchorSelect (Store $ store )
393452 {
394453 $ isAnchorAttributeId = $ this ->config ->getAttribute (
395454 \Magento \Catalog \Model \Category::ENTITY ,
396455 'is_anchor '
397456 )->getId ();
398- $ statusAttributeId = $ this ->config ->getAttribute (\ Magento \ Catalog \ Model \ Product::ENTITY , 'status ' )->getId ();
457+ $ statusAttributeId = $ this ->config ->getAttribute (Product::ENTITY , 'status ' )->getId ();
399458 $ visibilityAttributeId = $ this ->config ->getAttribute (
400- \ Magento \ Catalog \ Model \ Product::ENTITY ,
459+ Product::ENTITY ,
401460 'visibility '
402461 )->getId ();
403462 $ rootCatIds = explode ('/ ' , $ this ->getPathFromCategoryId ($ store ->getRootCategoryId ()));
404463 array_pop ($ rootCatIds );
405464
406465 $ temporaryTreeTable = $ this ->makeTempCategoryTreeIndex ();
407466
408- $ productMetadata = $ this ->getMetadataPool () ->getMetadata (\ Magento \ Catalog \ Api \ Data \ ProductInterface::class);
409- $ categoryMetadata = $ this ->getMetadataPool () ->getMetadata (\Magento \Catalog \Api \Data \CategoryInterface::class);
467+ $ productMetadata = $ this ->metadataPool ->getMetadata (ProductInterface::class);
468+ $ categoryMetadata = $ this ->metadataPool ->getMetadata (\Magento \Catalog \Api \Data \CategoryInterface::class);
410469 $ productLinkField = $ productMetadata ->getLinkField ();
411470 $ categoryLinkField = $ categoryMetadata ->getLinkField ();
412471
413- return $ this ->connection ->select ()->from (
472+ $ select = $ this ->connection ->select ()->from (
414473 ['cc ' => $ this ->getTable ('catalog_category_entity ' )],
415474 []
416475 )->joinInner (
@@ -492,6 +551,10 @@ protected function createAnchorSelect(\Magento\Store\Model\Store $store)
492551 'visibility ' => new \Zend_Db_Expr ($ this ->connection ->getIfNullSql ('cpvs.value ' , 'cpvd.value ' )),
493552 ]
494553 );
554+
555+ $ this ->addFilteringByChildProductsToSelect ($ select , $ store );
556+
557+ return $ select ;
495558 }
496559
497560 /**
@@ -586,10 +649,10 @@ protected function fillTempCategoryTreeIndex($temporaryName)
586649 /**
587650 * Retrieve select for reindex products of non anchor categories
588651 *
589- * @param \Magento\Store\Model\ Store $store
590- * @return \Magento\Framework\DB\ Select
652+ * @param Store $store
653+ * @return Select
591654 */
592- protected function getAnchorCategoriesSelect (\ Magento \ Store \ Model \ Store $ store )
655+ protected function getAnchorCategoriesSelect (Store $ store )
593656 {
594657 if (!$ this ->hasAnchorSelect ($ store )) {
595658 $ this ->anchorSelects [$ store ->getId ()] = $ this ->createAnchorSelect ($ store );
@@ -600,10 +663,10 @@ protected function getAnchorCategoriesSelect(\Magento\Store\Model\Store $store)
600663 /**
601664 * Reindex products of anchor categories
602665 *
603- * @param \Magento\Store\Model\ Store $store
666+ * @param Store $store
604667 * @return void
605668 */
606- protected function reindexAnchorCategories (\ Magento \ Store \ Model \ Store $ store )
669+ protected function reindexAnchorCategories (Store $ store )
607670 {
608671 $ selects = $ this ->prepareSelectsByRange ($ this ->getAnchorCategoriesSelect ($ store ), 'entity_id ' );
609672
@@ -622,22 +685,23 @@ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store)
622685 /**
623686 * Get select for all products
624687 *
625- * @param \Magento\Store\Model\Store $store
626- * @return \Magento\Framework\DB\Select
688+ * @param Store $store
689+ * @return Select
690+ * @throws \Exception when metadata not found for ProductInterface
627691 */
628- protected function getAllProducts (\ Magento \ Store \ Model \ Store $ store )
692+ protected function getAllProducts (Store $ store )
629693 {
630694 if (!isset ($ this ->productsSelects [$ store ->getId ()])) {
631695 $ statusAttributeId = $ this ->config ->getAttribute (
632- \ Magento \ Catalog \ Model \ Product::ENTITY ,
696+ Product::ENTITY ,
633697 'status '
634698 )->getId ();
635699 $ visibilityAttributeId = $ this ->config ->getAttribute (
636- \ Magento \ Catalog \ Model \ Product::ENTITY ,
700+ Product::ENTITY ,
637701 'visibility '
638702 )->getId ();
639703
640- $ metadata = $ this ->getMetadataPool () ->getMetadata (\ Magento \ Catalog \ Api \ Data \ ProductInterface::class);
704+ $ metadata = $ this ->metadataPool ->getMetadata (ProductInterface::class);
641705 $ linkField = $ metadata ->getLinkField ();
642706
643707 $ select = $ this ->connection ->select ()->from (
@@ -726,10 +790,10 @@ protected function isIndexRootCategoryNeeded()
726790 /**
727791 * Reindex all products to root category
728792 *
729- * @param \Magento\Store\Model\ Store $store
793+ * @param Store $store
730794 * @return void
731795 */
732- protected function reindexRootCategory (\ Magento \ Store \ Model \ Store $ store )
796+ protected function reindexRootCategory (Store $ store )
733797 {
734798 if ($ this ->isIndexRootCategoryNeeded ()) {
735799 $ selects = $ this ->prepareSelectsByRange (
@@ -750,16 +814,4 @@ protected function reindexRootCategory(\Magento\Store\Model\Store $store)
750814 }
751815 }
752816 }
753-
754- /**
755- * @return \Magento\Framework\EntityManager\MetadataPool
756- */
757- private function getMetadataPool ()
758- {
759- if (null === $ this ->metadataPool ) {
760- $ this ->metadataPool = \Magento \Framework \App \ObjectManager::getInstance ()
761- ->get (\Magento \Framework \EntityManager \MetadataPool::class);
762- }
763- return $ this ->metadataPool ;
764- }
765817}
0 commit comments