ConditionsToSearchCriteriaMapper.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. declare(strict_types=1);
  7. namespace Magento\CatalogRule\Model\Rule\Condition;
  8. use Magento\Framework\Exception\InputException;
  9. use Magento\Rule\Model\Condition\ConditionInterface;
  10. use Magento\CatalogRule\Model\Rule\Condition\Combine as CombinedCondition;
  11. use Magento\CatalogRule\Model\Rule\Condition\Product as SimpleCondition;
  12. use Magento\Framework\Api\CombinedFilterGroup as FilterGroup;
  13. use Magento\Framework\Api\Filter;
  14. use Magento\Framework\Api\SearchCriteria;
  15. /**
  16. * Maps catalog price rule conditions to search criteria
  17. */
  18. class ConditionsToSearchCriteriaMapper
  19. {
  20. /**
  21. * @var \Magento\Framework\Api\SearchCriteriaBuilderFactory
  22. */
  23. private $searchCriteriaBuilderFactory;
  24. /**
  25. * @var \Magento\Framework\Api\CombinedFilterGroupFactory
  26. */
  27. private $combinedFilterGroupFactory;
  28. /**
  29. * @var \Magento\Framework\Api\FilterFactory
  30. */
  31. private $filterFactory;
  32. /**
  33. * @param \Magento\Framework\Api\SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory
  34. * @param \Magento\Framework\Api\CombinedFilterGroupFactory $combinedFilterGroupFactory
  35. * @param \Magento\Framework\Api\FilterFactory $filterFactory
  36. */
  37. public function __construct(
  38. \Magento\Framework\Api\SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory,
  39. \Magento\Framework\Api\CombinedFilterGroupFactory $combinedFilterGroupFactory,
  40. \Magento\Framework\Api\FilterFactory $filterFactory
  41. ) {
  42. $this->searchCriteriaBuilderFactory = $searchCriteriaBuilderFactory;
  43. $this->combinedFilterGroupFactory = $combinedFilterGroupFactory;
  44. $this->filterFactory = $filterFactory;
  45. }
  46. /**
  47. * Maps catalog price rule conditions to search criteria
  48. *
  49. * @param CombinedCondition $conditions
  50. * @return SearchCriteria
  51. * @throws InputException
  52. */
  53. public function mapConditionsToSearchCriteria(CombinedCondition $conditions): SearchCriteria
  54. {
  55. $filterGroup = $this->mapCombinedConditionToFilterGroup($conditions);
  56. $searchCriteriaBuilder = $this->searchCriteriaBuilderFactory->create();
  57. if ($filterGroup !== null) {
  58. $searchCriteriaBuilder->setFilterGroups([$filterGroup]);
  59. }
  60. return $searchCriteriaBuilder->create();
  61. }
  62. /**
  63. * Convert condition to filter group
  64. *
  65. * @param ConditionInterface $condition
  66. * @return null|\Magento\Framework\Api\CombinedFilterGroup|\Magento\Framework\Api\Filter
  67. * @throws InputException
  68. */
  69. private function mapConditionToFilterGroup(ConditionInterface $condition)
  70. {
  71. if ($condition->getType() === CombinedCondition::class) {
  72. return $this->mapCombinedConditionToFilterGroup($condition);
  73. } elseif ($condition->getType() === SimpleCondition::class) {
  74. return $this->mapSimpleConditionToFilterGroup($condition);
  75. }
  76. throw new InputException(
  77. __('Undefined condition type "%1" passed in.', $condition->getType())
  78. );
  79. }
  80. /**
  81. * Convert combined condition to filter group
  82. *
  83. * @param Combine $combinedCondition
  84. * @return null|\Magento\Framework\Api\CombinedFilterGroup
  85. * @throws InputException
  86. */
  87. private function mapCombinedConditionToFilterGroup(CombinedCondition $combinedCondition)
  88. {
  89. $filters = [];
  90. foreach ($combinedCondition->getConditions() as $condition) {
  91. $filter = $this->mapConditionToFilterGroup($condition);
  92. if ($filter === null) {
  93. continue;
  94. }
  95. // This required to solve cases when condition is configured like:
  96. // "If ALL/ANY of these conditions are FALSE" - we need to reverse SQL operator for this "FALSE"
  97. if ((bool)$combinedCondition->getValue() === false) {
  98. $this->reverseSqlOperatorInFilter($filter);
  99. }
  100. $filters[] = $filter;
  101. }
  102. if (count($filters) === 0) {
  103. return null;
  104. }
  105. return $this->createCombinedFilterGroup($filters, $combinedCondition->getAggregator());
  106. }
  107. /**
  108. * Convert simple condition to filter group
  109. *
  110. * @param ConditionInterface $productCondition
  111. * @return FilterGroup|Filter
  112. * @throws InputException
  113. */
  114. private function mapSimpleConditionToFilterGroup(ConditionInterface $productCondition)
  115. {
  116. if (is_array($productCondition->getValue())) {
  117. return $this->processSimpleConditionWithArrayValue($productCondition);
  118. }
  119. return $this->createFilter(
  120. $productCondition->getAttribute(),
  121. (string) $productCondition->getValue(),
  122. $productCondition->getOperator()
  123. );
  124. }
  125. /**
  126. * Convert simple condition with array value to filter group
  127. *
  128. * @param ConditionInterface $productCondition
  129. * @return FilterGroup
  130. * @throws InputException
  131. */
  132. private function processSimpleConditionWithArrayValue(ConditionInterface $productCondition): FilterGroup
  133. {
  134. $filters = [];
  135. foreach ($productCondition->getValue() as $subValue) {
  136. $filters[] = $this->createFilter(
  137. $productCondition->getAttribute(),
  138. (string) $subValue,
  139. $productCondition->getOperator()
  140. );
  141. }
  142. $combinationMode = $this->getGlueForArrayValues($productCondition->getOperator());
  143. return $this->createCombinedFilterGroup($filters, $combinationMode);
  144. }
  145. /**
  146. * Get glue for multiple values by operator
  147. *
  148. * @param string $operator
  149. * @return string
  150. */
  151. private function getGlueForArrayValues(string $operator): string
  152. {
  153. if (in_array($operator, ['!=', '!{}', '!()'], true)) {
  154. return 'all';
  155. }
  156. return 'any';
  157. }
  158. /**
  159. * Reverse sql conditions to their corresponding negative analog
  160. *
  161. * @param Filter $filter
  162. * @return void
  163. * @throws InputException
  164. */
  165. private function reverseSqlOperatorInFilter(Filter $filter)
  166. {
  167. $operatorsMap = [
  168. 'eq' => 'neq',
  169. 'neq' => 'eq',
  170. 'gteq' => 'lt',
  171. 'lteq' => 'gt',
  172. 'gt' => 'lteq',
  173. 'lt' => 'gteq',
  174. 'like' => 'nlike',
  175. 'nlike' => 'like',
  176. 'in' => 'nin',
  177. 'nin' => 'in',
  178. ];
  179. if (!array_key_exists($filter->getConditionType(), $operatorsMap)) {
  180. throw new InputException(
  181. __(
  182. 'Undefined SQL operator "%1" passed in. Valid operators are: %2',
  183. $filter->getConditionType(),
  184. implode(',', array_keys($operatorsMap))
  185. )
  186. );
  187. }
  188. $filter->setConditionType(
  189. $operatorsMap[$filter->getConditionType()]
  190. );
  191. }
  192. /**
  193. * Convert filters array into combined filter group
  194. *
  195. * @param array $filters
  196. * @param string $combinationMode
  197. * @return FilterGroup
  198. * @throws InputException
  199. */
  200. private function createCombinedFilterGroup(array $filters, string $combinationMode): FilterGroup
  201. {
  202. return $this->combinedFilterGroupFactory->create([
  203. 'data' => [
  204. FilterGroup::FILTERS => $filters,
  205. FilterGroup::COMBINATION_MODE => $this->mapRuleAggregatorToSQLAggregator($combinationMode)
  206. ]
  207. ]);
  208. }
  209. /**
  210. * Creating of filter object by filtering params
  211. *
  212. * @param string $field
  213. * @param string $value
  214. * @param string $conditionType
  215. * @return Filter
  216. * @throws InputException
  217. */
  218. private function createFilter(string $field, string $value, string $conditionType): Filter
  219. {
  220. return $this->filterFactory->create([
  221. 'data' => [
  222. Filter::KEY_FIELD => $field,
  223. Filter::KEY_VALUE => $value,
  224. Filter::KEY_CONDITION_TYPE => $this->mapRuleOperatorToSQLCondition($conditionType)
  225. ]
  226. ]);
  227. }
  228. /**
  229. * Maps catalog price rule operators to their corresponding operators in SQL
  230. *
  231. * @param string $ruleOperator
  232. * @return string
  233. * @throws InputException
  234. */
  235. private function mapRuleOperatorToSQLCondition(string $ruleOperator): string
  236. {
  237. $operatorsMap = [
  238. '==' => 'eq', // is
  239. '!=' => 'neq', // is not
  240. '>=' => 'gteq', // equals or greater than
  241. '<=' => 'lteq', // equals or less than
  242. '>' => 'gt', // greater than
  243. '<' => 'lt', // less than
  244. '{}' => 'like', // contains
  245. '!{}' => 'nlike', // does not contains
  246. '()' => 'in', // is one of
  247. '!()' => 'nin', // is not one of
  248. '<=>' => 'is_null'
  249. ];
  250. if (!array_key_exists($ruleOperator, $operatorsMap)) {
  251. throw new InputException(
  252. __(
  253. 'Undefined rule operator "%1" passed in. Valid operators are: %2',
  254. $ruleOperator,
  255. implode(',', array_keys($operatorsMap))
  256. )
  257. );
  258. }
  259. return $operatorsMap[$ruleOperator];
  260. }
  261. /**
  262. * Map rule combine aggregations to corresponding SQL operator
  263. *
  264. * @param string $ruleAggregator
  265. * @return string
  266. * @throws InputException
  267. */
  268. private function mapRuleAggregatorToSQLAggregator(string $ruleAggregator): string
  269. {
  270. $operatorsMap = [
  271. 'all' => 'AND',
  272. 'any' => 'OR',
  273. ];
  274. if (!array_key_exists(strtolower($ruleAggregator), $operatorsMap)) {
  275. throw new InputException(
  276. __(
  277. 'Undefined rule aggregator "%1" passed in. Valid operators are: %2',
  278. $ruleAggregator,
  279. implode(',', array_keys($operatorsMap))
  280. )
  281. );
  282. }
  283. return $operatorsMap[$ruleAggregator];
  284. }
  285. }