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