InConditionBuilder.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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\db\conditions;
  8. use yii\db\ExpressionBuilderInterface;
  9. use yii\db\ExpressionBuilderTrait;
  10. use yii\db\ExpressionInterface;
  11. use yii\db\Query;
  12. /**
  13. * Class InConditionBuilder builds objects of [[InCondition]]
  14. *
  15. * @author Dmytro Naumenko <d.naumenko.a@gmail.com>
  16. * @since 2.0.14
  17. */
  18. class InConditionBuilder implements ExpressionBuilderInterface
  19. {
  20. use ExpressionBuilderTrait;
  21. /**
  22. * Method builds the raw SQL from the $expression that will not be additionally
  23. * escaped or quoted.
  24. *
  25. * @param ExpressionInterface|InCondition $expression the expression to be built.
  26. * @param array $params the binding parameters.
  27. * @return string the raw SQL that will not be additionally escaped or quoted.
  28. */
  29. public function build(ExpressionInterface $expression, array &$params = [])
  30. {
  31. $operator = strtoupper($expression->getOperator());
  32. $column = $expression->getColumn();
  33. $values = $expression->getValues();
  34. if ($column === []) {
  35. // no columns to test against
  36. return $operator === 'IN' ? '0=1' : '';
  37. }
  38. if ($values instanceof Query) {
  39. return $this->buildSubqueryInCondition($operator, $column, $values, $params);
  40. }
  41. if (!is_array($values) && !$values instanceof \Traversable) {
  42. // ensure values is an array
  43. $values = (array) $values;
  44. }
  45. if (is_array($column)) {
  46. if (count($column) > 1) {
  47. return $this->buildCompositeInCondition($operator, $column, $values, $params);
  48. }
  49. $column = reset($column);
  50. }
  51. if ($column instanceof \Traversable) {
  52. if (iterator_count($column) > 1) {
  53. return $this->buildCompositeInCondition($operator, $column, $values, $params);
  54. }
  55. $column->rewind();
  56. $column = $column->current();
  57. }
  58. if (is_array($values)) {
  59. $rawValues = $values;
  60. } elseif ($values instanceof \Traversable) {
  61. $rawValues = $this->getRawValuesFromTraversableObject($values);
  62. }
  63. $nullCondition = null;
  64. $nullConditionOperator = null;
  65. if (isset($rawValues) && in_array(null, $rawValues, true)) {
  66. $nullCondition = $this->getNullCondition($operator, $column);
  67. $nullConditionOperator = $operator === 'IN' ? 'OR' : 'AND';
  68. }
  69. $sqlValues = $this->buildValues($expression, $values, $params);
  70. if (empty($sqlValues)) {
  71. if ($nullCondition === null) {
  72. return $operator === 'IN' ? '0=1' : '';
  73. }
  74. return $nullCondition;
  75. }
  76. if (strpos($column, '(') === false) {
  77. $column = $this->queryBuilder->db->quoteColumnName($column);
  78. }
  79. if (count($sqlValues) > 1) {
  80. $sql = "$column $operator (" . implode(', ', $sqlValues) . ')';
  81. } else {
  82. $operator = $operator === 'IN' ? '=' : '<>';
  83. $sql = $column . $operator . reset($sqlValues);
  84. }
  85. return $nullCondition !== null && $nullConditionOperator !== null
  86. ? sprintf('%s %s %s', $sql, $nullConditionOperator, $nullCondition)
  87. : $sql;
  88. }
  89. /**
  90. * Builds $values to be used in [[InCondition]]
  91. *
  92. * @param ConditionInterface|InCondition $condition
  93. * @param array $values
  94. * @param array $params the binding parameters
  95. * @return array of prepared for SQL placeholders
  96. */
  97. protected function buildValues(ConditionInterface $condition, $values, &$params)
  98. {
  99. $sqlValues = [];
  100. $column = $condition->getColumn();
  101. if (is_array($column)) {
  102. $column = reset($column);
  103. }
  104. if ($column instanceof \Traversable) {
  105. $column->rewind();
  106. $column = $column->current();
  107. }
  108. foreach ($values as $i => $value) {
  109. if (is_array($value) || $value instanceof \ArrayAccess) {
  110. $value = isset($value[$column]) ? $value[$column] : null;
  111. }
  112. if ($value === null) {
  113. continue;
  114. } elseif ($value instanceof ExpressionInterface) {
  115. $sqlValues[$i] = $this->queryBuilder->buildExpression($value, $params);
  116. } else {
  117. $sqlValues[$i] = $this->queryBuilder->bindParam($value, $params);
  118. }
  119. }
  120. return $sqlValues;
  121. }
  122. /**
  123. * Builds SQL for IN condition.
  124. *
  125. * @param string $operator
  126. * @param array|string $columns
  127. * @param Query $values
  128. * @param array $params
  129. * @return string SQL
  130. */
  131. protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
  132. {
  133. $sql = $this->queryBuilder->buildExpression($values, $params);
  134. if (is_array($columns)) {
  135. foreach ($columns as $i => $col) {
  136. if (strpos($col, '(') === false) {
  137. $columns[$i] = $this->queryBuilder->db->quoteColumnName($col);
  138. }
  139. }
  140. return '(' . implode(', ', $columns) . ") $operator $sql";
  141. }
  142. if (strpos($columns, '(') === false) {
  143. $columns = $this->queryBuilder->db->quoteColumnName($columns);
  144. }
  145. return "$columns $operator $sql";
  146. }
  147. /**
  148. * Builds SQL for IN condition.
  149. *
  150. * @param string $operator
  151. * @param array|\Traversable $columns
  152. * @param array $values
  153. * @param array $params
  154. * @return string SQL
  155. */
  156. protected function buildCompositeInCondition($operator, $columns, $values, &$params)
  157. {
  158. $vss = [];
  159. foreach ($values as $value) {
  160. $vs = [];
  161. foreach ($columns as $column) {
  162. if (isset($value[$column])) {
  163. $vs[] = $this->queryBuilder->bindParam($value[$column], $params);
  164. } else {
  165. $vs[] = 'NULL';
  166. }
  167. }
  168. $vss[] = '(' . implode(', ', $vs) . ')';
  169. }
  170. if (empty($vss)) {
  171. return $operator === 'IN' ? '0=1' : '';
  172. }
  173. $sqlColumns = [];
  174. foreach ($columns as $i => $column) {
  175. $sqlColumns[] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
  176. }
  177. return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')';
  178. }
  179. /**
  180. * Builds is null/is not null condition for column based on operator
  181. *
  182. * @param string $operator
  183. * @param string $column
  184. * @return string is null or is not null condition
  185. * @since 2.0.31
  186. */
  187. protected function getNullCondition($operator, $column) {
  188. $column = $this->queryBuilder->db->quoteColumnName($column);
  189. if ($operator === 'IN') {
  190. return sprintf('%s IS NULL', $column);
  191. }
  192. return sprintf('%s IS NOT NULL', $column);
  193. }
  194. /**
  195. * @param \Traversable $traversableObject
  196. * @return array raw values
  197. * @since 2.0.31
  198. */
  199. protected function getRawValuesFromTraversableObject(\Traversable $traversableObject)
  200. {
  201. $rawValues = [];
  202. foreach ($traversableObject as $value) {
  203. if (is_array($value)) {
  204. $values = array_values($value);
  205. $rawValues = array_merge($rawValues, $values);
  206. } else {
  207. $rawValues[] = $value;
  208. }
  209. }
  210. return $rawValues;
  211. }
  212. }