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