InConditionBuilder.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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 = $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. } else {
  49. $column = reset($column);
  50. }
  51. }
  52. if ($column instanceof \Traversable) {
  53. if (iterator_count($column) > 1) {
  54. return $this->buildCompositeInCondition($operator, $column, $values, $params);
  55. } else {
  56. $column->rewind();
  57. $column = $column->current();
  58. }
  59. }
  60. $sqlValues = $this->buildValues($expression, $values, $params);
  61. if (empty($sqlValues)) {
  62. return $operator === 'IN' ? '0=1' : '';
  63. }
  64. if (strpos($column, '(') === false) {
  65. $column = $this->queryBuilder->db->quoteColumnName($column);
  66. }
  67. if (count($sqlValues) > 1) {
  68. return "$column $operator (" . implode(', ', $sqlValues) . ')';
  69. }
  70. $operator = $operator === 'IN' ? '=' : '<>';
  71. return $column . $operator . reset($sqlValues);
  72. }
  73. /**
  74. * Builds $values to be used in [[InCondition]]
  75. *
  76. * @param ConditionInterface|InCondition $condition
  77. * @param array $values
  78. * @param array $params the binding parameters
  79. * @return array of prepared for SQL placeholders
  80. */
  81. protected function buildValues(ConditionInterface $condition, $values, &$params)
  82. {
  83. $sqlValues = [];
  84. $column = $condition->getColumn();
  85. if (is_array($column)) {
  86. $column = reset($column);
  87. }
  88. if ($column instanceof \Traversable) {
  89. $column->rewind();
  90. $column = $column->current();
  91. }
  92. foreach ($values as $i => $value) {
  93. if (is_array($value) || $value instanceof \ArrayAccess) {
  94. $value = isset($value[$column]) ? $value[$column] : null;
  95. }
  96. if ($value === null) {
  97. $sqlValues[$i] = 'NULL';
  98. } elseif ($value instanceof ExpressionInterface) {
  99. $sqlValues[$i] = $this->queryBuilder->buildExpression($value, $params);
  100. } else {
  101. $sqlValues[$i] = $this->queryBuilder->bindParam($value, $params);
  102. }
  103. }
  104. return $sqlValues;
  105. }
  106. /**
  107. * Builds SQL for IN condition.
  108. *
  109. * @param string $operator
  110. * @param array|string $columns
  111. * @param Query $values
  112. * @param array $params
  113. * @return string SQL
  114. */
  115. protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
  116. {
  117. $sql = $this->queryBuilder->buildExpression($values, $params);
  118. if (is_array($columns)) {
  119. foreach ($columns as $i => $col) {
  120. if (strpos($col, '(') === false) {
  121. $columns[$i] = $this->queryBuilder->db->quoteColumnName($col);
  122. }
  123. }
  124. return '(' . implode(', ', $columns) . ") $operator $sql";
  125. }
  126. if (strpos($columns, '(') === false) {
  127. $columns = $this->queryBuilder->db->quoteColumnName($columns);
  128. }
  129. return "$columns $operator $sql";
  130. }
  131. /**
  132. * Builds SQL for IN condition.
  133. *
  134. * @param string $operator
  135. * @param array|\Traversable $columns
  136. * @param array $values
  137. * @param array $params
  138. * @return string SQL
  139. */
  140. protected function buildCompositeInCondition($operator, $columns, $values, &$params)
  141. {
  142. $vss = [];
  143. foreach ($values as $value) {
  144. $vs = [];
  145. foreach ($columns as $column) {
  146. if (isset($value[$column])) {
  147. $vs[] = $this->queryBuilder->bindParam($value[$column], $params);
  148. } else {
  149. $vs[] = 'NULL';
  150. }
  151. }
  152. $vss[] = '(' . implode(', ', $vs) . ')';
  153. }
  154. if (empty($vss)) {
  155. return $operator === 'IN' ? '0=1' : '';
  156. }
  157. $sqlColumns = [];
  158. foreach ($columns as $i => $column) {
  159. $sqlColumns[] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
  160. }
  161. return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')';
  162. }
  163. }