Passed
Push — master ( 9dbdd9...d5a428 )
by Alexander
04:15
created

framework/db/conditions/InConditionBuilder.php (1 issue)

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());
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) {
0 ignored issues
show
$values is always a sub-type of Traversable.
Loading history...
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