/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php

https://gitlab.com/crazybutterfly815/magento2 · PHP · 251 lines · 170 code · 16 blank · 65 comment · 13 complexity · 18f39b54a164b36ca0cf51e85d3ac91a MD5 · raw file

  1. <?php
  2. /**
  3. * Copyright © 2016 Magento. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\CatalogSearch\Model\Search;
  7. use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory;
  8. use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
  9. use Magento\Framework\App\ResourceConnection as AppResource;
  10. use Magento\Framework\DB\Select;
  11. use Magento\Framework\Search\Request\FilterInterface;
  12. use Magento\Framework\Search\Request\Filter\BoolExpression;
  13. use Magento\Framework\Search\Request\Query\Filter;
  14. use Magento\Framework\Search\RequestInterface;
  15. use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
  16. use Magento\Store\Model\StoreManagerInterface;
  17. /**
  18. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  19. */
  20. class TableMapper
  21. {
  22. /**
  23. * @var Resource
  24. */
  25. private $resource;
  26. /**
  27. * @var StoreManagerInterface
  28. */
  29. private $storeManager;
  30. /**
  31. * @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection
  32. */
  33. private $attributeCollection;
  34. /**
  35. * @param AppResource $resource
  36. * @param StoreManagerInterface $storeManager
  37. * @param CollectionFactory $attributeCollectionFactory
  38. */
  39. public function __construct(
  40. AppResource $resource,
  41. StoreManagerInterface $storeManager,
  42. CollectionFactory $attributeCollectionFactory
  43. ) {
  44. $this->resource = $resource;
  45. $this->storeManager = $storeManager;
  46. $this->attributeCollection = $attributeCollectionFactory->create();
  47. }
  48. /**
  49. * @param Select $select
  50. * @param RequestInterface $request
  51. * @return Select
  52. */
  53. public function addTables(Select $select, RequestInterface $request)
  54. {
  55. $mappedTables = [];
  56. $filters = $this->getFilters($request->getQuery());
  57. foreach ($filters as $filter) {
  58. list($alias, $table, $mapOn, $mappedFields) = $this->getMappingData($filter);
  59. if (!array_key_exists($alias, $mappedTables)) {
  60. $select->joinLeft(
  61. [$alias => $table],
  62. $mapOn,
  63. $mappedFields
  64. );
  65. $mappedTables[$alias] = $table;
  66. }
  67. }
  68. return $select;
  69. }
  70. /**
  71. * @param FilterInterface $filter
  72. * @return string
  73. */
  74. public function getMappingAlias(FilterInterface $filter)
  75. {
  76. list($alias) = $this->getMappingData($filter);
  77. return $alias;
  78. }
  79. /**
  80. * Returns mapping data for field in format: [
  81. * 'table_alias',
  82. * 'table',
  83. * 'join_condition',
  84. * ['fields']
  85. * ]
  86. * @param FilterInterface $filter
  87. * @return array
  88. */
  89. private function getMappingData(FilterInterface $filter)
  90. {
  91. $alias = null;
  92. $table = null;
  93. $mapOn = null;
  94. $mappedFields = null;
  95. $field = $filter->getField();
  96. $fieldToTableMap = $this->getFieldToTableMap($field);
  97. if ($fieldToTableMap) {
  98. list($alias, $table, $mapOn, $mappedFields) = $fieldToTableMap;
  99. $table = $this->resource->getTableName($table);
  100. } elseif ($attribute = $this->getAttributeByCode($field)) {
  101. if ($filter->getType() === FilterInterface::TYPE_TERM
  102. && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true)
  103. ) {
  104. $table = $this->resource->getTableName('catalog_product_index_eav');
  105. $alias = $field . RequestGenerator::FILTER_SUFFIX;
  106. $mapOn = sprintf(
  107. 'search_index.entity_id = %1$s.entity_id AND %1$s.attribute_id = %2$d AND %1$s.store_id = %3$d',
  108. $alias,
  109. $attribute->getId(),
  110. $this->getStoreId()
  111. );
  112. $mappedFields = [];
  113. } elseif ($attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) {
  114. $table = $attribute->getBackendTable();
  115. $alias = $field . RequestGenerator::FILTER_SUFFIX;
  116. $mapOn = 'search_index.entity_id = ' . $alias . '.entity_id';
  117. $mappedFields = null;
  118. }
  119. }
  120. return [$alias, $table, $mapOn, $mappedFields];
  121. }
  122. /**
  123. * @param RequestQueryInterface $query
  124. * @return FilterInterface[]
  125. */
  126. private function getFilters($query)
  127. {
  128. $filters = [];
  129. switch ($query->getType()) {
  130. case RequestQueryInterface::TYPE_BOOL:
  131. /** @var \Magento\Framework\Search\Request\Query\BoolExpression $query */
  132. foreach ($query->getMust() as $subQuery) {
  133. $filters = array_merge($filters, $this->getFilters($subQuery));
  134. }
  135. foreach ($query->getShould() as $subQuery) {
  136. $filters = array_merge($filters, $this->getFilters($subQuery));
  137. }
  138. foreach ($query->getMustNot() as $subQuery) {
  139. $filters = array_merge($filters, $this->getFilters($subQuery));
  140. }
  141. break;
  142. case RequestQueryInterface::TYPE_FILTER:
  143. /** @var Filter $query */
  144. $filter = $query->getReference();
  145. if (FilterInterface::TYPE_BOOL === $filter->getType()) {
  146. $filters = array_merge($filters, $this->getFiltersFromBoolFilter($filter));
  147. } else {
  148. $filters[] = $filter;
  149. }
  150. break;
  151. default:
  152. break;
  153. }
  154. return $filters;
  155. }
  156. /**
  157. * @param BoolExpression $boolExpression
  158. * @return FilterInterface[]
  159. */
  160. private function getFiltersFromBoolFilter(BoolExpression $boolExpression)
  161. {
  162. $filters = [];
  163. /** @var BoolExpression $filter */
  164. foreach ($boolExpression->getMust() as $filter) {
  165. if ($filter->getType() === FilterInterface::TYPE_BOOL) {
  166. $filters = array_merge($filters, $this->getFiltersFromBoolFilter($filter));
  167. } else {
  168. $filters[] = $filter;
  169. }
  170. }
  171. foreach ($boolExpression->getShould() as $filter) {
  172. if ($filter->getType() === FilterInterface::TYPE_BOOL) {
  173. $filters = array_merge($filters, $this->getFiltersFromBoolFilter($filter));
  174. } else {
  175. $filters[] = $filter;
  176. }
  177. }
  178. foreach ($boolExpression->getMustNot() as $filter) {
  179. if ($filter->getType() === FilterInterface::TYPE_BOOL) {
  180. $filters = array_merge($filters, $this->getFiltersFromBoolFilter($filter));
  181. } else {
  182. $filters[] = $filter;
  183. }
  184. }
  185. return $filters;
  186. }
  187. /**
  188. * @return int
  189. */
  190. private function getWebsiteId()
  191. {
  192. return $this->storeManager->getWebsite()->getId();
  193. }
  194. /**
  195. * @return int
  196. */
  197. private function getStoreId()
  198. {
  199. return $this->storeManager->getStore()->getId();
  200. }
  201. /**
  202. * @param string $field
  203. * @return array|null
  204. */
  205. private function getFieldToTableMap($field)
  206. {
  207. $fieldToTableMap = [
  208. 'price' => [
  209. 'price_index',
  210. 'catalog_product_index_price',
  211. $this->resource->getConnection()->quoteInto(
  212. 'search_index.entity_id = price_index.entity_id AND price_index.website_id = ?',
  213. $this->getWebsiteId()
  214. ),
  215. []
  216. ],
  217. 'category_ids' => [
  218. 'category_ids_index',
  219. 'catalog_category_product_index',
  220. 'search_index.entity_id = category_ids_index.product_id',
  221. []
  222. ]
  223. ];
  224. return array_key_exists($field, $fieldToTableMap) ? $fieldToTableMap[$field] : null;
  225. }
  226. /**
  227. * @param string $field
  228. * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
  229. */
  230. private function getAttributeByCode($field)
  231. {
  232. $attribute = $this->attributeCollection->getItemByColumnValue('attribute_code', $field);
  233. return $attribute;
  234. }
  235. }