ActiveDataFilter.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\data;
  8. /**
  9. * ActiveDataFilter allows composing a filtering condition in a format suitable for [[\yii\db\QueryInterface::where()]].
  10. *
  11. * @see DataFilter
  12. *
  13. * @author Paul Klimov <klimov.paul@gmail.com>
  14. * @since 2.0.13
  15. */
  16. class ActiveDataFilter extends DataFilter
  17. {
  18. /**
  19. * @var array maps filtering condition keywords to build methods.
  20. * These methods are used by [[buildCondition()]] to build the actual filtering conditions.
  21. * Particular condition builder can be specified using a PHP callback. For example:
  22. *
  23. * ```php
  24. * [
  25. * 'XOR' => function (string $operator, mixed $condition) {
  26. * //return array;
  27. * },
  28. * 'LIKE' => function (string $operator, mixed $condition, string $attribute) {
  29. * //return array;
  30. * },
  31. * ]
  32. * ```
  33. */
  34. public $conditionBuilders = [
  35. 'AND' => 'buildConjunctionCondition',
  36. 'OR' => 'buildConjunctionCondition',
  37. 'NOT' => 'buildBlockCondition',
  38. '<' => 'buildOperatorCondition',
  39. '>' => 'buildOperatorCondition',
  40. '<=' => 'buildOperatorCondition',
  41. '>=' => 'buildOperatorCondition',
  42. '=' => 'buildOperatorCondition',
  43. '!=' => 'buildOperatorCondition',
  44. 'IN' => 'buildOperatorCondition',
  45. 'NOT IN' => 'buildOperatorCondition',
  46. 'LIKE' => 'buildOperatorCondition',
  47. ];
  48. /**
  49. * @var array map filtering operators to operators used in [[\yii\db\QueryInterface::where()]].
  50. * The format is: `[filterOperator => queryOperator]`.
  51. * If particular operator keyword does not appear in the map, it will be used as is.
  52. *
  53. * Usually the map can be left empty as filter operator names are consistent with the ones
  54. * used in [[\yii\db\QueryInterface::where()]]. However, you may want to adjust it in some special cases.
  55. * For example, when using PosgreSQL you may want to setup the following map:
  56. *
  57. * ```php
  58. * [
  59. * 'LIKE' => 'ILIKE'
  60. * ]
  61. * ```
  62. */
  63. public $queryOperatorMap = [];
  64. /**
  65. * {@inheritdoc}
  66. */
  67. protected function buildInternal()
  68. {
  69. $filter = $this->normalize(false);
  70. if (empty($filter)) {
  71. return [];
  72. }
  73. return $this->buildCondition($filter);
  74. }
  75. /**
  76. * @param array $condition
  77. * @return array built condition.
  78. */
  79. protected function buildCondition($condition)
  80. {
  81. $parts = [];
  82. foreach ($condition as $key => $value) {
  83. if (isset($this->conditionBuilders[$key])) {
  84. $method = $this->conditionBuilders[$key];
  85. if (is_string($method)) {
  86. $callback = [$this, $method];
  87. } else {
  88. $callback = $method;
  89. }
  90. } else {
  91. $callback = [$this, 'buildAttributeCondition'];
  92. }
  93. $parts[] = $callback($key, $value);
  94. }
  95. if (!empty($parts)) {
  96. if (count($parts) > 1) {
  97. $parts = array_merge(['AND'], $parts);
  98. } else {
  99. $parts = array_shift($parts);
  100. }
  101. }
  102. return $parts;
  103. }
  104. /**
  105. * Builds conjunction condition, which consists of multiple independent ones.
  106. * It covers such operators as `and` and `or`.
  107. * @param string $operator operator keyword.
  108. * @param mixed $condition raw condition.
  109. * @return array actual condition.
  110. */
  111. protected function buildConjunctionCondition($operator, $condition)
  112. {
  113. if (isset($this->queryOperatorMap[$operator])) {
  114. $operator = $this->queryOperatorMap[$operator];
  115. }
  116. $result = [$operator];
  117. foreach ($condition as $part) {
  118. $result[] = $this->buildCondition($part);
  119. }
  120. return $result;
  121. }
  122. /**
  123. * Builds block condition, which consists of a single condition.
  124. * It covers such operators as `not`.
  125. * @param string $operator operator keyword.
  126. * @param mixed $condition raw condition.
  127. * @return array actual condition.
  128. */
  129. protected function buildBlockCondition($operator, $condition)
  130. {
  131. if (isset($this->queryOperatorMap[$operator])) {
  132. $operator = $this->queryOperatorMap[$operator];
  133. }
  134. return [
  135. $operator,
  136. $this->buildCondition($condition),
  137. ];
  138. }
  139. /**
  140. * Builds search condition for a particular attribute.
  141. * @param string $attribute search attribute name.
  142. * @param mixed $condition search condition.
  143. * @return array actual condition.
  144. */
  145. protected function buildAttributeCondition($attribute, $condition)
  146. {
  147. if (is_array($condition)) {
  148. $parts = [];
  149. foreach ($condition as $operator => $value) {
  150. if (isset($this->operatorTypes[$operator])) {
  151. if (isset($this->conditionBuilders[$operator])) {
  152. $method = $this->conditionBuilders[$operator];
  153. if (is_string($method)) {
  154. $callback = [$this, $method];
  155. } else {
  156. $callback = $method;
  157. }
  158. $parts[] = $callback($operator, $value, $attribute);
  159. } else {
  160. $parts[] = $this->buildOperatorCondition($operator, $value, $attribute);
  161. }
  162. }
  163. }
  164. if (!empty($parts)) {
  165. if (count($parts) > 1) {
  166. return array_merge(['AND'], $parts);
  167. }
  168. return array_shift($parts);
  169. }
  170. }
  171. return [$attribute => $this->filterAttributeValue($attribute, $condition)];
  172. }
  173. /**
  174. * Builds an operator condition.
  175. * @param string $operator operator keyword.
  176. * @param mixed $condition attribute condition.
  177. * @param string $attribute attribute name.
  178. * @return array actual condition.
  179. */
  180. protected function buildOperatorCondition($operator, $condition, $attribute)
  181. {
  182. if (isset($this->queryOperatorMap[$operator])) {
  183. $operator = $this->queryOperatorMap[$operator];
  184. }
  185. return [$operator, $attribute, $this->filterAttributeValue($attribute, $condition)];
  186. }
  187. }