IndexerHandler.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Indexer\SaveHandler;
  7. use Magento\Framework\App\ResourceConnection;
  8. use Magento\Framework\DB\Adapter\AdapterInterface;
  9. use Magento\Framework\Search\Request\Dimension;
  10. use Magento\Framework\Search\Request\IndexScopeResolverInterface;
  11. use Magento\Framework\Indexer\IndexStructureInterface;
  12. use Magento\Framework\Indexer\ScopeResolver\FlatScopeResolver;
  13. use Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver;
  14. /**
  15. * Save handler for indexer.
  16. */
  17. class IndexerHandler implements IndexerInterface
  18. {
  19. /**
  20. * @var string[]
  21. */
  22. protected $dataTypes = ['searchable', 'filterable'];
  23. /**
  24. * @var IndexStructureInterface
  25. */
  26. protected $indexStructure;
  27. /**
  28. * @var array
  29. */
  30. protected $data;
  31. /**
  32. * @var array
  33. */
  34. protected $fields;
  35. /**
  36. * @var Resource|Resource
  37. */
  38. protected $resource;
  39. /**
  40. * @var Batch
  41. */
  42. protected $batch;
  43. /**
  44. * @var int
  45. */
  46. protected $batchSize;
  47. /**
  48. * @var IndexScopeResolverInterface[]
  49. */
  50. protected $scopeResolvers;
  51. /**
  52. * @param IndexStructureInterface $indexStructure
  53. * @var AdapterInterface
  54. */
  55. protected $connection;
  56. /**
  57. * @param IndexStructureInterface $indexStructure
  58. * @param ResourceConnection $resource
  59. * @param Batch $batch
  60. * @param \Magento\Framework\Indexer\ScopeResolver\IndexScopeResolver $indexScopeResolver
  61. * @param \Magento\Framework\Indexer\ScopeResolver\FlatScopeResolver $flatScopeResolver
  62. * @param array $data
  63. * @param int $batchSize
  64. */
  65. public function __construct(
  66. IndexStructureInterface $indexStructure,
  67. ResourceConnection $resource,
  68. Batch $batch,
  69. IndexScopeResolver $indexScopeResolver,
  70. FlatScopeResolver $flatScopeResolver,
  71. array $data,
  72. $batchSize = 100
  73. ) {
  74. $this->indexStructure = $indexStructure;
  75. $this->resource = $resource;
  76. $this->connection = $resource->getConnection();
  77. $this->batch = $batch;
  78. $this->scopeResolvers[$this->dataTypes[0]] = $indexScopeResolver;
  79. $this->scopeResolvers[$this->dataTypes[1]] = $flatScopeResolver;
  80. $this->data = $data;
  81. $this->batchSize = $batchSize;
  82. $this->fields = [];
  83. $this->prepareFields();
  84. }
  85. /**
  86. * @inheritdoc
  87. */
  88. public function saveIndex($dimensions, \Traversable $documents)
  89. {
  90. foreach ($this->batch->getItems($documents, $this->batchSize) as $batchDocuments) {
  91. $this->insertDocumentsForSearchable($batchDocuments, $dimensions);
  92. $this->insertDocumentsForFilterable($batchDocuments, $dimensions);
  93. }
  94. }
  95. /**
  96. * @inheritdoc
  97. */
  98. public function deleteIndex($dimensions, \Traversable $documents)
  99. {
  100. foreach ($this->dataTypes as $dataType) {
  101. foreach ($this->batch->getItems($documents, $this->batchSize) as $batchDocuments) {
  102. $documentsId = array_column($batchDocuments, 'id');
  103. $this->connection->delete($this->getTableName($dataType, $dimensions), ['id' => $documentsId]);
  104. }
  105. }
  106. }
  107. /**
  108. * @inheritdoc
  109. */
  110. public function cleanIndex($dimensions)
  111. {
  112. $this->indexStructure->delete($this->getIndexName(), $dimensions);
  113. $this->indexStructure->create($this->getIndexName(), $this->fields, $dimensions);
  114. }
  115. /**
  116. * @inheritdoc
  117. */
  118. public function isAvailable($dimensions = [])
  119. {
  120. return true;
  121. }
  122. /**
  123. * Returns table name.
  124. *
  125. * @param string $dataType
  126. * @param Dimension[] $dimensions
  127. * @return string
  128. */
  129. protected function getTableName($dataType, $dimensions)
  130. {
  131. return $this->scopeResolvers[$dataType]->resolve($this->getIndexName(), $dimensions);
  132. }
  133. /**
  134. * Returns index name
  135. *
  136. * @return string
  137. */
  138. protected function getIndexName()
  139. {
  140. return $this->data['indexer_id'];
  141. }
  142. /**
  143. * Save searchable documents to storage.
  144. *
  145. * @param array $documents
  146. * @param Dimension[] $dimensions
  147. * @return void
  148. */
  149. private function insertDocumentsForSearchable(array $documents, array $dimensions)
  150. {
  151. $this->connection->insertOnDuplicate(
  152. $this->getTableName($this->dataTypes[0], $dimensions),
  153. $this->prepareSearchableFields($documents),
  154. ['data_index']
  155. );
  156. }
  157. /**
  158. * Save filterable documents to storage.
  159. *
  160. * @param array $documents
  161. * @param Dimension[] $dimensions
  162. * @return void
  163. */
  164. protected function insertDocumentsForFilterable(array $documents, array $dimensions)
  165. {
  166. $onDuplicate = [];
  167. foreach ($this->fields as $field) {
  168. if ($field['type'] === $this->dataTypes[1]) {
  169. $onDuplicate[] = $field['name'];
  170. }
  171. }
  172. $this->connection->insertOnDuplicate(
  173. $this->getTableName($this->dataTypes[1], $dimensions),
  174. $this->prepareFilterableFields($documents),
  175. $onDuplicate
  176. );
  177. }
  178. /**
  179. * Prepare filterable fields.
  180. *
  181. * @param array $documents
  182. * @return array
  183. */
  184. protected function prepareFilterableFields(array $documents)
  185. {
  186. $insertDocuments = [];
  187. foreach ($documents as $entityId => $document) {
  188. $documentFlat = ['entity_id' => $entityId];
  189. foreach ($this->fields as $field) {
  190. if ($field['type'] == $this->dataTypes[1]) {
  191. $documentFlat[$field['name']] = $document[$field['name']];
  192. }
  193. }
  194. $insertDocuments[] = $documentFlat;
  195. }
  196. return $insertDocuments;
  197. }
  198. /**
  199. * Prepare searchable fields.
  200. *
  201. * @param array $documents
  202. * @return array
  203. */
  204. private function prepareSearchableFields(array $documents)
  205. {
  206. $insertDocuments = [];
  207. foreach ($documents as $entityId => $document) {
  208. foreach ($this->fields as $field) {
  209. if ($field['type'] === $this->dataTypes[0]) {
  210. $insertDocuments[] = [
  211. 'entity_id' => $entityId,
  212. 'attribute_id' => $field['name'],
  213. 'data_index' => $document[$field['name']],
  214. ];
  215. }
  216. }
  217. }
  218. return $insertDocuments;
  219. }
  220. /**
  221. * Prepare fields.
  222. *
  223. * @return void
  224. */
  225. private function prepareFields()
  226. {
  227. foreach ($this->data['fieldsets'] as $fieldset) {
  228. $this->fields = array_merge($this->fields, array_values($fieldset['fields']));
  229. }
  230. }
  231. }