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());
0 ignored issues
show
The method getOperator() does not exist on yii\db\ExpressionInterface. It seems like you code against a sub-type of said class. However, the method does not exist in yii\db\JsonExpression or yii\db\ArrayExpression or yii\db\PdoValue or yii\db\conditions\ConditionInterface or yii\db\conditions\HashCondition or yii\db\conditions\NotCondition. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

36
        $operator = strtoupper($expression->/** @scrutinizer ignore-call */ getOperator());
Loading history...
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